Skip to content

Commit 9b282a4

Browse files
committed
add expiry_time to PendingOutboundPayment::StaticInvoiceReceived
Previous this commit if the StaticInvoice has an expiration time of months or years would make our HTLC to hold for that time until is abandoned. This patch adds a defaults of 1 week of expiry time that the HTLC will be held waiting for the often-offline node comes online.
1 parent 6771d84 commit 9b282a4

File tree

1 file changed

+132
-3
lines changed

1 file changed

+132
-3
lines changed

lightning/src/ln/outbound_payment.rs

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ use crate::sync::Mutex;
5454
/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
5555
pub(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.
5964
pub(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

Comments
 (0)