use super::*;
use crate::control::ControlCommand;
use crate::ledger::receivables::{BlockedPeer, Forgiven, Receivable};
const PEER: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const OTHER: &str = "0123456789abffffffffffffffffffffffffffffffffffffffffffffffffffff";
fn pricing() -> Pricing {
Pricing {
input_msat_per_million: 1000,
output_msat_per_million: 1000,
minimum_invoice_msat: 1,
}
}
async fn unblock(service: &PaymentService, peer: &str) -> Result<Forgiven> {
Ok(serde_json::from_value(
service
.control(ControlCommand::Unblock { peer: peer.into() })
.await?,
)?)
}
#[tokio::test]
async fn blocked_peers_are_listed_with_their_identifier_and_unblocked_by_prefix() -> Result<()> {
let dir = tempfile::tempdir()?;
let service = PaymentService::open(dir.path())?;
assert!(service.ledger.blocked_peers()?.is_empty());
service
.ledger
.begin_serving("req-a", PEER, &pricing(), 10)?;
service.ledger.record_receivable(&Receivable {
request_id: "req-a".into(),
peer: PEER.into(),
segment: 0,
invoice: invoice(1, 100),
tokens: 10,
paid: false,
})?;
service
.ledger
.begin_serving("req-b", OTHER, &pricing(), 10)?;
service.ledger.record_delivered_tokens("req-b", 7)?;
service.ledger.finish_serving("req-b")?;
assert!(
service
.ledger
.begin_serving("req-c", PEER, &pricing(), 10)
.is_err()
);
assert!(
service
.ledger
.begin_serving("req-d", OTHER, &pricing(), 10)
.is_err()
);
assert!(service.ledger.has_outstanding_payment(PEER)?);
assert!(service.ledger.has_outstanding_payment(OTHER)?);
assert_eq!(
service.ledger.uninvoiced_output()?,
vec!["req-b".to_string()]
);
let blocked: Vec<BlockedPeer> =
serde_json::from_value(service.control(ControlCommand::Blocked).await?)?;
assert_eq!(blocked.len(), 2);
let peer = blocked.iter().find(|entry| entry.peer == PEER).unwrap();
assert_eq!(peer.peer_short, &PEER[..10]);
assert_eq!(peer.debts.len(), 1);
assert_eq!(peer.debts[0].kind, "unpaid_invoice");
assert_eq!(peer.debts[0].request_id, "req-a");
assert_eq!(peer.debts[0].amount_msat, Some(100));
assert!(peer.debts[0].expires_at_ms.is_some());
let other = blocked.iter().find(|entry| entry.peer == OTHER).unwrap();
assert_eq!(other.debts.len(), 1);
assert_eq!(other.debts[0].kind, "uninvoiced_output");
assert_eq!(other.debts[0].request_id, "req-b");
assert_eq!(other.debts[0].tokens, 7);
assert_eq!(other.debts[0].amount_msat, None);
assert!(unblock(&service, &PEER[..7]).await.is_err());
assert!(unblock(&service, "ffffffffffff").await.is_err());
assert!(unblock(&service, &PEER[..12]).await.is_err());
assert_eq!(service.ledger.blocked_peers()?.len(), 2);
let forgiven = unblock(&service, &PEER[..13]).await?;
assert_eq!(
(
forgiven.peer.as_str(),
forgiven.invoices,
forgiven.output_requests
),
(PEER, 1, 0)
);
let forgiven = unblock(&service, OTHER).await?;
assert_eq!(
(
forgiven.peer.as_str(),
forgiven.invoices,
forgiven.output_requests
),
(OTHER, 0, 1)
);
assert!(service.ledger.blocked_peers()?.is_empty());
assert!(!service.ledger.has_outstanding_payment(PEER)?);
assert!(!service.ledger.has_outstanding_payment(OTHER)?);
assert!(service.ledger.uninvoiced_output()?.is_empty());
service
.ledger
.begin_serving("req-c", PEER, &pricing(), 10)?;
service
.ledger
.begin_serving("req-d", OTHER, &pricing(), 10)?;
assert!(unblock(&service, PEER).await.is_err());
drop(service);
let service = PaymentService::open(dir.path())?;
assert!(service.ledger.blocked_peers()?.is_empty());
let (_, _, tokens, finished) = service.ledger.serving_account("req-b")?;
assert_eq!((tokens, finished), (7, true));
Ok(())
}
#[tokio::test]
async fn abandoned_input_with_nothing_delivered_does_not_block() -> Result<()> {
let grace = crate::lifetimes::INPUT_LAPSE_GRACE.as_millis() as u64;
let dir = tempfile::tempdir()?;
let service = PaymentService::open(dir.path())?;
let expired = invoice_with_expiry(1, 100, 1);
let lapsed_at = expired.expires_at_ms + grace;
service.ledger.begin_serving("slow", PEER, &pricing(), 10)?;
service.ledger.record_receivable(&Receivable {
request_id: "slow".into(),
peer: PEER.into(),
segment: 0,
invoice: expired.clone(),
tokens: 10,
paid: false,
})?;
assert!(
!service
.ledger
.lapse_abandoned_input("slow", &expired, lapsed_at)?
);
service.ledger.close_interrupted_serving()?;
assert!(
!service
.ledger
.lapse_abandoned_input("slow", &expired, expired.expires_at_ms)?
);
assert!(
!service
.ledger
.lapse_abandoned_input("slow", &expired, lapsed_at - 1)?
);
assert!(service.ledger.has_outstanding_payment(PEER)?);
assert_eq!(
service.ledger.unpaid_input_requests(PEER)?,
vec!["slow".to_string()]
);
assert!(
service
.ledger
.lapse_abandoned_input("slow", &expired, lapsed_at)?
);
assert!(!service.ledger.has_outstanding_payment(PEER)?);
assert!(service.ledger.blocked_peers()?.is_empty());
assert!(service.ledger.unpaid_input_requests(PEER)?.is_empty());
service.ledger.begin_serving("next", PEER, &pricing(), 10)?;
service.ledger.mark_received(&expired.payment_hash)?;
assert!(service.ledger.receivables(Some("slow"))?[0].paid);
let delivered = invoice_with_expiry(2, 100, 1);
service
.ledger
.begin_serving("owed", OTHER, &pricing(), 10)?;
service.ledger.record_receivable(&Receivable {
request_id: "owed".into(),
peer: OTHER.into(),
segment: 0,
invoice: delivered.clone(),
tokens: 10,
paid: false,
})?;
service.ledger.record_delivered_tokens("owed", 3)?;
service.ledger.finish_serving("owed")?;
assert!(!service.ledger.lapse_abandoned_input(
"owed",
&delivered,
delivered.expires_at_ms + grace
)?);
assert!(service.ledger.has_outstanding_payment(OTHER)?);
Ok(())
}