diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a76f061515e..d554855176d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -3179,6 +3179,11 @@ pub enum RecentPaymentDetails { /// Total amount (in msat, excluding fees) across all paths for this payment, /// not just the amount currently inflight. total_msat: u64, + /// Amount (in msat) currently locked in HTLCs. + /// + /// `total_msat - inflight_msat` gives the amount waiting to be retried + /// Reserve both from spendable balance. + inflight_msat: u64, }, /// When a pending payment is fulfilled, we continue tracking it until all pending HTLCs have /// been resolved. Upon receiving [`Event::PaymentSent`], we delay for a few minutes before the @@ -3884,11 +3889,12 @@ where PendingOutboundPayment::StaticInvoiceReceived { .. } => { Some(RecentPaymentDetails::AwaitingInvoice { payment_id: *payment_id }) }, - PendingOutboundPayment::Retryable { payment_hash, total_msat, .. } => { + PendingOutboundPayment::Retryable { payment_hash, total_msat, pending_amt_msat, .. } => { Some(RecentPaymentDetails::Pending { payment_id: *payment_id, payment_hash: *payment_hash, total_msat: *total_msat, + inflight_msat: *pending_amt_msat, }) }, PendingOutboundPayment::Abandoned { payment_hash, .. } => { diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 8f209c88e25..9fa9d14a43f 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -2098,8 +2098,16 @@ fn test_trivial_inflight_htlc_tracking() { } let pending_payments = nodes[0].node.list_recent_payments(); assert_eq!(pending_payments.len(), 1); - let details = RecentPaymentDetails::Pending { payment_id, payment_hash, total_msat: 500000 }; - assert_eq!(pending_payments[0], details); + match &pending_payments[0] { + RecentPaymentDetails::Pending { + payment_id: pid, payment_hash: ph, total_msat: tm, .. + } => { + assert_eq!(*pid, payment_id); + assert_eq!(*ph, payment_hash); + assert_eq!(*tm, 500000); + }, + _ => panic!("Expected Pending payment details"), + } // Now, let's claim the payment. This should result in the used liquidity to return `None`. claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); @@ -2141,6 +2149,39 @@ fn test_trivial_inflight_htlc_tracking() { assert_eq!(pending_payments.len(), 0); } +#[test] +fn test_pending_payment_tracking() { + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes(&nodes, 0, 1); + create_announced_chan_between_nodes(&nodes, 1, 2); + + let payment_amt = 100_000; + let (payment_preimage, _payment_hash, _, payment_id) = + route_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_amt); + + let pending_payments = nodes[0].node.list_recent_payments(); + assert_eq!(pending_payments.len(), 1); + match &pending_payments[0] { + RecentPaymentDetails::Pending { payment_id: pid, total_msat, inflight_msat, .. } => { + assert_eq!(*pid, payment_id); + assert_eq!(*total_msat, payment_amt); + assert_eq!(*inflight_msat, payment_amt); + }, + _ => panic!("Expected Pending payment details"), + } + + claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); + + for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS { + nodes[0].node.timer_tick_occurred(); + } + assert!(nodes[0].node.list_recent_payments().is_empty()); +} + #[test] fn test_holding_cell_inflight_htlcs() { let chanmon_cfgs = create_chanmon_cfgs(2); diff --git a/pending_changelog/3374-pending-retry-amount.txt b/pending_changelog/3374-pending-retry-amount.txt new file mode 100644 index 00000000000..6619a0e378f --- /dev/null +++ b/pending_changelog/3374-pending-retry-amount.txt @@ -0,0 +1,6 @@ +## API Updates + +* `RecentPaymentDetails::Pending` now includes `inflight_msat`, tracking the + amount currently locked in HTLCs. The difference `total_msat - inflight_msat` + is the amount waiting to retry. Reserve both from spendable balance to avoid + balance flicker during payment retries (#3374).