Skip to content

Commit 11ceee8

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 eae2bb1 commit 11ceee8

File tree

3 files changed

+100
-5
lines changed

3 files changed

+100
-5
lines changed

lightning/src/ln/async_payments_tests.rs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ use crate::ln::msgs::{
2323
};
2424
use crate::ln::offers_tests;
2525
use crate::ln::onion_utils::LocalHTLCFailureReason;
26-
use crate::ln::outbound_payment::PendingOutboundPayment;
27-
use crate::ln::outbound_payment::Retry;
26+
use crate::ln::outbound_payment::{
27+
PendingOutboundPayment, Retry, TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY,
28+
};
2829
use crate::offers::async_receive_offer_cache::{
2930
TEST_MAX_CACHED_OFFERS_TARGET, TEST_MAX_UPDATE_ATTEMPTS,
3031
TEST_MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS, TEST_OFFER_REFRESH_THRESHOLD,
@@ -681,6 +682,74 @@ fn expired_static_invoice_fail() {
681682
// doesn't currently provide them with a reply path to do so.
682683
}
683684

685+
#[cfg_attr(feature = "std", ignore)]
686+
#[test]
687+
fn timeout_unreleased_payment() {
688+
// If a server holds a pending HTLC for too long, payment is considered expired.
689+
let chanmon_cfgs = create_chanmon_cfgs(3);
690+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
691+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
692+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
693+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
694+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
695+
696+
let sender = &nodes[0];
697+
let server = &nodes[1];
698+
let recipient = &nodes[2];
699+
700+
let recipient_id = vec![42; 32];
701+
let inv_server_paths =
702+
server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap();
703+
recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap();
704+
705+
let static_invoice =
706+
pass_static_invoice_server_messages(server, recipient, recipient_id.clone()).invoice;
707+
let offer = recipient.node.get_async_receive_offer().unwrap();
708+
709+
let amt_msat = 5000;
710+
let payment_id = PaymentId([1; 32]);
711+
let params = RouteParametersConfig::default();
712+
sender
713+
.node
714+
.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params)
715+
.unwrap();
716+
717+
let invreq_om =
718+
sender.onion_messenger.next_onion_message_for_peer(server.node.get_our_node_id()).unwrap();
719+
server.onion_messenger.handle_onion_message(sender.node.get_our_node_id(), &invreq_om);
720+
721+
let mut events = server.node.get_and_clear_pending_events();
722+
assert_eq!(events.len(), 1);
723+
let reply_path = match events.pop().unwrap() {
724+
Event::StaticInvoiceRequested { reply_path, .. } => reply_path,
725+
_ => panic!(),
726+
};
727+
728+
server.node.send_static_invoice(static_invoice.clone(), reply_path).unwrap();
729+
let static_invoice_om =
730+
server.onion_messenger.next_onion_message_for_peer(sender.node.get_our_node_id()).unwrap();
731+
732+
// We handle the static invoice to held the pending HTLC
733+
sender.onion_messenger.handle_onion_message(server.node.get_our_node_id(), &static_invoice_om);
734+
735+
// We advance enough time to expire the payment.
736+
// We add 2 hours as is the margin added to remove stale payments in non-std implementation.
737+
let timeout_time_expiry = TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY
738+
+ Duration::from_secs(7200)
739+
+ Duration::from_secs(1);
740+
advance_time_by(timeout_time_expiry, sender);
741+
sender.node.timer_tick_occurred();
742+
let events = sender.node.get_and_clear_pending_events();
743+
assert_eq!(events.len(), 1);
744+
match events[0] {
745+
Event::PaymentFailed { payment_id: ev_payment_id, reason, .. } => {
746+
assert_eq!(reason.unwrap(), PaymentFailureReason::PaymentExpired);
747+
assert_eq!(ev_payment_id, payment_id);
748+
},
749+
_ => panic!(),
750+
}
751+
}
752+
684753
#[test]
685754
fn async_receive_mpp() {
686755
let chanmon_cfgs = create_chanmon_cfgs(4);

lightning/src/ln/outbound_payment.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ 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+
/// The default relative expiration to wait for a pending outbound HTLC to a often-offline
59+
/// payee to fulfill.
60+
const ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24 * 7);
61+
62+
#[cfg(all(async_payments, test))]
63+
pub(crate) const TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY: Duration =
64+
ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY;
65+
5766
/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102
5867
/// and later, also stores information for retrying the payment.
5968
pub(crate) enum PendingOutboundPayment {
@@ -98,6 +107,11 @@ pub(crate) enum PendingOutboundPayment {
98107
route_params: RouteParameters,
99108
invoice_request: InvoiceRequest,
100109
static_invoice: StaticInvoice,
110+
// The deadline as duration since the Unix epoch for the async recipient to come online,
111+
// after which we'll fail the payment.
112+
//
113+
// Defaults to [`ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY`].
114+
expiry_time: Duration,
101115
},
102116
Retryable {
103117
retry_strategy: Option<Retry>,
@@ -1164,6 +1178,7 @@ impl OutboundPayments {
11641178
abandon_with_entry!(entry, PaymentFailureReason::RouteNotFound);
11651179
return Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::OnionPacketSizeExceeded))
11661180
}
1181+
let absolute_expiry = duration_since_epoch.saturating_add(ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY);
11671182

