@@ -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+ /// `PaymentPathFailed` 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+ Abandoned {
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 abandoned ( & self ) -> bool {
484+ match self {
485+ PendingOutboundPayment :: Abandoned { .. } => 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 :: Abandoned { 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 :: Abandoned { 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_abandoned ( & 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 :: Abandoned { session_privs, payment_hash, .. } => {
527+ our_payment_hash = * payment_hash;
528+ session_privs
529+ } ,
530+ } ) ;
531+ * self = PendingOutboundPayment :: Abandoned { 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 :: Abandoned { 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 :: Abandoned { .. } => 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 :: Abandoned { session_privs, .. } => {
546585 session_privs. len ( )
547586 }
548587 }
@@ -2313,10 +2352,12 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
23132352 ///
23142353 /// Errors returned are a superset of those returned from [`send_payment`], so see
23152354 /// [`send_payment`] documentation for more details on errors. This method will also error if the
2316- /// retry amount puts the payment more than 10% over the payment's total amount, or if the payment
2317- /// for the given `payment_id` cannot be found (likely due to timeout or success).
2355+ /// retry amount puts the payment more than 10% over the payment's total amount, if the payment
2356+ /// for the given `payment_id` cannot be found (likely due to timeout or success), or if
2357+ /// further retries have been disabled with [`abandon_payment`].
23182358 ///
23192359 /// [`send_payment`]: [`ChannelManager::send_payment`]
2360+ /// [`abandon_payment`]: [`ChannelManager::abandon_payment`]
23202361 pub fn retry_payment ( & self , route : & Route , payment_id : PaymentId ) -> Result < ( ) , PaymentSendFailure > {
23212362 const RETRY_OVERFLOW_PERCENTAGE : u64 = 10 ;
23222363 for path in route. paths . iter ( ) {
@@ -2352,6 +2393,11 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
23522393 err : "Payment already completed"
23532394 } ) ) ;
23542395 } ,
2396+ PendingOutboundPayment :: Abandoned { .. } => {
2397+ return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
2398+ err : "Payment already abandoned (with some HTLCs still pending)" . to_owned ( )
2399+ } ) ) ;
2400+ } ,
23552401 }
23562402 } else {
23572403 return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
@@ -2362,6 +2408,21 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
23622408 return self . send_payment_internal ( route, payment_hash, & payment_secret, None , Some ( payment_id) , Some ( total_msat) ) . map ( |_| ( ) )
23632409 }
23642410
2411+ /// Signals that no further retries for the given payment will occur.
2412+ ///
2413+ /// After this method returns, any future calls to [`retry_payment`] for the given `payment_id`
2414+ /// will fail with [`PaymentSendFailure::ParameterError`].
2415+ ///
2416+ /// [`retry_payment`]: Self::retry_payment
2417+ pub fn abandon_payment ( & self , payment_id : PaymentId ) {
2418+ let _persistence_guard = PersistenceNotifierGuard :: notify_on_drop ( & self . total_consistency_lock , & self . persistence_notifier ) ;
2419+
2420+ let mut outbounds = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
2421+ if let hash_map:: Entry :: Occupied ( mut payment) = outbounds. entry ( payment_id) {
2422+ let _ = payment. get_mut ( ) . mark_abandoned ( ) ;
2423+ }
2424+ }
2425+
23652426 /// Send a spontaneous payment, which is a payment that does not require the recipient to have
23662427 /// generated an invoice. Optionally, you may specify the preimage. If you do choose to specify
23672428 /// the preimage, it must be a cryptographically secure random value that no intermediate node
@@ -5605,6 +5666,10 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
56055666 ( 8 , pending_amt_msat, required) ,
56065667 ( 10 , starting_block_height, required) ,
56075668 } ,
5669+ ( 3 , Abandoned ) => {
5670+ ( 0 , session_privs, required) ,
5671+ ( 2 , payment_hash, required) ,
5672+ } ,
56085673) ;
56095674
56105675impl < Signer : Sign , M : Deref , T : Deref , K : Deref , F : Deref , L : Deref > Writeable for ChannelManager < Signer , M , T , K , F , L >
@@ -5698,7 +5763,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
56985763 // For backwards compat, write the session privs and their total length.
56995764 let mut num_pending_outbounds_compat: u64 = 0 ;
57005765 for ( _, outbound) in pending_outbound_payments. iter ( ) {
5701- if !outbound. is_fulfilled ( ) {
5766+ if !outbound. is_fulfilled ( ) && !outbound . abandoned ( ) {
57025767 num_pending_outbounds_compat += outbound. remaining_parts ( ) as u64 ;
57035768 }
57045769 }
@@ -5712,6 +5777,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
57125777 }
57135778 }
57145779 PendingOutboundPayment :: Fulfilled { .. } => { } ,
5780+ PendingOutboundPayment :: Abandoned { .. } => { } ,
57155781 }
57165782 }
57175783
0 commit comments