From 91cc02ecc09284974dbe8e59edfa31d2a58f1eac Mon Sep 17 00:00:00 2001 From: jurvis Date: Sun, 4 Dec 2022 17:24:08 -0800 Subject: [PATCH] Expose pending payments through ChannelManager Adds a new method, `list_pending_payments` to `ChannelManager` that returns an array of `PendingPaymentDetails` containing the payment status (Fulfilled/Retryable/Abandoned) and its total amount across all paths. --- lightning/src/ln/channelmanager.rs | 38 ++++++++++++++++++++++++++++++ lightning/src/ln/payment_tests.rs | 25 +++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b22dacdd686..d8efe007946 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -485,6 +485,7 @@ impl PendingOutboundPayment { _ => false, } } + fn abandoned(&self) -> bool { match self { PendingOutboundPayment::Abandoned { .. } => true, @@ -1203,6 +1204,25 @@ impl ChannelDetails { } } +/// We store payments that are pending resolution as `PendingOutboundPayment` in `ChannelManager`, +/// and they can take on either one of the following states as defined in this enum. +#[derive(Debug, PartialEq)] +pub enum PendingPaymentStatus { + Retryable, + Fulfilled, + Abandoned, +} + +/// Details of a pending payment, as returned by [`ChannelManager::list_pending_payments`]. +pub struct PendingPaymentDetails { + /// Current status of a payment that is pending resolution. + pub status: PendingPaymentStatus, + /// Total amount (in msat) across all paths for this payment, not just the amount currently + /// inflight. This field is `None` if the payment's status is either + /// `PendingPaymentStatus::Fulfilled` or `PendingPaymentPaymentStatus::Abandoned`. + pub total_msat: Option, +} + /// If a payment fails to send, it can be in one of several states. This enum is returned as the /// Err() type describing which state the payment is in, see the description of individual enum /// states for more. @@ -1770,6 +1790,24 @@ impl ChannelManager Vec { + self.pending_outbound_payments.lock().unwrap().iter() + .filter_map(|(_, pending_outbound_payment)| match pending_outbound_payment { + PendingOutboundPayment::Retryable { total_msat, .. } => { + Some(PendingPaymentDetails { status: PendingPaymentStatus::Retryable, total_msat: Some(*total_msat) }) + }, + PendingOutboundPayment::Abandoned { .. } => { + Some(PendingPaymentDetails { status: PendingPaymentStatus::Abandoned, total_msat: None }) + }, + PendingOutboundPayment::Fulfilled { .. } => { + Some(PendingPaymentDetails { status: PendingPaymentStatus::Fulfilled, total_msat: None }) + }, + PendingOutboundPayment::Legacy { .. } => None + }) + .collect() + } + /// Helper function that issues the channel close events fn issue_channel_close_events(&self, channel: &Channel<::Signer>, closure_reason: ClosureReason) { let mut pending_events_lock = self.pending_events.lock().unwrap(); diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index baaa60a7a6a..878fab89a6a 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -16,7 +16,7 @@ use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS use crate::chain::transaction::OutPoint; use crate::chain::keysinterface::KeysInterface; use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS; -use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS}; +use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PendingPaymentStatus, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS}; use crate::ln::msgs; use crate::ln::msgs::ChannelMessageHandler; use crate::routing::router::{PaymentParameters, get_route}; @@ -1279,6 +1279,15 @@ fn test_trivial_inflight_htlc_tracking(){ assert_eq!(chan_1_used_liquidity, None); assert_eq!(chan_2_used_liquidity, None); + + let pending_payments = nodes[0].node.list_pending_payments(); + assert_eq!(pending_payments.len(), 1); + assert_eq!(pending_payments[0].status, PendingPaymentStatus::Fulfilled); + } + + // Remove fulfilled payment + for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS { + nodes[0].node.timer_tick_occurred(); } // Send the payment, but do not claim it. Our inflight HTLCs should contain the pending payment. @@ -1305,10 +1314,21 @@ fn test_trivial_inflight_htlc_tracking(){ // First hop accounts for expected 1000 msat fee assert_eq!(chan_1_used_liquidity, Some(501000)); assert_eq!(chan_2_used_liquidity, Some(500000)); + + let pending_payments = nodes[0].node.list_pending_payments(); + assert_eq!(pending_payments.len(), 1); + assert_eq!(pending_payments[0].status, PendingPaymentStatus::Retryable); + assert_eq!(pending_payments[0].total_msat, Some(500000)); } // 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); + + // Remove fulfilled payment + for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS { + nodes[0].node.timer_tick_occurred(); + } + { let inflight_htlcs = node_chanmgrs[0].compute_inflight_htlcs(); @@ -1330,6 +1350,9 @@ fn test_trivial_inflight_htlc_tracking(){ assert_eq!(chan_1_used_liquidity, None); assert_eq!(chan_2_used_liquidity, None); + + let pending_payments = nodes[0].node.list_pending_payments(); + assert_eq!(pending_payments.len(), 0); } }