11681183
*entry.into_mut() = PendingOutboundPayment::StaticInvoiceReceived {
11691184
payment_hash,
@@ -1176,6 +1191,7 @@ impl OutboundPayments {
11761191
.ok_or(Bolt12PaymentError::UnexpectedInvoice)?
11771192
.invoice_request,
11781193
static_invoice: invoice.clone(),
1194+
expiry_time: absolute_expiry,
11791195
};
11801196
return Ok(())
11811197
},
@@ -2278,11 +2294,12 @@ impl OutboundPayments {
22782294
true
22792295
}
22802296
},
2281-
PendingOutboundPayment::StaticInvoiceReceived { route_params, payment_hash, .. } => {
2282-
let is_stale =
2297+
PendingOutboundPayment::StaticInvoiceReceived { route_params, payment_hash, expiry_time, .. } => {
2298+
let is_stale = *expiry_time < duration_since_epoch;
2299+
let is_static_invoice_stale =
22832300
route_params.payment_params.expiry_time.unwrap_or(u64::MAX) <
22842301
duration_since_epoch.as_secs();
2285-
if is_stale {
2302+
if is_stale || is_static_invoice_stale {
22862303
let fail_ev = events::Event::PaymentFailed {
22872304
payment_id: *payment_id,
22882305
payment_hash: Some(*payment_hash),
@@ -2697,6 +2714,9 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
26972714
(6, route_params, required),
26982715
(8, invoice_request, required),
26992716
(10, static_invoice, required),
2717+
// Added in 0.2. Prior versions would have this TLV type defaulted to 0, which is safe because
2718+
// the type is not used.
2719+
(11, expiry_time, (default_value, Duration::from_secs(0))),
27002720
},
27012721
// Added in 0.1. Prior versions will drop these outbounds on downgrade, which is safe because
27022722
// no HTLCs are in-flight.
@@ -3347,6 +3367,7 @@ mod tests {
33473367
route_params,
33483368
invoice_request: dummy_invoice_request(),
33493369
static_invoice: dummy_static_invoice(),
3370+
expiry_time: Duration::from_secs(absolute_expiry + 2),
33503371
};
33513372
outbounds.insert(payment_id, outbound);
33523373
core::mem::drop(outbounds);
@@ -3396,6 +3417,7 @@ mod tests {
33963417
route_params,
33973418
invoice_request: dummy_invoice_request(),
33983419
static_invoice: dummy_static_invoice(),
3420+
expiry_time: now(),
33993421
};
34003422
outbounds.insert(payment_id, outbound);
34013423
core::mem::drop(outbounds);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## API Updates (0.2)
2+
3+
* Upgrading to v0.2.0 will timeout any pending async payment waiting for the often offline peer
4+
come online.

0 commit comments

Comments
 (0)