X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Foutbound_payment.rs;h=f37a745072902e0cee9c3dbb375ecc22b5e56a28;hb=48cba2954b245f351c875c36fd22a7173d532ebe;hp=025a197348ad18c616ffc0ea114ecaa6c3f116f5;hpb=0c31c6f162df1ae280fd232e26d0c1f173959ee6;p=rust-lightning diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 025a1973..f37a7450 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -19,16 +19,17 @@ use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, PaymentId}; use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason}; use crate::offers::invoice::Bolt12Invoice; -use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router}; +use crate::routing::router::{BlindedTail, InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router}; use crate::util::errors::APIError; use crate::util::logger::Logger; use crate::util::time::Time; -#[cfg(all(not(feature = "no-std"), test))] +#[cfg(all(feature = "std", test))] use crate::util::time::tests::SinceEpoch; use crate::util::ser::ReadableArgs; use core::fmt::{self, Display, Formatter}; use core::ops::Deref; +use core::time::Duration; use crate::prelude::*; use crate::sync::Mutex; @@ -39,12 +40,6 @@ use crate::sync::Mutex; /// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred pub(crate) const IDEMPOTENCY_TIMEOUT_TICKS: u8 = 7; -/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until an invoice request without -/// a response is timed out. -/// -/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred -const INVOICE_REQUEST_TIMEOUT_TICKS: u8 = 3; - /// 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 { @@ -52,7 +47,7 @@ pub(crate) enum PendingOutboundPayment { session_privs: HashSet<[u8; 32]>, }, AwaitingInvoice { - timer_ticks_without_response: u8, + expiration: StaleExpiration, retry_strategy: Retry, max_total_routing_fee_msat: Option, }, @@ -134,6 +129,11 @@ impl PendingOutboundPayment { params.previously_failed_channels.push(scid); } } + pub fn insert_previously_failed_blinded_path(&mut self, blinded_tail: &BlindedTail) { + if let PendingOutboundPayment::Retryable { payment_params: Some(params), .. } = self { + params.insert_previously_failed_blinded_path(blinded_tail); + } + } fn is_awaiting_invoice(&self) -> bool { match self { PendingOutboundPayment::AwaitingInvoice { .. } => true, @@ -171,7 +171,7 @@ impl PendingOutboundPayment { } fn mark_fulfilled(&mut self) { - let mut session_privs = HashSet::new(); + let mut session_privs = new_hash_set(); core::mem::swap(&mut session_privs, match self { PendingOutboundPayment::Legacy { session_privs } | PendingOutboundPayment::Retryable { session_privs, .. } | @@ -186,7 +186,7 @@ impl PendingOutboundPayment { fn mark_abandoned(&mut self, reason: PaymentFailureReason) { if let PendingOutboundPayment::Retryable { session_privs, payment_hash, .. } = self { - let mut our_session_privs = HashSet::new(); + let mut our_session_privs = new_hash_set(); core::mem::swap(&mut our_session_privs, session_privs); *self = PendingOutboundPayment::Abandoned { session_privs: our_session_privs, @@ -195,7 +195,7 @@ impl PendingOutboundPayment { }; } else if let PendingOutboundPayment::InvoiceReceived { payment_hash, .. } = self { *self = PendingOutboundPayment::Abandoned { - session_privs: HashSet::new(), + session_privs: new_hash_set(), payment_hash: *payment_hash, reason: Some(reason) }; @@ -248,7 +248,7 @@ impl PendingOutboundPayment { if insert_res { if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, - ref mut remaining_max_total_routing_fee_msat, .. + ref mut remaining_max_total_routing_fee_msat, .. } = self { *pending_amt_msat += path.final_value_msat(); let path_fee_msat = path.fee_msat(); @@ -287,7 +287,7 @@ pub enum Retry { /// retry, and may retry multiple failed HTLCs at once if they failed around the same time and /// were retried along a route from a single call to [`Router::find_route_with_id`]. Attempts(u32), - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] /// Time elapsed before abandoning retries for a payment. At least one attempt at payment is made; /// see [`PaymentParameters::expiry_time`] to avoid any attempt at payment after a specific time. /// @@ -295,13 +295,13 @@ pub enum Retry { Timeout(core::time::Duration), } -#[cfg(feature = "no-std")] +#[cfg(not(feature = "std"))] impl_writeable_tlv_based_enum!(Retry, ; (0, Attempts) ); -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] impl_writeable_tlv_based_enum!(Retry, ; (0, Attempts), @@ -314,10 +314,10 @@ impl Retry { (Retry::Attempts(max_retry_count), PaymentAttempts { count, .. }) => { max_retry_count > count }, - #[cfg(all(not(feature = "no-std"), not(test)))] + #[cfg(all(feature = "std", not(test)))] (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) => *max_duration >= crate::util::time::MonotonicTime::now().duration_since(*first_attempted_at), - #[cfg(all(not(feature = "no-std"), test))] + #[cfg(all(feature = "std", test))] (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) => *max_duration >= SinceEpoch::now().duration_since(*first_attempted_at), } @@ -343,27 +343,27 @@ pub(crate) struct PaymentAttemptsUsingTime { /// it means the result of the first attempt is not known yet. pub(crate) count: u32, /// This field is only used when retry is `Retry::Timeout` which is only build with feature std - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] first_attempted_at: T, - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] phantom: core::marker::PhantomData, } -#[cfg(not(any(feature = "no-std", test)))] -type ConfiguredTime = crate::util::time::MonotonicTime; -#[cfg(feature = "no-std")] +#[cfg(not(feature = "std"))] type ConfiguredTime = crate::util::time::Eternity; -#[cfg(all(not(feature = "no-std"), test))] +#[cfg(all(feature = "std", not(test)))] +type ConfiguredTime = crate::util::time::MonotonicTime; +#[cfg(all(feature = "std", test))] type ConfiguredTime = SinceEpoch; impl PaymentAttemptsUsingTime { pub(crate) fn new() -> Self { PaymentAttemptsUsingTime { count: 0, - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] first_attempted_at: T::now(), - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] phantom: core::marker::PhantomData, } } @@ -371,9 +371,9 @@ impl PaymentAttemptsUsingTime { impl Display for PaymentAttemptsUsingTime { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - #[cfg(feature = "no-std")] + #[cfg(not(feature = "std"))] return write!(f, "attempts: {}", self.count); - #[cfg(not(feature = "no-std"))] + #[cfg(feature = "std")] return write!( f, "attempts: {}, duration: {}s", @@ -383,6 +383,22 @@ impl Display for PaymentAttemptsUsingTime { } } +/// How long before a [`PendingOutboundPayment::AwaitingInvoice`] should be considered stale and +/// candidate for removal in [`OutboundPayments::remove_stale_payments`]. +#[derive(Clone, Copy)] +pub(crate) enum StaleExpiration { + /// Number of times [`OutboundPayments::remove_stale_payments`] is called. + TimerTicks(u64), + /// Duration since the Unix epoch. + AbsoluteTimeout(core::time::Duration), +} + +impl_writeable_tlv_based_enum!(StaleExpiration, + ; + (0, TimerTicks), + (2, AbsoluteTimeout) +); + /// Indicates an immediate error on [`ChannelManager::send_payment`]. Further errors may be /// surfaced later via [`Event::PaymentPathFailed`] and [`Event::PaymentFailed`]. /// @@ -594,10 +610,26 @@ impl RecipientOnionFields { /// Note that if this field is non-empty, it will contain strictly increasing TLVs, each /// represented by a `(u64, Vec)` for its type number and serialized value respectively. /// This is validated when setting this field using [`Self::with_custom_tlvs`]. + #[cfg(not(c_bindings))] pub fn custom_tlvs(&self) -> &Vec<(u64, Vec)> { &self.custom_tlvs } + /// Gets the custom TLVs that will be sent or have been received. + /// + /// Custom TLVs allow sending extra application-specific data with a payment. They provide + /// additional flexibility on top of payment metadata, as while other implementations may + /// require `payment_metadata` to reflect metadata provided in an invoice, custom TLVs + /// do not have this restriction. + /// + /// Note that if this field is non-empty, it will contain strictly increasing TLVs, each + /// represented by a `(u64, Vec)` for its type number and serialized value respectively. + /// This is validated when setting this field using [`Self::with_custom_tlvs`]. + #[cfg(c_bindings)] + pub fn custom_tlvs(&self) -> Vec<(u64, Vec)> { + self.custom_tlvs.clone() + } + /// When we have received some HTLC(s) towards an MPP payment, as we receive further HTLC(s) we /// have to make sure that some fields match exactly across the parts. For those that aren't /// required to match, if they don't match we should remove them so as to not expose data @@ -643,7 +675,7 @@ pub(super) struct OutboundPayments { impl OutboundPayments { pub(super) fn new() -> Self { Self { - pending_outbound_payments: Mutex::new(HashMap::new()), + pending_outbound_payments: Mutex::new(new_hash_map()), retry_lock: Mutex::new(()), } } @@ -701,7 +733,7 @@ impl OutboundPayments { { let preimage = payment_preimage .unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes())); - let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner()); + let payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array()); self.send_payment_internal(payment_id, payment_hash, recipient_onion, Some(preimage), retry_strategy, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, pending_events, send_payment_along_path) @@ -720,7 +752,7 @@ impl OutboundPayments { { let preimage = payment_preimage .unwrap_or_else(|| PaymentPreimage(entropy_source.get_secure_random_bytes())); - let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner()); + let payment_hash = PaymentHash(Sha256::hash(&preimage.0).to_byte_array()); let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, Some(preimage), &route, None, None, entropy_source, best_block_height)?; @@ -735,7 +767,6 @@ impl OutboundPayments { } } - #[allow(unused)] pub(super) fn send_payment_for_bolt12_invoice( &self, invoice: &Bolt12Invoice, payment_id: PaymentId, router: &R, first_hops: Vec, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, @@ -752,7 +783,7 @@ impl OutboundPayments { SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { let payment_hash = invoice.payment_hash(); - let mut max_total_routing_fee_msat = None; + let max_total_routing_fee_msat; match self.pending_outbound_payments.lock().unwrap().entry(payment_id) { hash_map::Entry::Occupied(entry) => match entry.get() { PendingOutboundPayment::AwaitingInvoice { retry_strategy, max_total_routing_fee_msat: max_total_fee, .. } => { @@ -768,11 +799,12 @@ impl OutboundPayments { hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice), }; - let route_params = RouteParameters { - payment_params: PaymentParameters::from_bolt12_invoice(&invoice), - final_value_msat: invoice.amount_msats(), - max_total_routing_fee_msat, - }; + let pay_params = PaymentParameters::from_bolt12_invoice(&invoice); + let amount_msat = invoice.amount_msats(); + let mut route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat); + if let Some(max_fee_msat) = max_total_routing_fee_msat { + route_params.max_total_routing_fee_msat = Some(max_fee_msat); + } self.find_route_and_send_payment( payment_hash, payment_id, route_params, router, first_hops, &inflight_htlcs, @@ -875,7 +907,7 @@ impl OutboundPayments { } } - let route = router.find_route_with_id( + let mut route = router.find_route_with_id( &node_signer.get_node_id(Recipient::Node).unwrap(), &route_params, Some(&first_hops.iter().collect::>()), inflight_htlcs(), payment_hash, payment_id, @@ -885,6 +917,12 @@ impl OutboundPayments { RetryableSendFailure::RouteNotFound })?; + if route.route_params.as_ref() != Some(&route_params) { + debug_assert!(false, + "Routers are expected to return a Route which includes the requested RouteParameters"); + route.route_params = Some(route_params.clone()); + } + let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, keysend_preimage, &route, Some(retry_strategy), Some(route_params.payment_params.clone()), entropy_source, best_block_height) @@ -926,7 +964,7 @@ impl OutboundPayments { } } - let route = match router.find_route_with_id( + let mut route = match router.find_route_with_id( &node_signer.get_node_id(Recipient::Node).unwrap(), &route_params, Some(&first_hops.iter().collect::>()), inflight_htlcs(), payment_hash, payment_id, @@ -938,6 +976,13 @@ impl OutboundPayments { return } }; + + if route.route_params.as_ref() != Some(&route_params) { + debug_assert!(false, + "Routers are expected to return a Route which includes the requested RouteParameters"); + route.route_params = Some(route_params.clone()); + } + for path in route.paths.iter() { if path.hops.len() == 0 { log_error!(logger, "Unusable path in route (path.hops.len() must be at least 1"); @@ -1223,7 +1268,7 @@ impl OutboundPayments { retry_strategy, attempts: PaymentAttempts::new(), payment_params, - session_privs: HashSet::new(), + session_privs: new_hash_set(), pending_amt_msat: 0, pending_fee_msat: Some(0), payment_hash, @@ -1244,16 +1289,16 @@ impl OutboundPayments { (payment, onion_session_privs) } - #[allow(unused)] pub(super) fn add_new_awaiting_invoice( - &self, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option + &self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry, + max_total_routing_fee_msat: Option ) -> Result<(), ()> { let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap(); match pending_outbounds.entry(payment_id) { hash_map::Entry::Occupied(_) => Err(()), hash_map::Entry::Vacant(entry) => { entry.insert(PendingOutboundPayment::AwaitingInvoice { - timer_ticks_without_response: 0, + expiration, retry_strategy, max_total_routing_fee_msat, }); @@ -1297,6 +1342,13 @@ impl OutboundPayments { continue 'path_check; } } + for (i, hop) in path.hops.iter().enumerate() { + // Check for duplicate channel_id in the remaining hops of the path + if path.hops.iter().skip(i + 1).any(|other_hop| other_hop.short_channel_id == hop.short_channel_id) { + path_errs.push(Err(APIError::InvalidRoute{err: "Path went through the same channel twice".to_owned()})); + continue 'path_check; + } + } total_value += path.final_value_msat(); path_errs.push(Ok(())); } @@ -1337,12 +1389,14 @@ impl OutboundPayments { } let mut has_ok = false; let mut has_err = false; - let mut pending_amt_unsent = 0; + let mut has_unsent = false; let mut total_ok_fees_msat = 0; + let mut total_ok_amt_sent_msat = 0; for (res, path) in results.iter().zip(route.paths.iter()) { if res.is_ok() { has_ok = true; total_ok_fees_msat += path.fee_msat(); + total_ok_amt_sent_msat += path.final_value_msat(); } if res.is_err() { has_err = true; } if let &Err(APIError::MonitorUpdateInProgress) = res { @@ -1351,23 +1405,27 @@ impl OutboundPayments { has_err = true; has_ok = true; total_ok_fees_msat += path.fee_msat(); + total_ok_amt_sent_msat += path.final_value_msat(); } else if res.is_err() { - pending_amt_unsent += path.final_value_msat(); + has_unsent = true; } } if has_err && has_ok { Err(PaymentSendFailure::PartialFailure { results, payment_id, - failed_paths_retry: if pending_amt_unsent != 0 { + failed_paths_retry: if has_unsent { if let Some(route_params) = &route.route_params { let mut route_params = route_params.clone(); // We calculate the leftover fee budget we're allowed to spend by // subtracting the used fee from the total fee budget. route_params.max_total_routing_fee_msat = route_params .max_total_routing_fee_msat.map(|m| m.saturating_sub(total_ok_fees_msat)); - route_params.final_value_msat = pending_amt_unsent; + // We calculate the remaining target amount by subtracting the succeded + // path values. + route_params.final_value_msat = route_params.final_value_msat + .saturating_sub(total_ok_amt_sent_msat); Some(route_params) } else { None } } else { None }, @@ -1417,7 +1475,7 @@ impl OutboundPayments { let mut pending_events = pending_events.lock().unwrap(); if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) { if !payment.get().is_fulfilled() { - let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()); + let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array()); log_info!(logger, "Payment with id {} and hash {} sent!", payment_id, payment_hash); let fee_paid_msat = payment.get().get_pending_fee_msat(); pending_events.push_back((events::Event::PaymentSent { @@ -1437,7 +1495,7 @@ impl OutboundPayments { // TODO: We should have a second monitor event that informs us of payments // irrevocably fulfilled. if payment.get_mut().remove(&session_priv_bytes, Some(&path)) { - let payment_hash = Some(PaymentHash(Sha256::hash(&payment_preimage.0).into_inner())); + let payment_hash = Some(PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array())); pending_events.push_back((events::Event::PaymentPathSuccessful { payment_id, payment_hash, @@ -1476,11 +1534,12 @@ impl OutboundPayments { } pub(super) fn remove_stale_payments( - &self, pending_events: &Mutex)>>) + &self, duration_since_epoch: Duration, + pending_events: &Mutex)>>) { let mut pending_outbound_payments = self.pending_outbound_payments.lock().unwrap(); let mut pending_events = pending_events.lock().unwrap(); - pending_outbound_payments.retain(|payment_id, payment| { + pending_outbound_payments.retain(|payment_id, payment| match payment { // If an outbound payment was completed, and no pending HTLCs remain, we should remove it // from the map. However, if we did that immediately when the last payment HTLC is claimed, // this could race the user making a duplicate send_payment call and our idempotency @@ -1488,7 +1547,7 @@ impl OutboundPayments { // removal. This should be more than sufficient to ensure the idempotency of any // `send_payment` calls that were made at the same time the `PaymentSent` event was being // processed. - if let PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } = payment { + PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } => { let mut no_remaining_entries = session_privs.is_empty(); if no_remaining_entries { for (ev, _) in pending_events.iter() { @@ -1512,17 +1571,31 @@ impl OutboundPayments { *timer_ticks_without_htlcs = 0; true } - } else if let PendingOutboundPayment::AwaitingInvoice { timer_ticks_without_response, .. } = payment { - *timer_ticks_without_response += 1; - if *timer_ticks_without_response <= INVOICE_REQUEST_TIMEOUT_TICKS { - true - } else { + }, + PendingOutboundPayment::AwaitingInvoice { expiration, .. } => { + let is_stale = match expiration { + StaleExpiration::AbsoluteTimeout(absolute_expiry) => { + *absolute_expiry <= duration_since_epoch + }, + StaleExpiration::TimerTicks(timer_ticks_remaining) => { + if *timer_ticks_remaining > 0 { + *timer_ticks_remaining -= 1; + false + } else { + true + } + }, + }; + if is_stale { pending_events.push_back( (events::Event::InvoiceRequestFailed { payment_id: *payment_id }, None) ); false + } else { + true } - } else { true } + }, + _ => true, }); } @@ -1536,11 +1609,12 @@ impl OutboundPayments { #[cfg(test)] let DecodedOnionFailure { network_update, short_channel_id, payment_failed_permanently, onion_error_code, - onion_error_data + onion_error_data, failed_within_blinded_path } = onion_error.decode_onion_failure(secp_ctx, logger, &source); #[cfg(not(test))] - let DecodedOnionFailure { network_update, short_channel_id, payment_failed_permanently } = - onion_error.decode_onion_failure(secp_ctx, logger, &source); + let DecodedOnionFailure { + network_update, short_channel_id, payment_failed_permanently, failed_within_blinded_path + } = onion_error.decode_onion_failure(secp_ctx, logger, &source); let payment_is_probe = payment_is_probe(payment_hash, &payment_id, probing_cookie_secret); let mut session_priv_bytes = [0; 32]; @@ -1579,6 +1653,12 @@ impl OutboundPayments { // next-hop is needlessly blaming us! payment.get_mut().insert_previously_failed_scid(scid); } + if failed_within_blinded_path { + debug_assert!(short_channel_id.is_none()); + if let Some(bt) = &path.blinded_tail { + payment.get_mut().insert_previously_failed_blinded_path(&bt); + } else { debug_assert!(false); } + } if payment_is_probe || !is_retryable_now || payment_failed_permanently { let reason = if payment_failed_permanently { @@ -1702,7 +1782,7 @@ fn probing_cookie_from_id(payment_id: &PaymentId, probing_cookie_secret: [u8; 32 let mut preimage = [0u8; 64]; preimage[..32].copy_from_slice(&probing_cookie_secret); preimage[32..].copy_from_slice(&payment_id.0); - PaymentHash(Sha256::hash(&preimage).into_inner()) + PaymentHash(Sha256::hash(&preimage).to_byte_array()) } impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, @@ -1738,7 +1818,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (2, payment_hash, required), }, (5, AwaitingInvoice) => { - (0, timer_ticks_without_response, required), + (0, expiration, required), (2, retry_strategy, required), (4, max_total_routing_fee_msat, option), }, @@ -1754,12 +1834,15 @@ mod tests { use bitcoin::network::constants::Network; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use core::time::Duration; + use crate::events::{Event, PathFailure, PaymentFailureReason}; use crate::ln::PaymentHash; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::features::{ChannelFeatures, NodeFeatures}; use crate::ln::msgs::{ErrorAction, LightningError}; - use crate::ln::outbound_payment::{Bolt12PaymentError, INVOICE_REQUEST_TIMEOUT_TICKS, OutboundPayments, Retry, RetryableSendFailure}; + use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure, StaleExpiration}; + #[cfg(feature = "std")] use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY; use crate::offers::offer::OfferBuilder; use crate::offers::test_utils::*; @@ -1805,7 +1888,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); @@ -1849,7 +1932,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); @@ -1888,7 +1971,7 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let secp_ctx = Secp256k1::new(); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); @@ -1912,7 +1995,9 @@ mod tests { router.expect_find_route(route_params.clone(), Ok(route.clone())); let mut route_params_w_failed_scid = route_params.clone(); route_params_w_failed_scid.payment_params.previously_failed_channels.push(failed_scid); - router.expect_find_route(route_params_w_failed_scid, Ok(route.clone())); + let mut route_w_failed_scid = route.clone(); + route_w_failed_scid.route_params = Some(route_params_w_failed_scid.clone()); + router.expect_find_route(route_params_w_failed_scid, Ok(route_w_failed_scid)); router.expect_find_route(route_params.clone(), Ok(route.clone())); router.expect_find_route(route_params.clone(), Ok(route.clone())); @@ -1962,24 +2047,82 @@ mod tests { } #[test] - fn removes_stale_awaiting_invoice() { + fn removes_stale_awaiting_invoice_using_absolute_timeout() { + let pending_events = Mutex::new(VecDeque::new()); + let outbound_payments = OutboundPayments::new(); + let payment_id = PaymentId([0; 32]); + let absolute_expiry = 100; + let tick_interval = 10; + let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(absolute_expiry)); + + assert!(!outbound_payments.has_pending_payments()); + assert!( + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), None + ).is_ok() + ); + assert!(outbound_payments.has_pending_payments()); + + for seconds_since_epoch in (0..absolute_expiry).step_by(tick_interval) { + let duration_since_epoch = Duration::from_secs(seconds_since_epoch); + outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events); + + assert!(outbound_payments.has_pending_payments()); + assert!(pending_events.lock().unwrap().is_empty()); + } + + let duration_since_epoch = Duration::from_secs(absolute_expiry); + outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events); + + assert!(!outbound_payments.has_pending_payments()); + assert!(!pending_events.lock().unwrap().is_empty()); + assert_eq!( + pending_events.lock().unwrap().pop_front(), + Some((Event::InvoiceRequestFailed { payment_id }, None)), + ); + assert!(pending_events.lock().unwrap().is_empty()); + + assert!( + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), None + ).is_ok() + ); + assert!(outbound_payments.has_pending_payments()); + + assert!( + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), None + ).is_err() + ); + } + + #[test] + fn removes_stale_awaiting_invoice_using_timer_ticks() { let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(); let payment_id = PaymentId([0; 32]); + let timer_ticks = 3; + let expiration = StaleExpiration::TimerTicks(timer_ticks); assert!(!outbound_payments.has_pending_payments()); assert!( - outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok() + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), None + ).is_ok() ); assert!(outbound_payments.has_pending_payments()); - for _ in 0..INVOICE_REQUEST_TIMEOUT_TICKS { - outbound_payments.remove_stale_payments(&pending_events); + for i in 0..timer_ticks { + let duration_since_epoch = Duration::from_secs(i * 60); + outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events); + assert!(outbound_payments.has_pending_payments()); assert!(pending_events.lock().unwrap().is_empty()); } - outbound_payments.remove_stale_payments(&pending_events); + let duration_since_epoch = Duration::from_secs(timer_ticks * 60); + outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events); + assert!(!outbound_payments.has_pending_payments()); assert!(!pending_events.lock().unwrap().is_empty()); assert_eq!( @@ -1989,13 +2132,16 @@ mod tests { assert!(pending_events.lock().unwrap().is_empty()); assert!( - outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok() + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), None + ).is_ok() ); assert!(outbound_payments.has_pending_payments()); assert!( - outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None) - .is_err() + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), None + ).is_err() ); } @@ -2004,10 +2150,13 @@ mod tests { let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(); let payment_id = PaymentId([0; 32]); + let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); assert!(!outbound_payments.has_pending_payments()); assert!( - outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok() + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), None + ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -2029,20 +2178,23 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(); let payment_id = PaymentId([0; 32]); + let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); assert!( - outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok() + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), None + ).is_ok() ); assert!(outbound_payments.has_pending_payments()); let created_at = now() - DEFAULT_RELATIVE_EXPIRY; - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2077,14 +2229,15 @@ mod tests { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(); let payment_id = PaymentId([0; 32]); + let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2094,9 +2247,11 @@ mod tests { .build().unwrap() .sign(recipient_sign).unwrap(); - assert!(outbound_payments.add_new_awaiting_invoice( - payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000)) - .is_ok() + assert!( + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), + Some(invoice.amount_msats() / 100 + 50_000) + ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -2128,75 +2283,20 @@ mod tests { assert!(pending_events.lock().unwrap().is_empty()); } - #[test] - fn fails_paying_for_bolt12_invoice() { - let logger = test_utils::TestLogger::new(); - let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); - let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); - let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); - - let pending_events = Mutex::new(VecDeque::new()); - let outbound_payments = OutboundPayments::new(); - let payment_id = PaymentId([0; 32]); - - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) - .amount_msats(1000) - .build().unwrap() - .request_invoice(vec![1; 32], payer_pubkey()).unwrap() - .build().unwrap() - .sign(payer_sign).unwrap() - .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() - .build().unwrap() - .sign(recipient_sign).unwrap(); - - assert!(outbound_payments.add_new_awaiting_invoice( - payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000)) - .is_ok() - ); - assert!(outbound_payments.has_pending_payments()); - - let route_params = RouteParameters::from_payment_params_and_value( - PaymentParameters::from_bolt12_invoice(&invoice), - invoice.amount_msats(), - ); - router.expect_find_route( - route_params.clone(), Ok(Route { paths: vec![], route_params: Some(route_params) }) - ); - - assert_eq!( - outbound_payments.send_payment_for_bolt12_invoice( - &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager, - &&keys_manager, 0, &&logger, &pending_events, |_| panic!() - ), - Ok(()), - ); - assert!(!outbound_payments.has_pending_payments()); - - let payment_hash = invoice.payment_hash(); - let reason = Some(PaymentFailureReason::UnexpectedError); - - assert!(!pending_events.lock().unwrap().is_empty()); - assert_eq!( - pending_events.lock().unwrap().pop_front(), - Some((Event::PaymentFailed { payment_id, payment_hash, reason }, None)), - ); - assert!(pending_events.lock().unwrap().is_empty()); - } - #[test] fn sends_payment_for_bolt12_invoice() { let logger = test_utils::TestLogger::new(); let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); let scorer = RwLock::new(test_utils::TestScorer::new()); - let router = test_utils::TestRouter::new(network_graph, &scorer); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); let pending_events = Mutex::new(VecDeque::new()); let outbound_payments = OutboundPayments::new(); let payment_id = PaymentId([0; 32]); + let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100)); - let invoice = OfferBuilder::new("foo".into(), recipient_pubkey()) + let invoice = OfferBuilder::new(recipient_pubkey()) .amount_msats(1000) .build().unwrap() .request_invoice(vec![1; 32], payer_pubkey()).unwrap() @@ -2246,7 +2346,9 @@ mod tests { assert!(pending_events.lock().unwrap().is_empty()); assert!( - outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), Some(1234)).is_ok() + outbound_payments.add_new_awaiting_invoice( + payment_id, expiration, Retry::Attempts(0), Some(1234) + ).is_ok() ); assert!(outbound_payments.has_pending_payments());