Support spontaneous payment retries in ChannelManager
authorValentine Wallace <vwallace@protonmail.com>
Tue, 24 Jan 2023 17:15:40 +0000 (12:15 -0500)
committerValentine Wallace <vwallace@protonmail.com>
Fri, 3 Feb 2023 00:30:25 +0000 (19:30 -0500)
lightning/src/ln/channelmanager.rs
lightning/src/ln/outbound_payment.rs
lightning/src/ln/payment_tests.rs

index ea17d2a614e5f36e56e789f75caf558ae72eb183..1b8d03a87a023dcbcaef334a76e9310c8d519cbd 100644 (file)
@@ -2558,7 +2558,21 @@ where
        /// [`send_payment`]: Self::send_payment
        pub fn send_spontaneous_payment(&self, route: &Route, payment_preimage: Option<PaymentPreimage>, payment_id: PaymentId) -> Result<PaymentHash, PaymentSendFailure> {
                let best_block_height = self.best_block.read().unwrap().height();
-               self.pending_outbound_payments.send_spontaneous_payment(route, payment_preimage, payment_id, &self.entropy_source, &self.node_signer, best_block_height,
+               self.pending_outbound_payments.send_spontaneous_payment_with_route(
+                       route, payment_preimage, payment_id, &self.entropy_source, &self.node_signer,
+                       best_block_height,
+                       |path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv|
+                       self.send_payment_along_path(path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv))
+       }
+
+       /// Similar to [`ChannelManager::send_spontaneous_payment`], but will automatically find a route
+       /// based on `route_params` and retry failed payment paths based on `retry_strategy`.
+       pub fn send_spontaneous_payment_with_retry(&self, payment_preimage: Option<PaymentPreimage>, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<PaymentHash, PaymentSendFailure> {
+               let best_block_height = self.best_block.read().unwrap().height();
+               self.pending_outbound_payments.send_spontaneous_payment(payment_preimage, payment_id,
+                       retry_strategy, route_params, &self.router, self.list_usable_channels(),
+                       self.compute_inflight_htlcs(),  &self.entropy_source, &self.node_signer, best_block_height,
+                       &self.logger,
                        |path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv|
                        self.send_payment_along_path(path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv))
        }
index a73bc9162a2ffbaddc02beceae6e7046b99d3842..3a49d7c2be6403da603369a63aa2876e4b5163f1 100644 (file)
@@ -416,7 +416,7 @@ impl OutboundPayments {
                F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
                         u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
        {
-               self.pay_internal(payment_id, Some((payment_hash, payment_secret, retry_strategy)),
+               self.pay_internal(payment_id, Some((payment_hash, payment_secret, None, retry_strategy)),
                        route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer,
                        best_block_height, logger, &send_payment_along_path)
                        .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
@@ -439,7 +439,33 @@ impl OutboundPayments {
                        .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
        }
 
-       pub(super) fn send_spontaneous_payment<ES: Deref, NS: Deref, F>(
+       pub(super) fn send_spontaneous_payment<R: Deref, ES: Deref, NS: Deref, F, L: Deref>(
+               &self, payment_preimage: Option<PaymentPreimage>, payment_id: PaymentId,
+               retry_strategy: Retry, route_params: RouteParameters, router: &R,
+               first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES,
+               node_signer: &NS, best_block_height: u32, logger: &L, send_payment_along_path: F
+       ) -> Result<PaymentHash, PaymentSendFailure>
+       where
+               R::Target: Router,
+               ES::Target: EntropySource,
+               NS::Target: NodeSigner,
+               L::Target: Logger,
+               F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+                        u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
+       {
+               let preimage = match payment_preimage {
+                       Some(p) => p,
+                       None => PaymentPreimage(entropy_source.get_secure_random_bytes()),
+               };
+               let payment_hash = PaymentHash(Sha256::hash(&preimage.0).into_inner());
+               self.pay_internal(payment_id, Some((payment_hash, &None, Some(preimage), retry_strategy)),
+                       route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer,
+                       best_block_height, logger, &send_payment_along_path)
+                       .map(|()| payment_hash)
+                       .map_err(|e| { self.remove_outbound_if_all_failed(payment_id, &e); e })
+       }
+
+       pub(super) fn send_spontaneous_payment_with_route<ES: Deref, NS: Deref, F>(
                &self, route: &Route, payment_preimage: Option<PaymentPreimage>, payment_id: PaymentId,
                entropy_source: &ES, node_signer: &NS, best_block_height: u32, send_payment_along_path: F
        ) -> Result<PaymentHash, PaymentSendFailure>
@@ -512,7 +538,7 @@ impl OutboundPayments {
 
        fn pay_internal<R: Deref, NS: Deref, ES: Deref, F, L: Deref>(
                &self, payment_id: PaymentId,
-               initial_send_info: Option<(PaymentHash, &Option<PaymentSecret>, Retry)>,
+               initial_send_info: Option<(PaymentHash, &Option<PaymentSecret>, Option<PaymentPreimage>, Retry)>,
                route_params: RouteParameters, router: &R, first_hops: Vec<ChannelDetails>,
                inflight_htlcs: InFlightHtlcs, entropy_source: &ES, node_signer: &NS, best_block_height: u32,
                logger: &L, send_payment_along_path: &F,
@@ -540,8 +566,8 @@ impl OutboundPayments {
                        err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound
                }))?;
 
-               let res = if let Some((payment_hash, payment_secret, retry_strategy)) = initial_send_info {
-                       let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, None, &route, Some(retry_strategy), Some(route_params.payment_params.clone()), entropy_source, best_block_height)?;
+               let res = if let Some((payment_hash, payment_secret, keysend_preimage, retry_strategy)) = initial_send_info {
+                       let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, keysend_preimage, &route, Some(retry_strategy), Some(route_params.payment_params.clone()), entropy_source, best_block_height)?;
                        self.pay_route_internal(&route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs, node_signer, best_block_height, send_payment_along_path)
                } else {
                        self.retry_payment_with_route(&route, payment_id, entropy_source, node_signer, best_block_height, send_payment_along_path)
@@ -597,13 +623,13 @@ impl OutboundPayments {
                        onion_session_privs.push(entropy_source.get_secure_random_bytes());
                }
 
-               let (total_msat, payment_hash, payment_secret) = {
+               let (total_msat, payment_hash, payment_secret, keysend_preimage) = {
                        let mut outbounds = self.pending_outbound_payments.lock().unwrap();
                        match outbounds.get_mut(&payment_id) {
                                Some(payment) => {
                                        let res = match payment {
                                                PendingOutboundPayment::Retryable {
-                                                       total_msat, payment_hash, payment_secret, pending_amt_msat, ..
+                                                       total_msat, payment_hash, keysend_preimage, payment_secret, pending_amt_msat, ..
                                                } => {
                                                        let retry_amt_msat: u64 = route.paths.iter().map(|path| path.last().unwrap().fee_msat).sum();
                                                        if retry_amt_msat + *pending_amt_msat > *total_msat * (100 + RETRY_OVERFLOW_PERCENTAGE) / 100 {
@@ -611,7 +637,7 @@ impl OutboundPayments {
                                                                        err: format!("retry_amt_msat of {} will put pending_amt_msat (currently: {}) more than 10% over total_payment_amt_msat of {}", retry_amt_msat, pending_amt_msat, total_msat).to_string()
                                                                }))
                                                        }
-                                                       (*total_msat, *payment_hash, *payment_secret)
+                                                       (*total_msat, *payment_hash, *payment_secret, *keysend_preimage)
                                                },
                                                PendingOutboundPayment::Legacy { .. } => {
                                                        return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
@@ -646,7 +672,7 @@ impl OutboundPayments {
                                        })),
                        }
                };
-               self.pay_route_internal(route, payment_hash, &payment_secret, None, payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height, &send_payment_along_path)
+               self.pay_route_internal(route, payment_hash, &payment_secret, keysend_preimage, payment_id, Some(total_msat), onion_session_privs, node_signer, best_block_height, &send_payment_along_path)
        }
 
        pub(super) fn send_probe<ES: Deref, NS: Deref, F>(
index 68074f1b59b33ecc8d47a88393004ed75b077734..17061caaa648aab7459c45d209194004aca75631 100644 (file)
@@ -1586,6 +1586,7 @@ fn do_test_intercepted_payment(test: InterceptTest) {
 #[derive(PartialEq)]
 enum AutoRetry {
        Success,
+       Spontaneous,
        FailAttempts,
        FailTimeout,
        FailOnRestart,
@@ -1594,6 +1595,7 @@ enum AutoRetry {
 #[test]
 fn automatic_retries() {
        do_automatic_retries(AutoRetry::Success);
+       do_automatic_retries(AutoRetry::Spontaneous);
        do_automatic_retries(AutoRetry::FailAttempts);
        do_automatic_retries(AutoRetry::FailTimeout);
        do_automatic_retries(AutoRetry::FailOnRestart);
@@ -1692,6 +1694,21 @@ fn do_automatic_retries(test: AutoRetry) {
                assert_eq!(msg_events.len(), 1);
                pass_along_path(&nodes[0], &[&nodes[1], &nodes[2]], amt_msat, payment_hash, Some(payment_secret), msg_events.pop().unwrap(), true, None);
                claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], false, payment_preimage);
+       } else if test == AutoRetry::Spontaneous {
+               nodes[0].node.send_spontaneous_payment_with_retry(Some(payment_preimage), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();
+               pass_failed_attempt_with_retry_along_path!(channel_id_2, true);
+
+               // Open a new channel with liquidity on the second hop so we can find a route for the retry
+               // attempt, since the initial second hop channel will be excluded from pathfinding
+               create_announced_chan_between_nodes(&nodes, 1, 2);
+
+               // We retry payments in `process_pending_htlc_forwards`
+               nodes[0].node.process_pending_htlc_forwards();
+               check_added_monitors!(nodes[0], 1);
+               let mut msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+               assert_eq!(msg_events.len(), 1);
+               pass_along_path(&nodes[0], &[&nodes[1], &nodes[2]], amt_msat, payment_hash, None, msg_events.pop().unwrap(), true, Some(payment_preimage));
+               claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], false, payment_preimage);
        } else if test == AutoRetry::FailAttempts {
                // Ensure ChannelManager will not retry a payment if it has run out of payment attempts.
                nodes[0].node.send_payment_with_retry(payment_hash, &Some(payment_secret), PaymentId(payment_hash.0), route_params, Retry::Attempts(1)).unwrap();