@@ -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
459470impl 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
@@ -5601,6 +5660,10 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
56015660 ( 8 , pending_amt_msat, required) ,
56025661 ( 10 , starting_block_height, required) ,
56035662 } ,
5663+ ( 3 , RetriesExceeded ) => {
5664+ ( 0 , session_privs, required) ,
5665+ ( 2 , payment_hash, required) ,
5666+ } ,
56045667) ;
56055668
56065669impl < Signer : Sign , M : Deref , T : Deref , K : Deref , F : Deref , L : Deref > Writeable for ChannelManager < Signer , M , T , K , F , L >
@@ -5694,7 +5757,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
56945757 // For backwards compat, write the session privs and their total length.
56955758 let mut num_pending_outbounds_compat: u64 = 0 ;
56965759 for ( _, outbound) in pending_outbound_payments. iter ( ) {
5697- if !outbound. is_fulfilled ( ) {
5760+ if !outbound. is_fulfilled ( ) && !outbound . retries_exceeded ( ) {
56985761 num_pending_outbounds_compat += outbound. remaining_parts ( ) as u64 ;
56995762 }
57005763 }
@@ -5708,6 +5771,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
57085771 }
57095772 }
57105773 PendingOutboundPayment :: Fulfilled { .. } => { } ,
5774+ PendingOutboundPayment :: RetriesExceeded { .. } => { } ,
57115775 }
57125776 }
57135777
0 commit comments