Skip to content

Commit d776dee

Browse files
Retry HTLCs in process_pending_htlc_forwards
1 parent 72a7da8 commit d776dee

File tree

2 files changed

+121
-2
lines changed

2 files changed

+121
-2
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3250,6 +3250,12 @@ where
32503250
}
32513251
}
32523252

3253+
let best_block_height = self.best_block.read().unwrap().height();
3254+
self.pending_outbound_payments.check_retry_payments(&self.router, || self.list_usable_channels(),
3255+
|| self.compute_inflight_htlcs(), &self.entropy_source, &self.node_signer, best_block_height, &self.logger,
3256+
|path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv|
3257+
self.send_payment_along_path(path, payment_params, payment_hash, payment_secret, total_value, cur_height, payment_id, keysend_preimage, session_priv));
3258+
32533259
for (htlc_source, payment_hash, failure_reason, destination) in failed_forwards.drain(..) {
32543260
self.fail_htlc_backwards_internal(&htlc_source, &payment_hash, &failure_reason, destination);
32553261
}

lightning/src/ln/outbound_payment.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
1515

1616
use crate::chain::keysinterface::{EntropySource, NodeSigner, Recipient};
1717
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
18-
use crate::ln::channelmanager::{HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, MIN_HTLC_RELAY_HOLDING_CELL_MILLIS, PaymentId};
18+
use crate::ln::channelmanager::{ChannelDetails, HTLCSource, IDEMPOTENCY_TIMEOUT_TICKS, MIN_HTLC_RELAY_HOLDING_CELL_MILLIS, PaymentId};
1919
use crate::ln::msgs::DecodeError;
2020
use crate::ln::onion_utils::HTLCFailReason;
21-
use crate::routing::router::{PaymentParameters, Route, RouteHop, RouteParameters, RoutePath};
21+
use crate::routing::router::{InFlightHtlcs, PaymentParameters, Route, RouteHop, RouteParameters, RoutePath, Router};
2222
use crate::util::errors::APIError;
2323
use crate::util::events;
2424
use crate::util::logger::Logger;
@@ -237,6 +237,16 @@ impl Retry {
237237
}
238238
}
239239

240+
#[cfg(feature = "std")]
241+
pub(super) fn has_expired(route_params: &RouteParameters) -> bool {
242+
if let Some(expiry_time) = route_params.payment_params.expiry_time {
243+
if let Ok(elapsed) = std::time::SystemTime::UNIX_EPOCH.elapsed() {
244+
return elapsed > core::time::Duration::from_secs(expiry_time)
245+
}
246+
}
247+
false
248+
}
249+
240250
pub(crate) type PaymentAttempts = PaymentAttemptsUsingTime<ConfiguredTime>;
241251

242252
/// Storing minimal payment attempts information required for determining if a outbound payment can
@@ -411,6 +421,109 @@ impl OutboundPayments {
411421
}
412422
}
413423

424+
pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
425+
&self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
426+
best_block_height: u32, logger: &L, send_payment_along_path: SP,
427+
)
428+
where
429+
R::Target: Router,
430+
ES::Target: EntropySource,
431+
NS::Target: NodeSigner,
432+
SP: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
433+
u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>,
434+
IH: Fn() -> InFlightHtlcs,
435+
FH: Fn() -> Vec<ChannelDetails>,
436+
L::Target: Logger,
437+
{
438+
loop {
439+
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
440+
let mut retry_id_route_params = None;
441+
for (pmt_id, pmt) in outbounds.iter_mut() {
442+
if pmt.is_retryable_now() {
443+
if let PendingOutboundPayment::Retryable { pending_amt_msat, total_msat, route_params: Some(params), .. } = pmt {
444+
if pending_amt_msat < total_msat {
445+
retry_id_route_params = Some((*pmt_id, params.clone()));
446+
pmt.increment_attempts();
447+
break
448+
}
449+
}
450+
}
451+
}
452+
if let Some((payment_id, route_params)) = retry_id_route_params {
453+
core::mem::drop(outbounds);
454+
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) {
455+
log_trace!(logger, "Errored retrying payment: {:?}", e);
456+
}
457+
} else { break }
458+
}
459+
}
460+
461+
fn pay_internal<R: Deref, NS: Deref, ES: Deref, F>(
462+
&self, payment_id: PaymentId, route_params: RouteParameters, router: &R,
463+
first_hops: Vec<ChannelDetails>, inflight_htlcs: InFlightHtlcs, entropy_source: &ES,
464+
node_signer: &NS, best_block_height: u32, send_payment_along_path: &F
465+
) -> Result<(), PaymentSendFailure>
466+
where
467+
R::Target: Router,
468+
ES::Target: EntropySource,
469+
NS::Target: NodeSigner,
470+
F: Fn(&Vec<RouteHop>, &Option<PaymentParameters>, &PaymentHash, &Option<PaymentSecret>, u64,
471+
u32, PaymentId, &Option<PaymentPreimage>, [u8; 32]) -> Result<(), APIError>
472+
{
473+
#[cfg(feature = "std")] {
474+
if has_expired(&route_params) {
475+
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
476+
err: format!("Invoice expired for payment id {}", log_bytes!(payment_id.0)),
477+
}))
478+
}
479+
}
480+
481+
let route = router.find_route(
482+
&node_signer.get_node_id(Recipient::Node).unwrap(), &route_params,
483+
Some(&first_hops.iter().collect::<Vec<_>>()), &inflight_htlcs
484+
).map_err(|e| PaymentSendFailure::ParameterError(APIError::APIMisuseError {
485+
err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound
486+
}))?;
487+
488+
let res = self.retry_payment_with_route(&route, payment_id, entropy_source, node_signer, best_block_height, send_payment_along_path);
489+
match res {
490+
Err(PaymentSendFailure::AllFailedResendSafe(_)) => {
491+
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
492+
if let Some(payment) = outbounds.get_mut(&payment_id) {
493+
let retryable = payment.is_retryable_now();
494+
if retryable {
495+
payment.increment_attempts();
496+
} else { return res }
497+
} else { return res }
498+
core::mem::drop(outbounds);
499+
self.pay_internal(payment_id, route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path)
500+
},
501+
Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, .. }) => {
502+
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
503+
if let Some(payment) = outbounds.get_mut(&payment_id) {
504+
let retryable = payment.is_retryable_now();
505+
if retryable {
506+
payment.increment_attempts();
507+
} else { return Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, payment_id }) }
508+
} else { return Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), results, payment_id }) }
509+
core::mem::drop(outbounds);
510+
511+
// Some paths were sent, even if we failed to send the full MPP value our recipient may
512+
// misbehave and claim the funds, at which point we have to consider the payment sent, so
513+
// return `Ok()` here, ignoring any retry errors.
514+
let _ = self.pay_internal(payment_id, retry, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, send_payment_along_path);
515+
Ok(())
516+
},
517+
Err(PaymentSendFailure::PartialFailure { failed_paths_retry: None, .. }) => {
518+
// This may happen if we send a payment and some paths fail, but only due to a temporary
519+
// monitor failure or the like, implying they're really in-flight, but we haven't sent the
520+
// initial HTLC-Add messages yet.
521+
Ok(())
522+
},
523+
res => res,
524+
}
525+
}
526+
414527
pub(super) fn retry_payment_with_route<ES: Deref, NS: Deref, F>(
415528
&self, route: &Route, payment_id: PaymentId, entropy_source: &ES, node_signer: &NS, best_block_height: u32,
416529
send_payment_along_path: F

0 commit comments

Comments
 (0)