@@ -54,6 +54,11 @@ use crate::sync::Mutex;
5454/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
5555pub ( crate ) const IDEMPOTENCY_TIMEOUT_TICKS : u8 = 7 ;
5656
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+
5762/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102
5863/// and later, also stores information for retrying the payment.
5964pub ( crate ) enum PendingOutboundPayment {
@@ -98,6 +103,9 @@ pub(crate) enum PendingOutboundPayment {
98103 route_params : RouteParameters ,
99104 invoice_request : InvoiceRequest ,
100105 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 ,
101109 } ,
102110 Retryable {
103111 retry_strategy : Option < Retry > ,
@@ -1164,6 +1172,7 @@ impl OutboundPayments {
11641172 abandon_with_entry ! ( entry, PaymentFailureReason :: RouteNotFound ) ;
11651173 return Err ( Bolt12PaymentError :: SendingFailed ( RetryableSendFailure :: OnionPacketSizeExceeded ) )
11661174 }
1175+ let absolute_expiry = invoice. created_at ( ) . saturating_add ( Duration :: from_secs ( DEFAULT_ASYNC_PAYMENT_FULFILLMENT_EXPIRY_TIME ) ) ;
11671176
11681177 * entry. into_mut ( ) = PendingOutboundPayment :: StaticInvoiceReceived {
11691178 payment_hash,
@@ -1176,6 +1185,7 @@ impl OutboundPayments {
11761185 . ok_or ( Bolt12PaymentError :: UnexpectedInvoice ) ?
11771186 . invoice_request ,
11781187 static_invoice : invoice. clone ( ) ,
1188+ expiry_time : StaleExpiration :: AbsoluteTimeout ( absolute_expiry) ,
11791189 } ;
11801190 return Ok ( ( ) )
11811191 } ,
@@ -2242,11 +2252,24 @@ impl OutboundPayments {
22422252 true
22432253 }
22442254 } ,
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 =
22472270 route_params. payment_params . expiry_time . unwrap_or ( u64:: MAX ) <
22482271 duration_since_epoch. as_secs ( ) ;
2249- if is_stale {
2272+ if is_stale || is_static_invoice_stale {
22502273 let fail_ev = events:: Event :: PaymentFailed {
22512274 payment_id : * payment_id,
22522275 payment_hash : Some ( * payment_hash) ,
@@ -2661,6 +2684,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
26612684 ( 6 , route_params, required) ,
26622685 ( 8 , invoice_request, required) ,
26632686 ( 10 , static_invoice, required) ,
2687+ ( 12 , expiry_time, required) ,
26642688 } ,
26652689 // Added in 0.1. Prior versions will drop these outbounds on downgrade, which is safe because
26662690 // no HTLCs are in-flight.
@@ -3311,6 +3335,7 @@ mod tests {
33113335 route_params,
33123336 invoice_request : dummy_invoice_request ( ) ,
33133337 static_invoice : dummy_static_invoice ( ) ,
3338+ expiry_time : StaleExpiration :: AbsoluteTimeout ( Duration :: from_secs ( absolute_expiry + 2 ) ) ,
33143339 } ;
33153340 outbounds. insert ( payment_id, outbound) ;
33163341 core:: mem:: drop ( outbounds) ;
@@ -3336,6 +3361,109 @@ mod tests {
33363361 } , None ) ) ;
33373362 }
33383363
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+
33393467 #[ test]
33403468 #[ rustfmt:: skip]
33413469 fn abandon_unreleased_async_payment ( ) {
@@ -3360,6 +3488,7 @@ mod tests {
33603488 route_params,
33613489 invoice_request : dummy_invoice_request ( ) ,
33623490 static_invoice : dummy_static_invoice ( ) ,
3491+ expiry_time : StaleExpiration :: AbsoluteTimeout ( now ( ) ) ,
33633492 } ;
33643493 outbounds. insert ( payment_id, outbound) ;
33653494 core:: mem:: drop ( outbounds) ;
0 commit comments