Skip to content

Commit c7c1a5f

Browse files
Implement automatic payment retries in ChannelManager
TODO: more tests
1 parent cb689b8 commit c7c1a5f

File tree

3 files changed

+319
-6
lines changed

3 files changed

+319
-6
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ use crate::ln::onion_utils;
5252
use crate::ln::onion_utils::HTLCFailReason;
5353
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError, MAX_VALUE_MSAT};
5454
use crate::ln::payment_retry::{PaymentAttempts, Retry};
55+
#[cfg(feature = "std")]
56+
use crate::ln::payment_retry::has_expired;
5557
use crate::ln::wire::Encode;
5658
use crate::chain::keysinterface::{Sign, KeysInterface, KeysManager, Recipient};
5759
use crate::util::config::{UserConfig, ChannelConfig};
@@ -2607,6 +2609,47 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<
26072609
self.send_payment_internal(route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs)
26082610
}
26092611

2612+
/// Similar to [`ChannelManager::send_payment`], but will automatically find a route based on
2613+
/// `route_params` and retry failed payment paths based on `retry_strategy`.
2614+
pub fn send_payment_with_retry(&self, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>, payment_id: PaymentId, route_params: RouteParameters, retry_strategy: Retry) -> Result<(), PaymentSendFailure> {
2615+
#[cfg(feature = "std")] {
2616+
if has_expired(&route_params) {
2617+
log_trace!(self.logger, "Invoice expired prior to send for payment {}", log_bytes!(payment_hash.0));
2618+
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError { err: format!("Invoice expired prior to send for payment {}", log_bytes!(payment_hash.0))}));
2619+
}
2620+
}
2621+
2622+
let route = self.router.find_route(
2623+
&self.get_our_node_id(), &route_params, Some(&self.list_usable_channels().iter().collect::<Vec<_>>()),
2624+
self.compute_inflight_htlcs()
2625+
).map_err(|e| PaymentSendFailure::ParameterError(APIError::APIMisuseError {
2626+
err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound
2627+
}))?;
2628+
2629+
log_trace!(self.logger, "Attempting to send payment with id {} and hash {} over route {}", log_bytes!(payment_id.0), log_bytes!(payment_hash.0), log_route!(route));
2630+
let onion_session_privs = self.add_new_pending_payment(payment_hash, *payment_secret, payment_id, &route, retry_strategy)?;
2631+
match self.send_payment_internal(&route, payment_hash, payment_secret, None, payment_id, None, onion_session_privs) {
2632+
Err(PaymentSendFailure::AllFailedResendSafe(_)) => {
2633+
self.retry_payment_auto_route(payment_id, route_params)
2634+
},
2635+
Err(PaymentSendFailure::PartialFailure { failed_paths_retry, .. }) => {
2636+
if let Some(retry_data) = failed_paths_retry {
2637+
// Some paths were sent, even if we failed to send the full MPP value our recipient may
2638+
// misbehave and claim the funds, at which point we have to consider the payment sent, so
2639+
// return `Ok()` here, ignoring any retry errors.
2640+
let _ = self.retry_payment_auto_route(payment_id, retry_data);
2641+
Ok(())
2642+
} else {
2643+
// This may happen if we send a payment and some paths fail, but only due to a temporary
2644+
// monitor failure or the like, implying they're really in-flight, but we haven't sent the
2645+
// initial HTLC-Add messages yet.
2646+
Ok(())
2647+
}
2648+
},
2649+
res => res,
2650+
}
2651+
}
2652+
26102653
#[cfg(test)]
26112654
pub(crate) fn test_add_new_pending_payment(&self, payment_hash: PaymentHash, payment_secret: Option<PaymentSecret>, payment_id: PaymentId, route: &Route) -> Result<Vec<[u8; 32]>, PaymentSendFailure> {
26122655
self.add_new_pending_payment(payment_hash, payment_secret, payment_id, route, Retry::Attempts(0))
@@ -2732,16 +2775,58 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<
27322775
} else { None },
27332776
})
27342777
} else if has_err {
2735-
// If we failed to send any paths, we should remove the new PaymentId from the
2736-
// `pending_outbound_payments` map, as the user isn't expected to `abandon_payment`.
2737-
let removed = self.pending_outbound_payments.lock().unwrap().remove(&payment_id).is_some();
2738-
debug_assert!(removed, "We should always have a pending payment to remove here");
2778+
if !self.pending_outbound_payments.lock().unwrap().get(&payment_id)
2779+
.map_or(false, |payment| payment.is_retryable())
2780+
{
2781+
// If we failed to send any paths, we should remove the new PaymentId from the
2782+
// `pending_outbound_payments` map, as the user isn't expected to `abandon_payment`.
2783+
let removed = self.pending_outbound_payments.lock().unwrap().remove(&payment_id).is_some();
2784+
debug_assert!(removed, "We should always have a pending payment to remove here");
2785+
}
27392786
Err(PaymentSendFailure::AllFailedResendSafe(results.drain(..).map(|r| r.unwrap_err()).collect()))
27402787
} else {
27412788
Ok(())
27422789
}
27432790
}
27442791

