@@ -54,6 +54,11 @@ use crate::sync::Mutex;
54
54
/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
55
55
pub ( crate ) const IDEMPOTENCY_TIMEOUT_TICKS : u8 = 7 ;
56
56
57
+ #[ cfg( async_payments) ]
58
+ /// Relative expiration in seconds to wait for a pending outbound HTLC to a often-offline
59
+ /// payee to fulfill.
60
+ const DEFAULT_ASYNC_PAYMENT_FULFILLMENT_EXPIRY_TIME : u64 = 60 * 60 * 24 * 7 ;
61
+
57
62
/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102
58
63
/// and later, also stores information for retrying the payment.
59
64
pub ( crate ) enum PendingOutboundPayment {
@@ -98,6 +103,9 @@ pub(crate) enum PendingOutboundPayment {
98
103
route_params : RouteParameters ,
99
104
invoice_request : InvoiceRequest ,
100
105
static_invoice : StaticInvoice ,
106
+ // Stale time expiration of how much time we will wait to the payment to fulfill.
107
+ // Defaults to [`DEFAULT_ASYNC_PAYMENT_FULFILLMENT_EXPIRY_TIME`].
108
+ expiry_time : StaleExpiration ,
101
109
} ,
102
110
Retryable {
103
111
retry_strategy : Option < Retry > ,
@@ -1164,6 +1172,7 @@ impl OutboundPayments {
1164
1172
abandon_with_entry ! ( entry, PaymentFailureReason :: RouteNotFound ) ;
1165
1173
return Err ( Bolt12PaymentError :: SendingFailed ( RetryableSendFailure :: OnionPacketSizeExceeded ) )
1166
1174
}
1175
+ let absolute_expiry = invoice. created_at ( ) . saturating_add ( Duration :: from_secs ( DEFAULT_ASYNC_PAYMENT_FULFILLMENT_EXPIRY_TIME ) ) ;
1167
1176
1168
1177
* entry. into_mut ( ) = PendingOutboundPayment :: StaticInvoiceReceived {
1169
1178
payment_hash,
@@ -1176,6 +1185,7 @@ impl OutboundPayments {
1176
1185
. ok_or ( Bolt12PaymentError :: UnexpectedInvoice ) ?
1177
1186
. invoice_request ,
1178
1187
static_invoice : invoice. clone ( ) ,
1188
+ expiry_time : StaleExpiration :: AbsoluteTimeout ( absolute_expiry) ,
1179
1189
} ;
1180
1190
return Ok ( ( ) )
1181
1191
} ,
@@ -2242,11 +2252,24 @@ impl OutboundPayments {
2242
2252
true
2243
2253
}
2244
2254
} ,
2245
- PendingOutboundPayment :: StaticInvoiceReceived { route_params, payment_hash, .. } => {
2246
- let is_stale =
2255
+ PendingOutboundPayment :: StaticInvoiceReceived { route_params, payment_hash, expiry_time, .. } => {
2256
+ let is_stale = match expiry_time {
2257
+ StaleExpiration :: AbsoluteTimeout ( expiration_time) => {
2258
+ * expiration_time < duration_since_epoch
2259
+ } ,
2260
+ StaleExpiration :: TimerTicks ( timer_ticks_remaining) => {
2261
+ if * timer_ticks_remaining > 0 {
2262
+ * timer_ticks_remaining -= 1 ;
2263
+ false
2264
+ } else {
2265
+ true
2266
+ }
2267
+ }
2268
+ } ;
2269
+ let is_static_invoice_stale =
2247
2270
route_params. payment_params . expiry_time . unwrap_or ( u64:: MAX ) <
2248
2271
duration_since_epoch. as_secs ( ) ;
2249
- if is_stale {
2272
+ if is_stale || is_static_invoice_stale {
2250
2273
let fail_ev = events:: Event :: PaymentFailed {
2251
2274
payment_id : * payment_id,
2252
2275
payment_hash : Some ( * payment_hash) ,
@@ -2661,6 +2684,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
2661
2684
( 6 , route_params, required) ,
2662
2685
( 8 , invoice_request, required) ,
2663
2686
( 10 , static_invoice, required) ,
2687
+ ( 12 , expiry_time, required) ,
2664
2688
} ,
2665
2689
// Added in 0.1. Prior versions will drop these outbounds on downgrade, which is safe because
2666
2690
// no HTLCs are in-flight.
@@ -3311,6 +3335,7 @@ mod tests {
3311
3335
route_params,
3312
3336
invoice_request : dummy_invoice_request ( ) ,
3313
3337
static_invoice : dummy_static_invoice ( ) ,
3338
+ expiry_time : StaleExpiration :: AbsoluteTimeout ( Duration :: from_secs ( absolute_expiry + 2 ) ) ,
3314
3339
} ;
3315
3340
outbounds. insert ( payment_id, outbound) ;
3316
3341
core:: mem:: drop ( outbounds) ;
@@ -3336,6 +3361,109 @@ mod tests {
3336
3361
} , None ) ) ;
3337
3362
}
3338
3363
3364
+ #[ test]
3365
+ #[ rustfmt:: skip]
3366
+ fn time_out_unreleased_async_payments_using_stale_absolute_time ( ) {
3367
+ let pending_events = Mutex :: new ( VecDeque :: new ( ) ) ;
3368
+ let outbound_payments = OutboundPayments :: new ( new_hash_map ( ) ) ;
3369
+ let payment_id = PaymentId ( [ 0 ; 32 ] ) ;
3370
+ let absolute_expiry = 60 ;
3371
+
3372
+ let mut outbounds = outbound_payments. pending_outbound_payments . lock ( ) . unwrap ( ) ;
3373
+ let payment_params = PaymentParameters :: from_node_id ( test_utils:: pubkey ( 42 ) , 0 )
3374
+ . with_expiry_time ( absolute_expiry) ;
3375
+ let route_params = RouteParameters {
3376
+ payment_params,
3377
+ final_value_msat : 0 ,
3378
+ max_total_routing_fee_msat : None ,
3379
+ } ;
3380
+ let payment_hash = PaymentHash ( [ 0 ; 32 ] ) ;
3381
+ let outbound = PendingOutboundPayment :: StaticInvoiceReceived {
3382
+ payment_hash,
3383
+ keysend_preimage : PaymentPreimage ( [ 0 ; 32 ] ) ,
3384
+ retry_strategy : Retry :: Attempts ( 0 ) ,
3385
+ route_params,
3386
+ invoice_request : dummy_invoice_request ( ) ,
3387
+ static_invoice : dummy_static_invoice ( ) ,
3388
+ expiry_time : StaleExpiration :: AbsoluteTimeout ( Duration :: from_secs ( absolute_expiry) ) ,
3389
+ } ;
3390
+ outbounds. insert ( payment_id, outbound) ;
3391
+ core:: mem:: drop ( outbounds) ;
3392
+
3393
+ // The payment will not be removed if it isn't expired yet.
3394
+ outbound_payments. remove_stale_payments ( Duration :: from_secs ( absolute_expiry) , & pending_events) ;
3395
+ let outbounds = outbound_payments. pending_outbound_payments . lock ( ) . unwrap ( ) ;
3396
+ assert_eq ! ( outbounds. len( ) , 1 ) ;
3397
+ let events = pending_events. lock ( ) . unwrap ( ) ;
3398
+ assert_eq ! ( events. len( ) , 0 ) ;
3399
+ core:: mem:: drop ( outbounds) ;
3400
+ core:: mem:: drop ( events) ;
3401
+
3402
+ outbound_payments. remove_stale_payments ( Duration :: from_secs ( absolute_expiry + 1 ) , & pending_events) ;
3403
+ let outbounds = outbound_payments. pending_outbound_payments . lock ( ) . unwrap ( ) ;
3404
+ assert_eq ! ( outbounds. len( ) , 0 ) ;
3405
+ let events = pending_events. lock ( ) . unwrap ( ) ;
3406
+ assert_eq ! ( events. len( ) , 1 ) ;
3407
+ assert_eq ! ( events[ 0 ] , ( Event :: PaymentFailed {
3408
+ payment_hash: Some ( payment_hash) ,
3409
+ payment_id,
3410
+ reason: Some ( PaymentFailureReason :: PaymentExpired ) ,
3411
+ } , None ) ) ;
3412
+ }
3413
+
3414
+ #[ test]
3415
+ #[ rustfmt:: skip]
3416
+ fn time_out_unreleased_async_payments_using_stale_timer_ticks ( ) {
3417
+ let pending_events = Mutex :: new ( VecDeque :: new ( ) ) ;
3418
+ let outbound_payments = OutboundPayments :: new ( new_hash_map ( ) ) ;
3419
+ let payment_id = PaymentId ( [ 0 ; 32 ] ) ;
3420
+ let absolute_expiry = 60 ;
3421
+
3422
+ let mut outbounds = outbound_payments. pending_outbound_payments . lock ( ) . unwrap ( ) ;
3423
+ let payment_params = PaymentParameters :: from_node_id ( test_utils:: pubkey ( 42 ) , 0 )
3424
+ . with_expiry_time ( absolute_expiry) ;
3425
+ let route_params = RouteParameters {
3426
+ payment_params,
3427
+ final_value_msat : 0 ,
3428
+ max_total_routing_fee_msat : None ,
3429
+ } ;
3430
+ let payment_hash = PaymentHash ( [ 0 ; 32 ] ) ;
3431
+ let timer_ticks = 1 ;
3432
+ let expiration = StaleExpiration :: TimerTicks ( timer_ticks) ;
3433
+ let outbound = PendingOutboundPayment :: StaticInvoiceReceived {
3434
+ payment_hash,
3435
+ keysend_preimage : PaymentPreimage ( [ 0 ; 32 ] ) ,
3436
+ retry_strategy : Retry :: Attempts ( 0 ) ,
3437
+ route_params,
3438
+ invoice_request : dummy_invoice_request ( ) ,
3439
+ static_invoice : dummy_static_invoice ( ) ,
3440
+ expiry_time : expiration,
3441
+ } ;
3442
+ outbounds. insert ( payment_id, outbound) ;
3443
+ core:: mem:: drop ( outbounds) ;
3444
+
3445
+ // First time should go through
3446
+ outbound_payments. remove_stale_payments ( Duration :: from_secs ( absolute_expiry) , & pending_events) ;
3447
+ let outbounds = outbound_payments. pending_outbound_payments . lock ( ) . unwrap ( ) ;
3448
+ assert_eq ! ( outbounds. len( ) , 1 ) ;
3449
+ let events = pending_events. lock ( ) . unwrap ( ) ;
3450
+ assert_eq ! ( events. len( ) , 0 ) ;
3451
+ core:: mem:: drop ( outbounds) ;
3452
+ core:: mem:: drop ( events) ;
3453
+
3454
+ // As timer ticks is 1, payment should be timed out
3455
+ outbound_payments. remove_stale_payments ( Duration :: from_secs ( absolute_expiry) , & pending_events) ;
3456
+ let outbounds = outbound_payments. pending_outbound_payments . lock ( ) . unwrap ( ) ;
3457
+ assert_eq ! ( outbounds. len( ) , 0 ) ;
3458
+ let events = pending_events. lock ( ) . unwrap ( ) ;
3459
+ assert_eq ! ( events. len( ) , 1 ) ;
3460
+ assert_eq ! ( events[ 0 ] , ( Event :: PaymentFailed {
3461
+ payment_hash: Some ( payment_hash) ,
3462
+ payment_id,
3463
+ reason: Some ( PaymentFailureReason :: PaymentExpired ) ,
3464
+ } , None ) ) ;
3465
+ }
3466
+
3339
3467
#[ test]
3340
3468
#[ rustfmt:: skip]
3341
3469
fn abandon_unreleased_async_payment ( ) {
@@ -3360,6 +3488,7 @@ mod tests {
3360
3488
route_params,
3361
3489
invoice_request : dummy_invoice_request ( ) ,
3362
3490
static_invoice : dummy_static_invoice ( ) ,
3491
+ expiry_time : StaleExpiration :: AbsoluteTimeout ( now ( ) ) ,
3363
3492
} ;
3364
3493
outbounds. insert ( payment_id, outbound) ;
3365
3494
core:: mem:: drop ( outbounds) ;
0 commit comments