From: Valentine Wallace Date: Sat, 7 Jan 2023 00:39:40 +0000 (-0500) Subject: Retry HTLCs in process_pending_htlc_forwards X-Git-Tag: v0.0.114-beta~43^2~4 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=d776dee3a553ee9ba3d2ce217207f42f10ed091f;p=rust-lightning Retry HTLCs in process_pending_htlc_forwards --- diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9cf58c918..8dc3059d4 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -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); } diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index c2d0a6b62..4b6278d19 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -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; /// 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( + &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, &Option, &PaymentHash, &Option, u64, + u32, PaymentId, &Option, [u8; 32]) -> Result<(), APIError>, + IH: Fn() -> InFlightHtlcs, + FH: Fn() -> Vec, + 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( + &self, payment_id: PaymentId, route_params: RouteParameters, router: &R, + first_hops: Vec, 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, &Option, &PaymentHash, &Option, u64, + u32, PaymentId, &Option, [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::>()), &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( &self, route: &Route, payment_id: PaymentId, entropy_source: &ES, node_signer: &NS, best_block_height: u32, send_payment_along_path: F