2792+
fn retry_payment_auto_route(&self, payment_id: PaymentId, route_params: RouteParameters) -> Result<(), PaymentSendFailure> {
2793+
#[cfg(feature = "std")] {
2794+
if has_expired(&route_params) {
2795+
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
2796+
err: format!("Invoice expired for payment id {}; not retrying", log_bytes!(payment_id.0)),
2797+
}))
2798+
}
2799+
}
2800+
2801+
self.pending_outbound_payments.lock().unwrap().get_mut(&payment_id).map(|pmt| pmt.increment_attempts());
2802+
if !self.pending_outbound_payments.lock().unwrap().get(&payment_id)
2803+
.map_or(false, |payment| payment.is_retryable())
2804+
{
2805+
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
2806+
err: format!("Payment {} is not retryable", log_bytes!(payment_id.0)),
2807+
}))
2808+
}
2809+
2810+
let route = self.router.find_route(
2811+
&self.get_our_node_id(), &route_params, Some(&self.list_usable_channels().iter().collect::<Vec<_>>()),
2812+
self.compute_inflight_htlcs()
2813+
).map_err(|e| PaymentSendFailure::ParameterError(APIError::APIMisuseError {
2814+
err: format!("Failed to find a route for payment {}: {:?}", log_bytes!(payment_id.0), e), // TODO: add APIError::RouteNotFound
2815+
}))?;
2816+
2817+
match self.retry_payment(&route, payment_id) {
2818+
Err(PaymentSendFailure::AllFailedResendSafe(_)) => {
2819+
self.retry_payment_auto_route(payment_id, route_params)
2820+
},
2821+
Err(PaymentSendFailure::PartialFailure { failed_paths_retry: Some(retry), .. }) => {
2822+
// Always return Ok for the same reason as noted in send_payment_with_retry.
2823+
let _ = self.retry_payment_auto_route(payment_id, retry);
2824+
Ok(())
2825+
},
2826+
res => res,
2827+
}
2828+
}
2829+
27452830
/// Retries a payment along the given [`Route`].
27462831
///
27472832
/// Errors returned are a superset of those returned from [`send_payment`], so see
@@ -3600,6 +3685,17 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<
36003685
}
36013686
}
36023687
}
3688+
{
3689+
let mut retryable_htlcs = Vec::new();
3690+
mem::swap(&mut retryable_htlcs, &mut self.retryable_htlcs.lock().unwrap());
3691+
for (payment_id, params) in retryable_htlcs {
3692+
// TODO: do we need to generate a PendingHTLCsForwardable event?
3693+
if let Err(e) = self.retry_payment_auto_route(payment_id, params) {
3694+
log_trace!(self.logger, "Errored retrying payment, marking as abandoned: {:?}", e);
3695+
self.abandon_payment(payment_id);
3696+
}
3697+
}
3698+
}
36033699

