Skip to content

Commit 19c9ef3

Browse files
Retry HTLCs in process_pending_htlc_forwards
1 parent 84a5066 commit 19c9ef3

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-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: 110 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
@@ -418,6 +428,104 @@ impl OutboundPayments {
418428
}
419429
}
420430

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

0 commit comments

Comments
 (0)