Skip to content

Commit 01f8847

Browse files
committed
Add a variant to PendingOutboundPayment for retries-exceeded
When a payer gives up trying to retry a payment, they don't know for sure what the current state of the event queue is. Specifically, they cannot be sure that there are not multiple additional `PaymentPathFailed` or even `PaymentSuccess` events pending which they will see later. Thus, they have a very hard time identifying whether a payment has truly failed (and informing the UI of that fact) or if it is still pending. See [1] for more information. In order to avoid this mess, we will resolve it here by having the payer give `ChannelManager` a bit more information - when they have given up on a payment - and using that to generate a `PaymentFailed` event when all paths have failed. This commit adds the neccessary storage and changes for the new state inside `ChannelManager` and a public method to mark a payment as failed, the next few commits will add the new `Event` and use the new features in our `PaymentRetrier`. [1] lightningdevkit#1164
1 parent e70ef99 commit 01f8847

File tree

1 file changed

+70
-6
lines changed

1 file changed

+70
-6
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,17 @@ pub(crate) enum PendingOutboundPayment {
454454
session_privs: HashSet<[u8; 32]>,
455455
payment_hash: Option<PaymentHash>,
456456
},
457+
/// When a payer gives up trying to retry a payment, they inform us, letting us generate a
458+
/// `PaymentFailed` event when all HTLCs have irrevocably failed. This avoids a number of race
459+
/// conditions in MPP-aware payment retriers (1), where the possibility of multiple
460+
/// `PaymentPathFaield` events with `all_paths_failed` can be pending at once, confusing a
461+
/// downstream event handler as to when a payment has actually failed.
462+
///
463+
/// (1) https://github.com/lightningdevkit/rust-lightning/issues/1164
464+
RetriesExceeded {
465+
session_privs: HashSet<[u8; 32]>,
466+
payment_hash: PaymentHash,
467+
},
457468
}
458469

459470
impl PendingOutboundPayment {
@@ -469,6 +480,12 @@ impl PendingOutboundPayment {
469480
_ => false,
470481
}
471482
}
483+
fn retries_exceeded(&self) -> bool {
484+
match self {
485+
PendingOutboundPayment::RetriesExceeded { .. } => true,
486+
_ => false,
487+
}
488+
}
472489
fn get_pending_fee_msat(&self) -> Option<u64> {
473490
match self {
474491
PendingOutboundPayment::Retryable { pending_fee_msat, .. } => pending_fee_msat.clone(),
@@ -481,6 +498,7 @@ impl PendingOutboundPayment {
481498
PendingOutboundPayment::Legacy { .. } => None,
482499
PendingOutboundPayment::Retryable { payment_hash, .. } => Some(*payment_hash),
483500
PendingOutboundPayment::Fulfilled { payment_hash, .. } => *payment_hash,
501+
PendingOutboundPayment::RetriesExceeded { payment_hash, .. } => Some(*payment_hash),
484502
}
485503
}
486504

@@ -489,19 +507,38 @@ impl PendingOutboundPayment {
489507
core::mem::swap(&mut session_privs, match self {
490508
PendingOutboundPayment::Legacy { session_privs } |
491509
PendingOutboundPayment::Retryable { session_privs, .. } |
492-
PendingOutboundPayment::Fulfilled { session_privs, .. }
493-
=> session_privs
510+
PendingOutboundPayment::Fulfilled { session_privs, .. } |
511+
PendingOutboundPayment::RetriesExceeded { session_privs, .. }
512+
=> session_privs,
494513
});
495514
let payment_hash = self.payment_hash();
496515
*self = PendingOutboundPayment::Fulfilled { session_privs, payment_hash };
497516
}
498517

518+
fn mark_retries_exceeded(&mut self) -> Result<(), ()> {
519+
let mut session_privs = HashSet::new();
520+
let our_payment_hash;
521+
core::mem::swap(&mut session_privs, match self {
522+
PendingOutboundPayment::Legacy { .. } |
523+
PendingOutboundPayment::Fulfilled { .. } =>
524+
return Err(()),
525+
PendingOutboundPayment::Retryable { session_privs, payment_hash, .. } |
526+
PendingOutboundPayment::RetriesExceeded { session_privs, payment_hash, .. } => {
527+
our_payment_hash = *payment_hash;
528+
session_privs
529+
},
530+
});
531+
*self = PendingOutboundPayment::RetriesExceeded { session_privs, payment_hash: our_payment_hash };
532+
Ok(())
533+
}
534+
499535
/// panics if path is None and !self.is_fulfilled
500536
fn remove(&mut self, session_priv: &[u8; 32], path: Option<&Vec<RouteHop>>) -> bool {
501537
let remove_res = match self {
502538
PendingOutboundPayment::Legacy { session_privs } |
503539
PendingOutboundPayment::Retryable { session_privs, .. } |
504-
PendingOutboundPayment::Fulfilled { session_privs, .. } => {
540+
PendingOutboundPayment::Fulfilled { session_privs, .. } |
541+
PendingOutboundPayment::RetriesExceeded { session_privs, .. } => {
505542
session_privs.remove(session_priv)
506543
}
507544
};
@@ -524,7 +561,8 @@ impl PendingOutboundPayment {
524561
PendingOutboundPayment::Retryable { session_privs, .. } => {
525562
session_privs.insert(session_priv)
526563
}
527-
PendingOutboundPayment::Fulfilled { .. } => false
564+
PendingOutboundPayment::Fulfilled { .. } => false,
565+
PendingOutboundPayment::RetriesExceeded { .. } => false,
528566
};
529567
if insert_res {
530568
if let PendingOutboundPayment::Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
@@ -542,7 +580,8 @@ impl PendingOutboundPayment {
542580
match self {
543581
PendingOutboundPayment::Legacy { session_privs } |
544582
PendingOutboundPayment::Retryable { session_privs, .. } |
545-
PendingOutboundPayment::Fulfilled { session_privs, .. } => {
583+
PendingOutboundPayment::Fulfilled { session_privs, .. } |
584+
PendingOutboundPayment::RetriesExceeded { session_privs, .. } => {
546585
session_privs.len()
547586
}
548587
}
@@ -2352,6 +2391,11 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
23522391
err: "Payment already completed"
23532392
}));
23542393
},
2394+
PendingOutboundPayment::RetriesExceeded { .. } => {
2395+
return Err(PaymentSendFailure::ParameterError(APIError::RouteError {
2396+
err: "Payment already marked timed out (with some HTLCs still pending)"
2397+
}));
2398+
},
23552399
}
23562400
} else {
23572401
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
@@ -2362,6 +2406,21 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
23622406
return self.send_payment_internal(route, payment_hash, &payment_secret, None, Some(payment_id), Some(total_msat)).map(|_| ())
23632407
}
23642408

