Parameterize add_new_pending_payment with retry strategy and route params
[rust-lightning] / lightning / src / ln / outbound_payment.rs
index 77ef8d910f4fa68db19085c95595ce8b811683b5..e230002a04ed7c00fea2785cc6564726bb565c8d 100644 (file)
@@ -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;
 
@@ -60,7 +65,7 @@ pub(crate) enum PendingOutboundPayment {
        /// `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
+       /// (1) <https://github.com/lightningdevkit/rust-lightning/issues/1164>
        Abandoned {
                session_privs: HashSet<[u8; 32]>,
                payment_hash: PaymentHash,
@@ -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<ConfiguredTime>;
+
+/// Storing minimal payment attempts information required for determining if a outbound payment can
+/// be retried.
+pub(crate) struct PaymentAttemptsUsingTime<T: Time> {
+       /// 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<T: Time> PaymentAttemptsUsingTime<T> {
+       pub(crate) fn new() -> Self {
+               PaymentAttemptsUsingTime {
+                       count: 0,
+                       first_attempted_at: T::now()
+               }
+       }
+}
+
+impl<T: Time> Display for PaymentAttemptsUsingTime<T> {
+       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<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
                   u32, PaymentId, &Option<PaymentPreimage>, [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<ES: Deref>(
                &self, payment_hash: PaymentHash, payment_secret: Option<PaymentSecret>, 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<Vec<[u8; 32]>, 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<ES: Deref>(
                &self, payment_hash: PaymentHash, payment_secret: Option<PaymentSecret>, payment_id: PaymentId,
-               route: &Route, entropy_source: &ES, best_block_height: u32
+               route: &Route, retry_strategy: Retry, route_params: Option<RouteParameters>,
+               entropy_source: &ES, best_block_height: u32
        ) -> Result<Vec<[u8; 32]>, PaymentSendFailure> where ES::Target: EntropySource {
                let mut onion_session_privs = Vec::with_capacity(route.paths.len());
                for _ in 0..route.paths.len() {