From 070832643f9a38c966116d75b41b422a3f1a5012 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 16 Dec 2022 15:32:52 -0500 Subject: [PATCH] Move PendingOutboundPayment to new outbound_payment module We want to move all outbound payment-related things to this new module, to help break up ChannelManager so future payment retries work doesn't increase the size of ChannelManager. --- lightning/src/ln/channelmanager.rs | 179 +------------------------ lightning/src/ln/mod.rs | 1 + lightning/src/ln/outbound_payment.rs | 193 +++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 178 deletions(-) create mode 100644 lightning/src/ln/outbound_payment.rs diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b8372ad9b..a12e4110e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -51,6 +51,7 @@ use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::HTLCFailReason; use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError, MAX_VALUE_MSAT}; +use crate::ln::outbound_payment::PendingOutboundPayment; use crate::ln::wire::Encode; use crate::chain::keysinterface::{Sign, KeysInterface, KeysManager, Recipient}; use crate::util::config::{UserConfig, ChannelConfig}; @@ -480,160 +481,6 @@ struct PendingInboundPayment { min_value_msat: Option, } -/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102 -/// and later, also stores information for retrying the payment. -pub(crate) enum PendingOutboundPayment { - Legacy { - session_privs: HashSet<[u8; 32]>, - }, - Retryable { - session_privs: HashSet<[u8; 32]>, - payment_hash: PaymentHash, - payment_secret: Option, - pending_amt_msat: u64, - /// Used to track the fee paid. Only present if the payment was serialized on 0.0.103+. - pending_fee_msat: Option, - /// The total payment amount across all paths, used to verify that a retry is not overpaying. - total_msat: u64, - /// Our best known block height at the time this payment was initiated. - starting_block_height: u32, - }, - /// When a pending payment is fulfilled, we continue tracking it until all pending HTLCs have - /// been resolved. This ensures we don't look up pending payments in ChannelMonitors on restart - /// and add a pending payment that was already fulfilled. - Fulfilled { - session_privs: HashSet<[u8; 32]>, - payment_hash: Option, - timer_ticks_without_htlcs: u8, - }, - /// When a payer gives up trying to retry a payment, they inform us, letting us generate a - /// `PaymentFailed` event when all HTLCs have irrevocably failed. This avoids a number of race - /// conditions in MPP-aware payment retriers (1), where the possibility of multiple - /// `PaymentPathFailed` events with `all_paths_failed` can be pending at once, confusing a - /// downstream event handler as to when a payment has actually failed. - /// - /// (1) https://github.com/lightningdevkit/rust-lightning/issues/1164 - Abandoned { - session_privs: HashSet<[u8; 32]>, - payment_hash: PaymentHash, - }, -} - -impl PendingOutboundPayment { - fn is_fulfilled(&self) -> bool { - match self { - PendingOutboundPayment::Fulfilled { .. } => true, - _ => false, - } - } - fn abandoned(&self) -> bool { - match self { - PendingOutboundPayment::Abandoned { .. } => true, - _ => false, - } - } - fn get_pending_fee_msat(&self) -> Option { - match self { - PendingOutboundPayment::Retryable { pending_fee_msat, .. } => pending_fee_msat.clone(), - _ => None, - } - } - - fn payment_hash(&self) -> Option { - match self { - PendingOutboundPayment::Legacy { .. } => None, - PendingOutboundPayment::Retryable { payment_hash, .. } => Some(*payment_hash), - PendingOutboundPayment::Fulfilled { payment_hash, .. } => *payment_hash, - PendingOutboundPayment::Abandoned { payment_hash, .. } => Some(*payment_hash), - } - } - - fn mark_fulfilled(&mut self) { - let mut session_privs = HashSet::new(); - core::mem::swap(&mut session_privs, match self { - PendingOutboundPayment::Legacy { session_privs } | - PendingOutboundPayment::Retryable { session_privs, .. } | - PendingOutboundPayment::Fulfilled { session_privs, .. } | - PendingOutboundPayment::Abandoned { session_privs, .. } - => session_privs, - }); - let payment_hash = self.payment_hash(); - *self = PendingOutboundPayment::Fulfilled { session_privs, payment_hash, timer_ticks_without_htlcs: 0 }; - } - - fn mark_abandoned(&mut self) -> Result<(), ()> { - let mut session_privs = HashSet::new(); - let our_payment_hash; - core::mem::swap(&mut session_privs, match self { - PendingOutboundPayment::Legacy { .. } | - PendingOutboundPayment::Fulfilled { .. } => - return Err(()), - PendingOutboundPayment::Retryable { session_privs, payment_hash, .. } | - PendingOutboundPayment::Abandoned { session_privs, payment_hash, .. } => { - our_payment_hash = *payment_hash; - session_privs - }, - }); - *self = PendingOutboundPayment::Abandoned { session_privs, payment_hash: our_payment_hash }; - Ok(()) - } - - /// panics if path is None and !self.is_fulfilled - fn remove(&mut self, session_priv: &[u8; 32], path: Option<&Vec>) -> bool { - let remove_res = match self { - PendingOutboundPayment::Legacy { session_privs } | - PendingOutboundPayment::Retryable { session_privs, .. } | - PendingOutboundPayment::Fulfilled { session_privs, .. } | - PendingOutboundPayment::Abandoned { session_privs, .. } => { - session_privs.remove(session_priv) - } - }; - if remove_res { - if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self { - let path = path.expect("Fulfilling a payment should always come with a path"); - let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); - *pending_amt_msat -= path_last_hop.fee_msat; - if let Some(fee_msat) = pending_fee_msat.as_mut() { - *fee_msat -= path.get_path_fees(); - } - } - } - remove_res - } - - fn insert(&mut self, session_priv: [u8; 32], path: &Vec) -> bool { - let insert_res = match self { - PendingOutboundPayment::Legacy { session_privs } | - PendingOutboundPayment::Retryable { session_privs, .. } => { - session_privs.insert(session_priv) - } - PendingOutboundPayment::Fulfilled { .. } => false, - PendingOutboundPayment::Abandoned { .. } => false, - }; - if insert_res { - if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self { - let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); - *pending_amt_msat += path_last_hop.fee_msat; - if let Some(fee_msat) = pending_fee_msat.as_mut() { - *fee_msat += path.get_path_fees(); - } - } - } - insert_res - } - - fn remaining_parts(&self) -> usize { - match self { - PendingOutboundPayment::Legacy { session_privs } | - PendingOutboundPayment::Retryable { session_privs, .. } | - PendingOutboundPayment::Fulfilled { session_privs, .. } | - PendingOutboundPayment::Abandoned { session_privs, .. } => { - session_privs.len() - } - } - } -} - /// SimpleArcChannelManager is useful when you need a ChannelManager with a static lifetime, e.g. /// when you're using lightning-net-tokio (since tokio::spawn requires parameters with static /// lifetimes). Other times you can afford a reference, which is more efficient, in which case @@ -6950,30 +6797,6 @@ impl_writeable_tlv_based!(PendingInboundPayment, { (8, min_value_msat, required), }); -impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, - (0, Legacy) => { - (0, session_privs, required), - }, - (1, Fulfilled) => { - (0, session_privs, required), - (1, payment_hash, option), - (3, timer_ticks_without_htlcs, (default_value, 0)), - }, - (2, Retryable) => { - (0, session_privs, required), - (1, pending_fee_msat, option), - (2, payment_hash, required), - (4, payment_secret, option), - (6, total_msat, required), - (8, pending_amt_msat, required), - (10, starting_block_height, required), - }, - (3, Abandoned) => { - (0, session_privs, required), - (2, payment_hash, required), - }, -); - impl Writeable for ChannelManager where M::Target: chain::Watch<::Signer>, T::Target: BroadcasterInterface, diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index bdf3e8029..bc2806314 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -44,6 +44,7 @@ pub mod channel; pub(crate) mod channel; pub(crate) mod onion_utils; +mod outbound_payment; pub mod wire; // Older rustc (which we support) refuses to let us call the get_payment_preimage_hash!() macro diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs new file mode 100644 index 000000000..ea1ba8112 --- /dev/null +++ b/lightning/src/ln/outbound_payment.rs @@ -0,0 +1,193 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Utilities to send payments and manage outbound payment information. + +use crate::ln::{PaymentHash, PaymentSecret}; +use crate::ln::msgs::DecodeError; +use crate::routing::router::{RouteHop, RoutePath}; +use crate::prelude::*; + +/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102 +/// and later, also stores information for retrying the payment. +pub(crate) enum PendingOutboundPayment { + Legacy { + session_privs: HashSet<[u8; 32]>, + }, + Retryable { + session_privs: HashSet<[u8; 32]>, + payment_hash: PaymentHash, + payment_secret: Option, + pending_amt_msat: u64, + /// Used to track the fee paid. Only present if the payment was serialized on 0.0.103+. + pending_fee_msat: Option, + /// The total payment amount across all paths, used to verify that a retry is not overpaying. + total_msat: u64, + /// Our best known block height at the time this payment was initiated. + starting_block_height: u32, + }, + /// When a pending payment is fulfilled, we continue tracking it until all pending HTLCs have + /// been resolved. This ensures we don't look up pending payments in ChannelMonitors on restart + /// and add a pending payment that was already fulfilled. + Fulfilled { + session_privs: HashSet<[u8; 32]>, + payment_hash: Option, + timer_ticks_without_htlcs: u8, + }, + /// When a payer gives up trying to retry a payment, they inform us, letting us generate a + /// `PaymentFailed` event when all HTLCs have irrevocably failed. This avoids a number of race + /// conditions in MPP-aware payment retriers (1), where the possibility of multiple + /// `PaymentPathFailed` events with `all_paths_failed` can be pending at once, confusing a + /// downstream event handler as to when a payment has actually failed. + /// + /// (1) https://github.com/lightningdevkit/rust-lightning/issues/1164 + Abandoned { + session_privs: HashSet<[u8; 32]>, + payment_hash: PaymentHash, + }, +} + +impl PendingOutboundPayment { + pub(super) fn is_fulfilled(&self) -> bool { + match self { + PendingOutboundPayment::Fulfilled { .. } => true, + _ => false, + } + } + pub(super) fn abandoned(&self) -> bool { + match self { + PendingOutboundPayment::Abandoned { .. } => true, + _ => false, + } + } + pub(super) fn get_pending_fee_msat(&self) -> Option { + match self { + PendingOutboundPayment::Retryable { pending_fee_msat, .. } => pending_fee_msat.clone(), + _ => None, + } + } + + pub(super) fn payment_hash(&self) -> Option { + match self { + PendingOutboundPayment::Legacy { .. } => None, + PendingOutboundPayment::Retryable { payment_hash, .. } => Some(*payment_hash), + PendingOutboundPayment::Fulfilled { payment_hash, .. } => *payment_hash, + PendingOutboundPayment::Abandoned { payment_hash, .. } => Some(*payment_hash), + } + } + + pub(super) fn mark_fulfilled(&mut self) { + let mut session_privs = HashSet::new(); + core::mem::swap(&mut session_privs, match self { + PendingOutboundPayment::Legacy { session_privs } | + PendingOutboundPayment::Retryable { session_privs, .. } | + PendingOutboundPayment::Fulfilled { session_privs, .. } | + PendingOutboundPayment::Abandoned { session_privs, .. } + => session_privs, + }); + let payment_hash = self.payment_hash(); + *self = PendingOutboundPayment::Fulfilled { session_privs, payment_hash, timer_ticks_without_htlcs: 0 }; + } + + pub(super) fn mark_abandoned(&mut self) -> Result<(), ()> { + let mut session_privs = HashSet::new(); + let our_payment_hash; + core::mem::swap(&mut session_privs, match self { + PendingOutboundPayment::Legacy { .. } | + PendingOutboundPayment::Fulfilled { .. } => + return Err(()), + PendingOutboundPayment::Retryable { session_privs, payment_hash, .. } | + PendingOutboundPayment::Abandoned { session_privs, payment_hash, .. } => { + our_payment_hash = *payment_hash; + session_privs + }, + }); + *self = PendingOutboundPayment::Abandoned { session_privs, payment_hash: our_payment_hash }; + Ok(()) + } + + /// panics if path is None and !self.is_fulfilled + pub(super) fn remove(&mut self, session_priv: &[u8; 32], path: Option<&Vec>) -> bool { + let remove_res = match self { + PendingOutboundPayment::Legacy { session_privs } | + PendingOutboundPayment::Retryable { session_privs, .. } | + PendingOutboundPayment::Fulfilled { session_privs, .. } | + PendingOutboundPayment::Abandoned { session_privs, .. } => { + session_privs.remove(session_priv) + } + }; + if remove_res { + if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self { + let path = path.expect("Fulfilling a payment should always come with a path"); + let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); + *pending_amt_msat -= path_last_hop.fee_msat; + if let Some(fee_msat) = pending_fee_msat.as_mut() { + *fee_msat -= path.get_path_fees(); + } + } + } + remove_res + } + + pub(super) fn insert(&mut self, session_priv: [u8; 32], path: &Vec) -> bool { + let insert_res = match self { + PendingOutboundPayment::Legacy { session_privs } | + PendingOutboundPayment::Retryable { session_privs, .. } => { + session_privs.insert(session_priv) + } + PendingOutboundPayment::Fulfilled { .. } => false, + PendingOutboundPayment::Abandoned { .. } => false, + }; + if insert_res { + if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self { + let path_last_hop = path.last().expect("Outbound payments must have had a valid path"); + *pending_amt_msat += path_last_hop.fee_msat; + if let Some(fee_msat) = pending_fee_msat.as_mut() { + *fee_msat += path.get_path_fees(); + } + } + } + insert_res + } + + pub(super) fn remaining_parts(&self) -> usize { + match self { + PendingOutboundPayment::Legacy { session_privs } | + PendingOutboundPayment::Retryable { session_privs, .. } | + PendingOutboundPayment::Fulfilled { session_privs, .. } | + PendingOutboundPayment::Abandoned { session_privs, .. } => { + session_privs.len() + } + } + } +} + +impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, + (0, Legacy) => { + (0, session_privs, required), + }, + (1, Fulfilled) => { + (0, session_privs, required), + (1, payment_hash, option), + (3, timer_ticks_without_htlcs, (default_value, 0)), + }, + (2, Retryable) => { + (0, session_privs, required), + (1, pending_fee_msat, option), + (2, payment_hash, required), + (4, payment_secret, option), + (6, total_msat, required), + (8, pending_amt_msat, required), + (10, starting_block_height, required), + }, + (3, Abandoned) => { + (0, session_privs, required), + (2, payment_hash, required), + }, +); -- 2.39.5