2409+
/// Signals that no further retries for the given payment will occur.
2410+
///
2411+
/// After this method returns, any future calls to [`retry_payment`] for the given `payment_id`
2412+
/// will fail with an [`PaymentSendFailure::ParameterError`] error.
2413+
///
2414+
/// [`retry_payment`]: Self::retry_payment
2415+
pub fn no_further_payment_retries(&self, payment_id: PaymentId) {
2416+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
2417+
2418+
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
2419+
if let hash_map::Entry::Occupied(mut payment) = outbounds.entry(payment_id) {
2420+
let _ = payment.get_mut().mark_retries_exceeded();
2421+
}
2422+
}
2423+
23652424
/// Send a spontaneous payment, which is a payment that does not require the recipient to have
23662425
/// generated an invoice. Optionally, you may specify the preimage. If you do choose to specify
23672426
/// the preimage, it must be a cryptographically secure random value that no intermediate node
@@ -5605,6 +5664,10 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
56055664
(8, pending_amt_msat, required),
56065665
(10, starting_block_height, required),
56075666
},
5667+
(3, RetriesExceeded) => {
5668+
(0, session_privs, required),
5669+
(2, payment_hash, required),
5670+
},
56085671
);
56095672

56105673
impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable for ChannelManager<Signer, M, T, K, F, L>
@@ -5698,7 +5761,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
56985761
// For backwards compat, write the session privs and their total length.
56995762
let mut num_pending_outbounds_compat: u64 = 0;
57005763
for (_, outbound) in pending_outbound_payments.iter() {
5701-
if !outbound.is_fulfilled() {
5764+
if !outbound.is_fulfilled() && !outbound.retries_exceeded() {
57025765
num_pending_outbounds_compat += outbound.remaining_parts() as u64;
57035766
}
57045767
}
@@ -5712,6 +5775,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
57125775
}
57135776
}
57145777
PendingOutboundPayment::Fulfilled { .. } => {},
5778+
PendingOutboundPayment::RetriesExceeded { .. } => {},
57155779
}
57165780
}
57175781

0 commit comments

Comments
 (0)