From 419908da9a4c4ab947444fac3e750668978e9a72 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 7 May 2026 15:12:58 +0200 Subject: [PATCH] Expose probe status in recent payments Previously, `ChannelManager::list_recent_payments` didn't give us the means to discern 'real' payments from inflight probes. In https://github.com/lightningdevkit/ldk-node/pull/815 we found that we need a way to re-derive which probes are still pending so our accounting of inflight probing amounts is still correct after restart. To this end, we here let callers distinguish liquidity probes while they are pending or abandoned. Co-Authored-By: HAL 9000 Co-Authored-By: HAL 9000 Signed-off-by: Elias Rohrer --- lightning/src/ln/channelmanager.rs | 14 +++++++++-- lightning/src/ln/payment_tests.rs | 38 +++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 64486598005..f5a2bb02f83 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -84,7 +84,6 @@ use crate::ln::onion_utils::{ }; use crate::ln::onion_utils::{process_fulfill_attribution_data, AttributionData}; use crate::ln::our_peer_storage::{EncryptedOurPeerStorage, PeerStorageMonitorHolder}; -#[cfg(test)] use crate::ln::outbound_payment; #[cfg(any(test, feature = "_externalize_tests"))] use crate::ln::outbound_payment::PaymentSendFailure; @@ -3289,6 +3288,8 @@ pub enum RecentPaymentDetails { /// Total amount (in msat, excluding fees) across all paths for this payment, /// not just the amount currently inflight. total_msat: u64, + /// Whether this payment is a liquidity probe. + is_probe: bool, }, /// 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 @@ -3316,6 +3317,8 @@ pub enum RecentPaymentDetails { payment_id: PaymentId, /// Hash of the payment that we have given up trying to send. payment_hash: PaymentHash, + /// Whether this payment is a liquidity probe. + is_probe: bool, }, } @@ -4102,14 +4105,21 @@ impl< Some(RecentPaymentDetails::AwaitingInvoice { payment_id: *payment_id }) }, PendingOutboundPayment::Retryable { payment_hash, total_msat, .. } => { + let is_probe = outbound_payment::payment_is_probe(payment_hash, payment_id, self.probing_cookie_secret); Some(RecentPaymentDetails::Pending { payment_id: *payment_id, payment_hash: *payment_hash, total_msat: *total_msat, + is_probe, }) }, PendingOutboundPayment::Abandoned { payment_hash, .. } => { - Some(RecentPaymentDetails::Abandoned { payment_id: *payment_id, payment_hash: *payment_hash }) + let is_probe = outbound_payment::payment_is_probe(payment_hash, payment_id, self.probing_cookie_secret); + Some(RecentPaymentDetails::Abandoned { + payment_id: *payment_id, + payment_hash: *payment_hash, + is_probe, + }) }, PendingOutboundPayment::Fulfilled { payment_hash, .. } => { Some(RecentPaymentDetails::Fulfilled { payment_id: *payment_id, payment_hash: *payment_hash }) diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 5b4f5f93d71..f2ab44f8fe3 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -1591,17 +1591,32 @@ fn sent_probe_is_probe_of_sending_node() { // Then build an actual two-hop probing path let (route, _, _, _) = get_route_and_payment_hash!(&nodes[0], nodes[2], 100_000); - match nodes[0].node.send_probe(route.paths[0].clone()) { - Ok((payment_hash, payment_id)) => { - assert!(nodes[0].node.payment_is_probe(&payment_hash, &payment_id)); - assert!(!nodes[1].node.payment_is_probe(&payment_hash, &payment_id)); - assert!(!nodes[2].node.payment_is_probe(&payment_hash, &payment_id)); - }, - _ => panic!(), - } + let (payment_hash, payment_id) = nodes[0].node.send_probe(route.paths[0].clone()).unwrap(); + assert!(nodes[0].node.payment_is_probe(&payment_hash, &payment_id)); + assert!(!nodes[1].node.payment_is_probe(&payment_hash, &payment_id)); + assert!(!nodes[2].node.payment_is_probe(&payment_hash, &payment_id)); + assert!(matches!( + nodes[0].node.list_recent_payments().as_slice(), + [RecentPaymentDetails::Pending { + payment_id: listed_payment_id, + payment_hash: listed_payment_hash, + is_probe: true, + .. + }] if *listed_payment_id == payment_id && *listed_payment_hash == payment_hash + )); get_htlc_update_msgs(&nodes[0], &node_b_id); check_added_monitors(&nodes[0], 1); + + nodes[0].node.abandon_payment(payment_id); + assert!(matches!( + nodes[0].node.list_recent_payments().as_slice(), + [RecentPaymentDetails::Abandoned { + payment_id: listed_payment_id, + payment_hash: listed_payment_hash, + is_probe: true, + }] if *listed_payment_id == payment_id && *listed_payment_hash == payment_hash + )); } #[test] @@ -2142,7 +2157,12 @@ 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 }; + let details = RecentPaymentDetails::Pending { + payment_id, + payment_hash, + total_msat: 500000, + is_probe: false, + }; assert_eq!(pending_payments[0], details); // Now, let's claim the payment. This should result in the used liquidity to return `None`.