36043700
for (htlc_source, payment_hash, failure_reason, destination) in failed_forwards.drain(..) {
36053701
self.fail_htlc_backwards_internal(&htlc_source, &payment_hash, &failure_reason, destination);
@@ -3966,7 +4062,8 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<
39664062
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
39674063
let mut all_paths_failed = false;
39684064
let mut full_failure_ev = None;
3969-
if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) {
4065+
let mut pending_retry_ev = None;
4066+
let attempts_remaining = if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(*payment_id) {
39704067
if !payment.get_mut().remove(&session_priv_bytes, Some(&path)) {
39714068
log_trace!(self.logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
39724069
return;
@@ -3975,6 +4072,7 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<
39754072
log_trace!(self.logger, "Received failure of HTLC with payment_hash {} after payment completion", log_bytes!(payment_hash.0));
39764073
return;
39774074
}
4075+
let is_retryable = payment.get().is_retryable();
39784076
if payment.get().remaining_parts() == 0 {
39794077
all_paths_failed = true;
39804078
if payment.get().abandoned() {
@@ -3985,10 +4083,11 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<
39854083
payment.remove();
39864084
}
39874085
}
4086+
is_retryable
39884087
} else {
39894088
log_trace!(self.logger, "Received duplicative fail for HTLC with payment_hash {}", log_bytes!(payment_hash.0));
39904089
return;
3991-
}
4090+
};
39924091
let mut retry = if let Some(payment_params_data) = payment_params {
39934092
let path_last_hop = path.last().expect("Outbound payments must have had a valid path");
39944093
Some(RouteParameters {
@@ -4027,6 +4126,15 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<
40274126
if let Some(scid) = short_channel_id {
40284127
retry.as_mut().map(|r| r.payment_params.previously_failed_channels.push(scid));
40294128
}
4129+
if payment_retryable && attempts_remaining {
4130+
if let Some(retry_params) = &retry {
4131+
debug_assert!(full_failure_ev.is_none());
4132+
pending_retry_ev = Some(events::Event::PendingHTLCsForwardable {
4133+
time_forwardable: Duration::from_millis(MIN_HTLC_RELAY_HOLDING_CELL_MILLIS),
4134+
});
4135+
self.retryable_htlcs.lock().unwrap().push((*payment_id, retry_params.clone()));
4136+
}
4137+
}
40304138
events::Event::PaymentPathFailed {
40314139
payment_id: Some(*payment_id),
40324140
payment_hash: payment_hash.clone(),
@@ -4046,6 +4154,7 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, R: Deref, L: Deref> ChannelManager<
40464154
let mut pending_events = self.pending_events.lock().unwrap();
40474155
pending_events.push(path_failure);
40484156
if let Some(ev) = full_failure_ev { pending_events.push(ev); }
4157+
if let Some(ev) = pending_retry_ev { pending_events.push(ev); }
40494158
},
40504159
HTLCSource::PreviousHopData(HTLCPreviousHopData { ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret, ref phantom_shared_secret, ref outpoint }) => {
40514160
log_trace!(self.logger, "Failing HTLC with payment_hash {} backwards from us with {:?}", log_bytes!(payment_hash.0), onion_error);

lightning/src/ln/payment_retry.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
//! Payment retry utilities for use in and with `ChannelManager`.
1111
12+
#[cfg(feature = "std")]
13+
use crate::routing::router::RouteParameters;
1214
use crate::util::time::Time;
1315
#[cfg(all(not(feature = "no-std"), test))]
1416
use crate::util::time::tests::SinceEpoch;
@@ -46,6 +48,16 @@ impl Retry {
4648
}
4749
}
4850

51+
#[cfg(feature = "std")]
52+
pub(crate) fn has_expired(route_params: &RouteParameters) -> bool {
53+
if let Some(expiry_time) = route_params.payment_params.expiry_time {
54+
if let Ok(elapsed) = std::time::SystemTime::UNIX_EPOCH.elapsed() {
55+
return elapsed > core::time::Duration::from_secs(expiry_time)
56+
}
57+
}
58+
false
59+
}
60+
4961
#[cfg(not(any(feature = "no-std", test)))]
5062
type ConfiguredTime = std::time::Instant;
5163
#[cfg(feature = "no-std")]

0 commit comments

Comments
 (0)