Retry HTLCs in process_pending_htlc_forwards
authorValentine Wallace <vwallace@protonmail.com>
Sat, 7 Jan 2023 00:39:40 +0000 (19:39 -0500)
committerValentine Wallace <vwallace@protonmail.com>
Wed, 25 Jan 2023 19:44:07 +0000 (14:44 -0500)
lightning/src/ln/channelmanager.rs
lightning/src/ln/outbound_payment.rs

index 9cf58c918256cf02b707ac83660d1a510c86979f..8dc3059d4859abd28f321b72cb6d5f544b7516c8 100644 (file)
@@ -3250,6 +3250,12 @@ where
                        }
                }
 
+               let best_block_height = self.best_block.read().unwrap().height();
+               self.pending_outbound_payments.check_retry_payments(&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));
+
                for (htlc_source, payment_hash, failure_reason, destination) in failed_forwards.drain(..) {
                        self.fail_htlc_backwards_internal(&htlc_source, &payment_hash, &failure_reason, destination);
                }
index c2d0a6b62c9d11285439645a7a168d410ad9d76f..4b6278d194904419dcb189ffdb1c125215f3e7c4 100644 (file)
@@ -15,10 +15,10 @@ use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
 
 use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
 use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
-use crate::ln::channelmanager::{HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, MIN_HTLC_RELAY_HOLDING_CELL_MILLIS, PaymentId};
+use crate::ln::channelmanager::{ChannelDetails, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, MIN_HTLC_RELAY_HOLDING_CELL_MILLIS, PaymentId};
 use crate::ln::msgs::DecodeError;
 use crate::ln::onion_utils::HTLCFailReason;
-use crate::routing::router::{PaymentParameters, Route, RouteHop, RouteParameters, RoutePath};
+use crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteHop, RouteParameters, RoutePath, Router};
 use crate::util::errors::APIError;
 use crate::util::events;
 use crate::util::logger::Logger;
@@ -237,6 +237,16 @@ impl Retry {
        }
 }
 
+#[cfg(feature = "std")]
+pub(super) fn has_expired(route_params: &RouteParameters) -> bool {
+       if let Some(expiry_time) = route_params.payment_params.expiry_time {
+               if let Ok(elapsed) = std::time::SystemTime::UNIX_EPOCH.elapsed() {
+                       return elapsed > core::time::Duration::from_secs(expiry_time)
+               }
+       }
+       false
+}
+
 pub(crate) type PaymentAttempts = PaymentAttemptsUsingTime<ConfiguredTime>;
 
 /// Storing minimal payment attempts information required for determining if a outbound payment can
@@ -411,6 +421,109 @@ impl OutboundPayments {
                }
        }
 
+       pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
+               &self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
+               best_block_height: u32, logger: &L, send_payment_along_path: SP,
+       )
+       where
+               R::Target: Router,
+               ES::Target: EntropySource,
+               NS::Target: NodeSigner,
+               SP: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+                  u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
+               IH: Fn() -> InFlightHtlcs,
+               FH: Fn() -> Vec<ChannelDetails>,
+               L::Target: Logger,
+       {
+               loop {
+                       let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+                       let mut retry_id_route_params = None;
+                       for (pmt_id, pmt) in outbounds.iter_mut() {
+                               if pmt.is_retryable_now() {
+                                       if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, route_params: Some(params), .. } = pmt {
+                                               if pending_amt_msat < total_msat {
+                                                       retry_id_route_params = Some((*pmt_id, params.clone()));
+                                                       pmt.increment_attempts();
+                                                       break
+                                               }
+                                       }
+                               }
+                       }
+                       if let Some((payment_id, route_params)) = retry_id_route_params {
+                               core::mem::drop(outbounds);
+                               if let Err(e) = self.pay_internal(payment_id, route_params, router, first_hops(), inflight_htlcs(), entropy_source, node_signer, best_block_height, &send_payment_along_path) {
+                                       log_trace!(logger, "Errored retrying payment: {:?}", e);
+                               }
+                       } else { break }
+               }
+       }
+
+       fn pay_internal<R: Deref, NS: Deref, ES: Deref, F>(
+               &self, payment_id: PaymentId, route_params: RouteParameters, router: &R,
+               first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES,
+               node_signer: &NS, best_block_height: u32, send_payment_along_path: &F
+       ) -> Result<(), PaymentSendFailure>
+       where
+               R::Target: Router,
+               ES::Target: EntropySource,
+               NS::Target: NodeSigner,
+               F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
+                  u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
+       {
+               #[cfg(feature = "std")] {
+                       if has_expired(&route_params) {
+                               return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
+                                       err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
+                               }))
+                       }
+               }
+
+               let route = router.find_route(
+                       &node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
+                       Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs
+               ).map_err(|e| PaymentSendFailure::ParameterError(APIError::APIMisuseError {
+                       err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound
+               }))?;
+
+               let res = self.retry_payment_with_route(&route, payment_id, entropy_source, node_signer, best_block_height, send_payment_along_path);
+               match res {
+                       Err(PaymentSendFailure::AllFailedResendSafe(_)) => {
+                               let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+                               if let Some(payment) = outbounds.get_mut(&payment_id) {
+                                       let retryable = payment.is_retryable_now();
+                                       if retryable {
+                                               payment.increment_attempts();
+                                       } else { return res }
+                               } else { return res }
+                               core::mem::drop(outbounds);
+                               self.pay_internal(payment_id, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path)
+                       },
+                       Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. }) => {
+                               let mut outbounds = self.pending_outbound_payments.lock().unwrap();
+                               if let Some(payment) = outbounds.get_mut(&payment_id) {
+                                       let retryable = payment.is_retryable_now();
+                                       if retryable {
+                                               payment.increment_attempts();
+                                       } else { return Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, payment_id }) }
+                               } else { return Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, payment_id }) }
+                               core::mem::drop(outbounds);
+
+                               // Some paths were sent, even if we failed to send the full MPP value our recipient may
+                               // misbehave and claim the funds, at which point we have to consider the payment sent, so
+                               // return `Ok()` here, ignoring any retry errors.
+                               let _ = self.pay_internal(payment_id, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path);
+                               Ok(())
+                       },
+                       Err(PaymentSendFailure::PartialFailure { failed_paths_retry: None, .. }) => {
+                               // This may happen if we send a payment and some paths fail, but only due to a temporary
+                               // monitor failure or the like, implying they're really in-flight, but we haven't sent the
+                               // initial HTLC-Add messages yet.
+                               Ok(())
+                       },
+                       res => res,
+               }
+       }
+
        pub(super) fn retry_payment_with_route<ES: Deref, NS: Deref, F>(
                &self, route: &Route, payment_id: PaymentId, entropy_source: &ES, node_signer: &NS, best_block_height: u32,
                send_payment_along_path: F