X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Foutbound_payment.rs;h=e230002a04ed7c00fea2785cc6564726bb565c8d;hb=ae3453393227f932eef574735f8feee4278eccc8;hp=161f6ecc14bebbe46317db4ad7fe058bd757ff41;hpb=ce6bcf68a15331c082b34d09902241583bc6ff71;p=rust-lightning diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 161f6ecc..e230002a 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -22,9 +22,14 @@ use crate::routing::router::{PaymentParameters, Route, RouteHop, RouteParameters use crate::util::errors::APIError; use crate::util::events; use crate::util::logger::Logger; +use crate::util::time::Time; +#[cfg(all(not(feature = "no-std"), test))] +use crate::util::time::tests::SinceEpoch; use core::cmp; +use core::fmt::{self, Display, Formatter}; use core::ops::Deref; + use crate::prelude::*; use crate::sync::Mutex; @@ -182,6 +187,78 @@ impl PendingOutboundPayment { } } +/// Strategies available to retry payment path failures. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Retry { + /// Max number of attempts to retry payment. + /// + /// Note that this is the number of *path* failures, not full payment retries. For multi-path + /// payments, if this is less than the total number of paths, we will never even retry all of the + /// payment's paths. + Attempts(usize), + #[cfg(not(feature = "no-std"))] + /// Time elapsed before abandoning retries for a payment. + Timeout(core::time::Duration), +} + +impl Retry { + pub(crate) fn is_retryable_now(&self, attempts: &PaymentAttempts) -> bool { + match (self, attempts) { + (Retry::Attempts(max_retry_count), PaymentAttempts { count, .. }) => { + max_retry_count > count + }, + #[cfg(all(not(feature = "no-std"), not(test)))] + (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) => + *max_duration >= std::time::Instant::now().duration_since(*first_attempted_at), + #[cfg(all(not(feature = "no-std"), test))] + (Retry::Timeout(max_duration), PaymentAttempts { first_attempted_at, .. }) => + *max_duration >= SinceEpoch::now().duration_since(*first_attempted_at), + } + } +} + +pub(crate) type PaymentAttempts = PaymentAttemptsUsingTime; + +/// Storing minimal payment attempts information required for determining if a outbound payment can +/// be retried. +pub(crate) struct PaymentAttemptsUsingTime { + /// This count will be incremented only after the result of the attempt is known. When it's 0, + /// it means the result of the first attempt is not known yet. + pub(crate) count: usize, + /// This field is only used when retry is `Retry::Timeout` which is only build with feature std + first_attempted_at: T +} + +#[cfg(not(any(feature = "no-std", test)))] +type ConfiguredTime = std::time::Instant; +#[cfg(feature = "no-std")] +type ConfiguredTime = crate::util::time::Eternity; +#[cfg(all(not(feature = "no-std"), test))] +type ConfiguredTime = SinceEpoch; + +impl PaymentAttemptsUsingTime { + pub(crate) fn new() -> Self { + PaymentAttemptsUsingTime { + count: 0, + first_attempted_at: T::now() + } + } +} + +impl Display for PaymentAttemptsUsingTime { + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + #[cfg(feature = "no-std")] + return write!(f, "attempts: {}", self.count); + #[cfg(not(feature = "no-std"))] + return write!( + f, + "attempts: {}, duration: {}s", + self.count, + T::now().duration_since(self.first_attempted_at).as_secs() + ); + } +} + /// 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. @@ -282,7 +359,7 @@ impl OutboundPayments { F: Fn(&Vec, &Option, &PaymentHash, &Option, u64, u32, PaymentId, &Option, [u8; 32]) -> Result<(), APIError> { - let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, route, entropy_source, best_block_height)?; + let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, route, Retry::Attempts(0), None, entropy_source, best_block_height)?; self.send_payment_internal(route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) } @@ -301,7 +378,7 @@ impl OutboundPayments { None => PaymentPreimage(entropy_source.get_secure_random_bytes()), }; let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner()); - let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, entropy_source, best_block_height)?; + let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, Retry::Attempts(0), None, entropy_source, best_block_height)?; match self.send_payment_internal(route, payment_hash, &None, Some(preimage), payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) { Ok(()) => Ok(payment_hash), @@ -400,7 +477,7 @@ impl OutboundPayments { } let route = Route { paths: vec![hops], payment_params: None }; - let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, entropy_source, best_block_height)?; + let onion_session_privs = self.add_new_pending_payment(payment_hash, None, payment_id, &route, Retry::Attempts(0), None, entropy_source, best_block_height)?; match self.send_payment_internal(&route, payment_hash, &None, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path) { Ok(()) => Ok((payment_hash, payment_id)), @@ -411,14 +488,15 @@ impl OutboundPayments { #[cfg(test)] pub(super) fn test_add_new_pending_payment( &self, payment_hash: PaymentHash, payment_secret: Option, payment_id: PaymentId, - route: &Route, entropy_source: &ES, best_block_height: u32 + route: &Route, retry_strategy: Retry, entropy_source: &ES, best_block_height: u32 ) -> Result, PaymentSendFailure> where ES::Target: EntropySource { - self.add_new_pending_payment(payment_hash, payment_secret, payment_id, route, entropy_source, best_block_height) + self.add_new_pending_payment(payment_hash, payment_secret, payment_id, route, retry_strategy, None, entropy_source, best_block_height) } pub(super) fn add_new_pending_payment( &self, payment_hash: PaymentHash, payment_secret: Option, payment_id: PaymentId, - route: &Route, entropy_source: &ES, best_block_height: u32 + route: &Route, retry_strategy: Retry, route_params: Option, + entropy_source: &ES, best_block_height: u32 ) -> Result, PaymentSendFailure> where ES::Target: EntropySource { let mut onion_session_privs = Vec::with_capacity(route.paths.len()); for _ in 0..route.paths.len() {