@@ -454,6 +454,17 @@ pub(crate) enum PendingOutboundPayment {
454
454
session_privs : HashSet < [ u8 ; 32 ] > ,
455
455
payment_hash : Option < PaymentHash > ,
456
456
} ,
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
+ } ,
457
468
}
458
469
459
470
impl PendingOutboundPayment {
@@ -469,6 +480,12 @@ impl PendingOutboundPayment {
469
480
_ => false ,
470
481
}
471
482
}
483
+ fn retries_exceeded ( & self ) -> bool {
484
+ match self {
485
+ PendingOutboundPayment :: RetriesExceeded { .. } => true ,
486
+ _ => false ,
487
+ }
488
+ }
472
489
fn get_pending_fee_msat ( & self ) -> Option < u64 > {
473
490
match self {
474
491
PendingOutboundPayment :: Retryable { pending_fee_msat, .. } => pending_fee_msat. clone ( ) ,
@@ -481,6 +498,7 @@ impl PendingOutboundPayment {
481
498
PendingOutboundPayment :: Legacy { .. } => None ,
482
499
PendingOutboundPayment :: Retryable { payment_hash, .. } => Some ( * payment_hash) ,
483
500
PendingOutboundPayment :: Fulfilled { payment_hash, .. } => * payment_hash,
501
+ PendingOutboundPayment :: RetriesExceeded { payment_hash, .. } => Some ( * payment_hash) ,
484
502
}
485
503
}
486
504
@@ -489,19 +507,38 @@ impl PendingOutboundPayment {
489
507
core:: mem:: swap ( & mut session_privs, match self {
490
508
PendingOutboundPayment :: Legacy { session_privs } |
491
509
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,
494
513
} ) ;
495
514
let payment_hash = self . payment_hash ( ) ;
496
515
* self = PendingOutboundPayment :: Fulfilled { session_privs, payment_hash } ;
497
516
}
498
517
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
+
499
535
/// panics if path is None and !self.is_fulfilled
500
536
fn remove ( & mut self , session_priv : & [ u8 ; 32 ] , path : Option < & Vec < RouteHop > > ) -> bool {
501
537
let remove_res = match self {
502
538
PendingOutboundPayment :: Legacy { session_privs } |
503
539
PendingOutboundPayment :: Retryable { session_privs, .. } |
504
- PendingOutboundPayment :: Fulfilled { session_privs, .. } => {
540
+ PendingOutboundPayment :: Fulfilled { session_privs, .. } |
541
+ PendingOutboundPayment :: RetriesExceeded { session_privs, .. } => {
505
542
session_privs. remove ( session_priv)
506
543
}
507
544
} ;
@@ -524,7 +561,8 @@ impl PendingOutboundPayment {
524
561
PendingOutboundPayment :: Retryable { session_privs, .. } => {
525
562
session_privs. insert ( session_priv)
526
563
}
527
- PendingOutboundPayment :: Fulfilled { .. } => false
564
+ PendingOutboundPayment :: Fulfilled { .. } => false ,
565
+ PendingOutboundPayment :: RetriesExceeded { .. } => false ,
528
566
} ;
529
567
if insert_res {
530
568
if let PendingOutboundPayment :: Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
@@ -542,7 +580,8 @@ impl PendingOutboundPayment {
542
580
match self {
543
581
PendingOutboundPayment :: Legacy { session_privs } |
544
582
PendingOutboundPayment :: Retryable { session_privs, .. } |
545
- PendingOutboundPayment :: Fulfilled { session_privs, .. } => {
583
+ PendingOutboundPayment :: Fulfilled { session_privs, .. } |
584
+ PendingOutboundPayment :: RetriesExceeded { session_privs, .. } => {
546
585
session_privs. len ( )
547
586
}
548
587
}
@@ -2352,6 +2391,11 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
2352
2391
err : "Payment already completed"
2353
2392
} ) ) ;
2354
2393
} ,
2394
+ PendingOutboundPayment :: RetriesExceeded { .. } => {
2395
+ return Err ( PaymentSendFailure :: ParameterError ( APIError :: RouteError {
2396
+ err : "Payment already marked timed out (with some HTLCs still pending)"
2397
+ } ) ) ;
2398
+ } ,
2355
2399
}
2356
2400
} else {
2357
2401
return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
@@ -2362,6 +2406,21 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
2362
2406
return self . send_payment_internal ( route, payment_hash, & payment_secret, None , Some ( payment_id) , Some ( total_msat) ) . map ( |_| ( ) )
2363
2407
}
2364
2408
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
+
2365
2424
/// Send a spontaneous payment, which is a payment that does not require the recipient to have
2366
2425
/// generated an invoice. Optionally, you may specify the preimage. If you do choose to specify
2367
2426
/// 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,
5605
5664
( 8 , pending_amt_msat, required) ,
5606
5665
( 10 , starting_block_height, required) ,
5607
5666
} ,
5667
+ ( 3 , RetriesExceeded ) => {
5668
+ ( 0 , session_privs, required) ,
5669
+ ( 2 , payment_hash, required) ,
5670
+ } ,
5608
5671
) ;
5609
5672
5610
5673
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
5698
5761
// For backwards compat, write the session privs and their total length.
5699
5762
let mut num_pending_outbounds_compat: u64 = 0 ;
5700
5763
for ( _, outbound) in pending_outbound_payments. iter ( ) {
5701
- if !outbound. is_fulfilled ( ) {
5764
+ if !outbound. is_fulfilled ( ) && !outbound . retries_exceeded ( ) {
5702
5765
num_pending_outbounds_compat += outbound. remaining_parts ( ) as u64 ;
5703
5766
}
5704
5767
}
@@ -5712,6 +5775,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
5712
5775
}
5713
5776
}
5714
5777
PendingOutboundPayment :: Fulfilled { .. } => { } ,
5778
+ PendingOutboundPayment :: RetriesExceeded { .. } => { } ,
5715
5779
}
5716
5780
}
5717
5781
0 commit comments