@@ -454,6 +454,16 @@ 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
+ /// [1] https://github.com/lightningdevkit/rust-lightning/issues/1164
463
+ RetriesExceeded {
464
+ session_privs : HashSet < [ u8 ; 32 ] > ,
465
+ payment_hash : PaymentHash ,
466
+ } ,
457
467
}
458
468
459
469
impl PendingOutboundPayment {
@@ -469,6 +479,12 @@ impl PendingOutboundPayment {
469
479
_ => false ,
470
480
}
471
481
}
482
+ fn retries_exceeded ( & self ) -> bool {
483
+ match self {
484
+ PendingOutboundPayment :: RetriesExceeded { .. } => true ,
485
+ _ => false ,
486
+ }
487
+ }
472
488
fn get_pending_fee_msat ( & self ) -> Option < u64 > {
473
489
match self {
474
490
PendingOutboundPayment :: Retryable { pending_fee_msat, .. } => pending_fee_msat. clone ( ) ,
@@ -481,6 +497,7 @@ impl PendingOutboundPayment {
481
497
PendingOutboundPayment :: Legacy { .. } => None ,
482
498
PendingOutboundPayment :: Retryable { payment_hash, .. } => Some ( * payment_hash) ,
483
499
PendingOutboundPayment :: Fulfilled { payment_hash, .. } => * payment_hash,
500
+ PendingOutboundPayment :: RetriesExceeded { payment_hash, .. } => Some ( * payment_hash) ,
484
501
}
485
502
}
486
503
@@ -489,19 +506,38 @@ impl PendingOutboundPayment {
489
506
core:: mem:: swap ( & mut session_privs, match self {
490
507
PendingOutboundPayment :: Legacy { session_privs } |
491
508
PendingOutboundPayment :: Retryable { session_privs, .. } |
492
- PendingOutboundPayment :: Fulfilled { session_privs, .. }
493
- => session_privs
509
+ PendingOutboundPayment :: Fulfilled { session_privs, .. } |
510
+ PendingOutboundPayment :: RetriesExceeded { session_privs, .. }
511
+ => session_privs,
494
512
} ) ;
495
513
let payment_hash = self . payment_hash ( ) ;
496
514
* self = PendingOutboundPayment :: Fulfilled { session_privs, payment_hash } ;
497
515
}
498
516
517
+ fn mark_retries_exceeded ( & mut self ) -> Result < ( ) , ( ) > {
518
+ let mut session_privs = HashSet :: new ( ) ;
519
+ let our_payment_hash;
520
+ core:: mem:: swap ( & mut session_privs, match self {
521
+ PendingOutboundPayment :: Legacy { .. } |
522
+ PendingOutboundPayment :: Fulfilled { .. } =>
523
+ return Err ( ( ) ) ,
524
+ PendingOutboundPayment :: Retryable { session_privs, payment_hash, .. } |
525
+ PendingOutboundPayment :: RetriesExceeded { session_privs, payment_hash, .. } => {
526
+ our_payment_hash = * payment_hash;
527
+ session_privs
528
+ } ,
529
+ } ) ;
530
+ * self = PendingOutboundPayment :: RetriesExceeded { session_privs, payment_hash : our_payment_hash } ;
531
+ Ok ( ( ) )
532
+ }
533
+
499
534
/// panics if path is None and !self.is_fulfilled
500
535
fn remove ( & mut self , session_priv : & [ u8 ; 32 ] , path : Option < & Vec < RouteHop > > ) -> bool {
501
536
let remove_res = match self {
502
537
PendingOutboundPayment :: Legacy { session_privs } |
503
538
PendingOutboundPayment :: Retryable { session_privs, .. } |
504
- PendingOutboundPayment :: Fulfilled { session_privs, .. } => {
539
+ PendingOutboundPayment :: Fulfilled { session_privs, .. } |
540
+ PendingOutboundPayment :: RetriesExceeded { session_privs, .. } => {
505
541
session_privs. remove ( session_priv)
506
542
}
507
543
} ;
@@ -524,7 +560,8 @@ impl PendingOutboundPayment {
524
560
PendingOutboundPayment :: Retryable { session_privs, .. } => {
525
561
session_privs. insert ( session_priv)
526
562
}
527
- PendingOutboundPayment :: Fulfilled { .. } => false
563
+ PendingOutboundPayment :: Fulfilled { .. } => false ,
564
+ PendingOutboundPayment :: RetriesExceeded { .. } => false ,
528
565
} ;
529
566
if insert_res {
530
567
if let PendingOutboundPayment :: Retryable { ref mut pending_amt_msat, ref mut pending_fee_msat, .. } = self {
@@ -542,7 +579,8 @@ impl PendingOutboundPayment {
542
579
match self {
543
580
PendingOutboundPayment :: Legacy { session_privs } |
544
581
PendingOutboundPayment :: Retryable { session_privs, .. } |
545
- PendingOutboundPayment :: Fulfilled { session_privs, .. } => {
582
+ PendingOutboundPayment :: Fulfilled { session_privs, .. } |
583
+ PendingOutboundPayment :: RetriesExceeded { session_privs, .. } => {
546
584
session_privs. len ( )
547
585
}
548
586
}
@@ -2352,6 +2390,11 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
2352
2390
err : "Payment already completed"
2353
2391
} ) ) ;
2354
2392
} ,
2393
+ PendingOutboundPayment :: RetriesExceeded { .. } => {
2394
+ return Err ( PaymentSendFailure :: ParameterError ( APIError :: RouteError {
2395
+ err : "Payment already marked timed out (with some HTLCs still pending)"
2396
+ } ) ) ;
2397
+ } ,
2355
2398
}
2356
2399
} else {
2357
2400
return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
@@ -2362,6 +2405,14 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
2362
2405
return self . send_payment_internal ( route, payment_hash, & payment_secret, None , Some ( payment_id) , Some ( total_msat) ) . map ( |_| ( ) )
2363
2406
}
2364
2407
2408
+ /// Signals that no further retries for the given payment will occur.
2409
+ pub fn no_further_payment_retries ( & self , payment_id : PaymentId ) {
2410
+ let mut outbounds = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
2411
+ if let hash_map:: Entry :: Occupied ( mut payment) = outbounds. entry ( payment_id) {
2412
+ let _ = payment. get_mut ( ) . mark_retries_exceeded ( ) ;
2413
+ }
2414
+ }
2415
+
2365
2416
/// Send a spontaneous payment, which is a payment that does not require the recipient to have
2366
2417
/// generated an invoice. Optionally, you may specify the preimage. If you do choose to specify
2367
2418
/// the preimage, it must be a cryptographically secure random value that no intermediate node
@@ -5601,6 +5652,10 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
5601
5652
( 8 , pending_amt_msat, required) ,
5602
5653
( 10 , starting_block_height, required) ,
5603
5654
} ,
5655
+ ( 3 , RetriesExceeded ) => {
5656
+ ( 0 , session_privs, required) ,
5657
+ ( 2 , payment_hash, required) ,
5658
+ } ,
5604
5659
) ;
5605
5660
5606
5661
impl < Signer : Sign , M : Deref , T : Deref , K : Deref , F : Deref , L : Deref > Writeable for ChannelManager < Signer , M , T , K , F , L >
@@ -5694,7 +5749,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
5694
5749
// For backwards compat, write the session privs and their total length.
5695
5750
let mut num_pending_outbounds_compat: u64 = 0 ;
5696
5751
for ( _, outbound) in pending_outbound_payments. iter ( ) {
5697
- if !outbound. is_fulfilled ( ) {
5752
+ if !outbound. is_fulfilled ( ) && !outbound . retries_exceeded ( ) {
5698
5753
num_pending_outbounds_compat += outbound. remaining_parts ( ) as u64 ;
5699
5754
}
5700
5755
}
@@ -5708,6 +5763,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
5708
5763
}
5709
5764
}
5710
5765
PendingOutboundPayment :: Fulfilled { .. } => { } ,
5766
+ PendingOutboundPayment :: RetriesExceeded { .. } => { } ,
5711
5767
}
5712
5768
}
5713
5769
0 commit comments