@@ -41,7 +41,7 @@ pub(crate) enum PendingOutboundPayment {
41
41
session_privs : HashSet < [ u8 ; 32 ] > ,
42
42
} ,
43
43
Retryable {
44
- retry_strategy : Retry ,
44
+ retry_strategy : Option < Retry > ,
45
45
attempts : PaymentAttempts ,
46
46
route_params : Option < RouteParameters > ,
47
47
session_privs : HashSet < [ u8 ; 32 ] > ,
@@ -82,11 +82,25 @@ impl PendingOutboundPayment {
82
82
attempts. count += 1 ;
83
83
}
84
84
}
85
- fn is_retryable_now ( & self ) -> bool {
86
- if let PendingOutboundPayment :: Retryable { retry_strategy, attempts, .. } = self {
87
- return retry_strategy. is_retryable_now ( & attempts)
85
+ fn auto_retries_remaining_now ( & self ) -> usize {
86
+ match self {
87
+ PendingOutboundPayment :: Retryable { retry_strategy : Some ( strategy) , attempts, .. } => {
88
+ strategy. retries_remaining_now ( & attempts)
89
+ } ,
90
+ _ => 0 ,
91
+ }
92
+ }
93
+ fn is_retryable_now ( & self , pending_paths : usize ) -> bool {
94
+ match self {
95
+ PendingOutboundPayment :: Retryable { retry_strategy : None , .. } => {
96
+ // We're handling retries manually, we can always retry.
97
+ true
98
+ } ,
99
+ PendingOutboundPayment :: Retryable { retry_strategy : Some ( strategy) , attempts, .. } => {
100
+ strategy. retries_remaining_now ( & attempts) >= pending_paths
101
+ } ,
102
+ _ => false ,
88
103
}
89
- false
90
104
}
91
105
pub fn insert_previously_failed_scid ( & mut self , scid : u64 ) {
92
106
if let PendingOutboundPayment :: Retryable { route_params : Some ( params) , .. } = self {
@@ -212,27 +226,27 @@ impl PendingOutboundPayment {
212
226
pub enum Retry {
213
227
/// Max number of attempts to retry payment.
214
228
///
215
- /// Note that this is the number of *path* failures, not full payment retries. For multi-path
216
- /// payments, if this is less than the total number of paths, we will never even retry all of the
217
- /// payment's paths .
229
+ /// This is the number of additional paths along which the payment will be retried after the
230
+ /// first payment. If two paths fail at around the same time and are retried with a single
231
+ /// MPP path/HTLC, it will only count as a single retry .
218
232
Attempts ( usize ) ,
219
233
#[ cfg( not( feature = "no-std" ) ) ]
220
234
/// Time elapsed before abandoning retries for a payment.
221
235
Timeout ( core:: time:: Duration ) ,
222
236
}
223
237
224
238
impl Retry {
225
- pub ( crate ) fn is_retryable_now ( & self , attempts : & PaymentAttempts ) -> bool {
239
+ pub ( crate ) fn retries_remaining_now ( & self , attempts : & PaymentAttempts ) -> usize {
226
240
match ( self , attempts) {
227
241
( Retry :: Attempts ( max_retry_count) , PaymentAttempts { count, .. } ) => {
228
- max_retry_count > count
242
+ * max_retry_count - count
229
243
} ,
230
244
#[ cfg( all( not( feature = "no-std" ) , not( test) ) ) ]
231
245
( Retry :: Timeout ( max_duration) , PaymentAttempts { first_attempted_at, .. } ) =>
232
- * max_duration >= std:: time:: Instant :: now ( ) . duration_since ( * first_attempted_at) ,
246
+ if * max_duration >= std:: time:: Instant :: now ( ) . duration_since ( * first_attempted_at) { usize :: max_value ( ) } else { 0 } ,
233
247
#[ cfg( all( not( feature = "no-std" ) , test) ) ]
234
248
( Retry :: Timeout ( max_duration) , PaymentAttempts { first_attempted_at, .. } ) =>
235
- * max_duration >= SinceEpoch :: now ( ) . duration_since ( * first_attempted_at) ,
249
+ if * max_duration >= SinceEpoch :: now ( ) . duration_since ( * first_attempted_at) { usize :: max_value ( ) } else { 0 } ,
236
250
}
237
251
}
238
252
}
@@ -409,7 +423,7 @@ impl OutboundPayments {
409
423
F : Fn ( & Vec < RouteHop > , & Option < PaymentParameters > , & PaymentHash , & Option < PaymentSecret > , u64 ,
410
424
u32 , PaymentId , & Option < PaymentPreimage > , [ u8 ; 32 ] ) -> Result < ( ) , APIError >
411
425
{
412
- let onion_session_privs = self . add_new_pending_payment ( payment_hash, * payment_secret, payment_id, route, Retry :: Attempts ( 0 ) , None , entropy_source, best_block_height) ?;
426
+ let onion_session_privs = self . add_new_pending_payment ( payment_hash, * payment_secret, payment_id, route, None , None , entropy_source, best_block_height) ?;
413
427
self . pay_route_internal ( route, payment_hash, payment_secret, None , payment_id, None ,
414
428
onion_session_privs, node_signer, best_block_height, & send_payment_along_path)
415
429
. map_err ( |e| { self . remove_outbound_if_all_failed ( payment_id, & e) ; e } )
@@ -430,7 +444,7 @@ impl OutboundPayments {
430
444
None => PaymentPreimage ( entropy_source. get_secure_random_bytes ( ) ) ,
431
445
} ;
432
446
let payment_hash = PaymentHash ( Sha256 :: hash ( & preimage. 0 ) . into_inner ( ) ) ;
433
- let onion_session_privs = self . add_new_pending_payment ( payment_hash, None , payment_id, & route, Retry :: Attempts ( 0 ) , None , entropy_source, best_block_height) ?;
447
+ let onion_session_privs = self . add_new_pending_payment ( payment_hash, None , payment_id, & route, None , None , entropy_source, best_block_height) ?;
434
448
435
449
match self . pay_route_internal ( route, payment_hash, & None , Some ( preimage) , payment_id, None , onion_session_privs, node_signer, best_block_height, & send_payment_along_path) {
436
450
Ok ( ( ) ) => Ok ( payment_hash) ,
@@ -459,11 +473,15 @@ impl OutboundPayments {
459
473
let mut outbounds = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
460
474
let mut retry_id_route_params = None ;
461
475
for ( pmt_id, pmt) in outbounds. iter_mut ( ) {
462
- if pmt. is_retryable_now ( ) {
476
+ let retries_remaining = pmt. auto_retries_remaining_now ( ) ;
477
+ if retries_remaining > 0 {
463
478
if let PendingOutboundPayment :: Retryable { pending_amt_msat, total_msat, route_params : Some ( params) , .. } = pmt {
464
479
if pending_amt_msat < total_msat {
465
- retry_id_route_params = Some ( ( * pmt_id, params. clone ( ) ) ) ;
466
- pmt. increment_attempts ( ) ;
480
+ let mut params = params. clone ( ) ;
481
+ if params. payment_params . max_path_count as usize > retries_remaining {
482
+ params. payment_params . max_path_count = retries_remaining as u8 ;
483
+ }
484
+ retry_id_route_params = Some ( ( * pmt_id, params) ) ;
467
485
break
468
486
}
469
487
}
@@ -509,35 +527,21 @@ impl OutboundPayments {
509
527
} ) ) ?;
510
528
511
529
let res = if let Some ( ( payment_hash, payment_secret, retry_strategy) ) = initial_send_info {
512
- let onion_session_privs = self . add_new_pending_payment ( payment_hash, * payment_secret, payment_id, & route, retry_strategy, Some ( route_params. clone ( ) ) , entropy_source, best_block_height) ?;
530
+ let onion_session_privs = self . add_new_pending_payment ( payment_hash, * payment_secret, payment_id, & route, Some ( retry_strategy) , Some ( route_params. clone ( ) ) , entropy_source, best_block_height) ?;
513
531
self . pay_route_internal ( & route, payment_hash, payment_secret, None , payment_id, None , onion_session_privs, node_signer, best_block_height, send_payment_along_path)
514
532
} else {
515
533
self . retry_payment_with_route ( & route, payment_id, entropy_source, node_signer, best_block_height, send_payment_along_path)
516
534
} ;
517
535
match res {
518
536
Err ( PaymentSendFailure :: AllFailedResendSafe ( _) ) => {
519
- let mut outbounds = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
520
- if let Some ( payment) = outbounds. get_mut ( & payment_id) {
521
- let retryable = payment. is_retryable_now ( ) ;
522
- if retryable {
523
- payment. increment_attempts ( ) ;
524
- } else { return res }
525
- } else { return res }
526
- core:: mem:: drop ( outbounds) ;
527
537
let retry_res = self . pay_internal ( payment_id, None , route_params, router, first_hops, inflight_htlcs, entropy_source, node_signer, best_block_height, logger, send_payment_along_path) ;
528
538
log_info ! ( logger, "Errored retrying payment: {:?}" , retry_res) ;
539
+ if let Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError { err } ) ) = & retry_res {
540
+ if err. starts_with ( "Retries exhausted " ) { return res; }
541
+ }
529
542
retry_res
530
543
} ,
531
- Err ( PaymentSendFailure :: PartialFailure { failed_paths_retry : Some ( retry) , results, .. } ) => {
532
- let mut outbounds = self . pending_outbound_payments . lock ( ) . unwrap ( ) ;
533
- if let Some ( payment) = outbounds. get_mut ( & payment_id) {
534
- let retryable = payment. is_retryable_now ( ) ;
535
- if retryable {
536
- payment. increment_attempts ( ) ;
537
- } else { return Err ( PaymentSendFailure :: PartialFailure { failed_paths_retry : Some ( retry) , results, payment_id } ) }
538
- } else { return Err ( PaymentSendFailure :: PartialFailure { failed_paths_retry : Some ( retry) , results, payment_id } ) }
539
- core:: mem:: drop ( outbounds) ;
540
-
544
+ Err ( PaymentSendFailure :: PartialFailure { failed_paths_retry : Some ( retry) , .. } ) => {
541
545
// Some paths were sent, even if we failed to send the full MPP value our recipient may
542
546
// misbehave and claim the funds, at which point we have to consider the payment sent, so
543
547
// return `Ok()` here, ignoring any retry errors.
@@ -611,8 +615,14 @@ impl OutboundPayments {
611
615
} ) ) ;
612
616
} ,
613
617
} ;
618
+ if !payment. is_retryable_now ( route. paths . len ( ) ) {
619
+ return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
620
+ err : format ! ( "Retries exhausted for payment id {}" , log_bytes!( payment_id. 0 ) ) ,
621
+ } ) )
622
+ }
614
623
for ( path, session_priv_bytes) in route. paths . iter ( ) . zip ( onion_session_privs. iter ( ) ) {
615
624
assert ! ( payment. insert( * session_priv_bytes, path) ) ;
625
+ payment. increment_attempts ( ) ;
616
626
}
617
627
res
618
628
} ,
@@ -646,7 +656,7 @@ impl OutboundPayments {
646
656
}
647
657
648
658
let route = Route { paths : vec ! [ hops] , payment_params : None } ;
649
- let onion_session_privs = self . add_new_pending_payment ( payment_hash, None , payment_id, & route, Retry :: Attempts ( 0 ) , None , entropy_source, best_block_height) ?;
659
+ let onion_session_privs = self . add_new_pending_payment ( payment_hash, None , payment_id, & route, None , None , entropy_source, best_block_height) ?;
650
660
651
661
match self . pay_route_internal ( & route, payment_hash, & None , None , payment_id, None , onion_session_privs, node_signer, best_block_height, & send_payment_along_path) {
652
662
Ok ( ( ) ) => Ok ( ( payment_hash, payment_id) ) ,
@@ -660,14 +670,14 @@ impl OutboundPayments {
660
670
#[ cfg( test) ]
661
671
pub ( super ) fn test_add_new_pending_payment < ES : Deref > (
662
672
& self , payment_hash : PaymentHash , payment_secret : Option < PaymentSecret > , payment_id : PaymentId ,
663
- route : & Route , retry_strategy : Retry , entropy_source : & ES , best_block_height : u32
673
+ route : & Route , retry_strategy : Option < Retry > , entropy_source : & ES , best_block_height : u32
664
674
) -> Result < Vec < [ u8 ; 32 ] > , PaymentSendFailure > where ES :: Target : EntropySource {
665
675
self . add_new_pending_payment ( payment_hash, payment_secret, payment_id, route, retry_strategy, None , entropy_source, best_block_height)
666
676
}
667
677
668
678
pub ( super ) fn add_new_pending_payment < ES : Deref > (
669
679
& self , payment_hash : PaymentHash , payment_secret : Option < PaymentSecret > , payment_id : PaymentId ,
670
- route : & Route , retry_strategy : Retry , route_params : Option < RouteParameters > ,
680
+ route : & Route , retry_strategy : Option < Retry > , route_params : Option < RouteParameters > ,
671
681
entropy_source : & ES , best_block_height : u32
672
682
) -> Result < Vec < [ u8 ; 32 ] > , PaymentSendFailure > where ES :: Target : EntropySource {
673
683
let mut onion_session_privs = Vec :: with_capacity ( route. paths . len ( ) ) ;
@@ -969,7 +979,7 @@ impl OutboundPayments {
969
979
log_trace ! ( logger, "Received failure of HTLC with payment_hash {} after payment completion" , log_bytes!( payment_hash. 0 ) ) ;
970
980
return
971
981
}
972
- let is_retryable_now = payment. get ( ) . is_retryable_now ( ) ;
982
+ let is_retryable_now = payment. get ( ) . auto_retries_remaining_now ( ) > 0 ;
973
983
if let Some ( scid) = short_channel_id {
974
984
payment. get_mut ( ) . insert_previously_failed_scid ( scid) ;
975
985
}
@@ -1110,7 +1120,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
1110
1120
( 0 , session_privs, required) ,
1111
1121
( 1 , pending_fee_msat, option) ,
1112
1122
( 2 , payment_hash, required) ,
1113
- ( not_written, retry_strategy, ( static_value, Retry :: Attempts ( 0 ) ) ) ,
1123
+ ( not_written, retry_strategy, ( static_value, None ) ) ,
1114
1124
( 4 , payment_secret, option) ,
1115
1125
( not_written, attempts, ( static_value, PaymentAttempts :: new( ) ) ) ,
1116
1126
( 6 , total_msat, required) ,
@@ -1207,7 +1217,7 @@ mod tests {
1207
1217
1208
1218
let err = if on_retry {
1209
1219
outbound_payments. add_new_pending_payment ( PaymentHash ( [ 0 ; 32 ] ) , None , PaymentId ( [ 0 ; 32 ] ) ,
1210
- & Route { paths : vec ! [ ] , payment_params : None } , Retry :: Attempts ( 1 ) , Some ( route_params. clone ( ) ) ,
1220
+ & Route { paths : vec ! [ ] , payment_params : None } , Some ( Retry :: Attempts ( 1 ) ) , Some ( route_params. clone ( ) ) ,
1211
1221
& & keys_manager, 0 ) . unwrap ( ) ;
1212
1222
outbound_payments. pay_internal (
1213
1223
PaymentId ( [ 0 ; 32 ] ) , None , route_params, & & router, vec ! [ ] , InFlightHtlcs :: new ( ) ,
0 commit comments