@@ -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
+ /// `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
+ } ,
457
468
}
458
469
459
470
impl PendingOutboundPayment {
@@ -469,6 +480,12 @@ impl PendingOutboundPayment {
469
480
_ => false ,
470
481
}
471
482
}
483
+ fn abandoned ( & self ) -> bool {
484
+ match self {
485
+ PendingOutboundPayment :: Abandoned { .. } => 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 :: Abandoned { 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 :: Abandoned { 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_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
+
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 :: Abandoned { 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 :: Abandoned { .. } => 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 :: Abandoned { session_privs, .. } => {
546
585
session_privs. len ( )
547
586
}
548
587
}
@@ -2313,10 +2352,12 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
2313
2352
///
2314
2353
/// Errors returned are a superset of those returned from [`send_payment`], so see
2315
2354
/// [`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`].
2318
2358
///
2319
2359
/// [`send_payment`]: [`ChannelManager::send_payment`]
2360
+ /// [`abandon_payment`]: [`ChannelManager::abandon_payment`]
2320
2361
pub fn retry_payment ( & self , route : & Route , payment_id : PaymentId ) -> Result < ( ) , PaymentSendFailure > {
2321
2362
const RETRY_OVERFLOW_PERCENTAGE : u64 = 10 ;
2322
2363
for path in route. paths . iter ( ) {
@@ -2352,6 +2393,11 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
2352
2393
err : "Payment already completed"
2353
2394
} ) ) ;
2354
2395
} ,
2396
+ PendingOutboundPayment :: Abandoned { .. } => {
2397
+ return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
2398
+ err : "Payment already abandoned (with some HTLCs still pending)" . to_owned ( )
2399
+ } ) ) ;
2400
+ } ,
2355
2401
}
2356
2402
} else {
2357
2403
return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
@@ -2362,6 +2408,21 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
2362
2408
return self . send_payment_internal ( route, payment_hash, & payment_secret, None , Some ( payment_id) , Some ( total_msat) ) . map ( |_| ( ) )
2363
2409
}
2364
2410
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
+
2365
2426
/// Send a spontaneous payment, which is a payment that does not require the recipient to have
2366
2427
/// generated an invoice. Optionally, you may specify the preimage. If you do choose to specify
2367
2428
/// 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,
5605
5666
( 8 , pending_amt_msat, required) ,
5606
5667
( 10 , starting_block_height, required) ,
5607
5668
} ,
5669
+ ( 3 , Abandoned ) => {
5670
+ ( 0 , session_privs, required) ,
5671
+ ( 2 , payment_hash, required) ,
5672
+ } ,
5608
5673
) ;
5609
5674
5610
5675
impl < 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
5698
5763
// For backwards compat, write the session privs and their total length.
5699
5764
let mut num_pending_outbounds_compat: u64 = 0 ;
5700
5765
for ( _, outbound) in pending_outbound_payments. iter ( ) {
5701
- if !outbound. is_fulfilled ( ) {
5766
+ if !outbound. is_fulfilled ( ) && !outbound . abandoned ( ) {
5702
5767
num_pending_outbounds_compat += outbound. remaining_parts ( ) as u64 ;
5703
5768
}
5704
5769
}
@@ -5712,6 +5777,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
5712
5777
}
5713
5778
}
5714
5779
PendingOutboundPayment :: Fulfilled { .. } => { } ,
5780
+ PendingOutboundPayment :: Abandoned { .. } => { } ,
5715
5781
}
5716
5782
}
5717
5783
0 commit comments