Skip to content

add expiry_time to PendingOutboundPayment::StaticInvoiceReceived #3918

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 71 additions & 2 deletions lightning/src/ln/async_payments_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ use crate::ln::msgs::{
};
use crate::ln::offers_tests;
use crate::ln::onion_utils::LocalHTLCFailureReason;
use crate::ln::outbound_payment::PendingOutboundPayment;
use crate::ln::outbound_payment::Retry;
use crate::ln::outbound_payment::{
PendingOutboundPayment, Retry, TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY,
};
use crate::offers::async_receive_offer_cache::{
TEST_MAX_CACHED_OFFERS_TARGET, TEST_MAX_UPDATE_ATTEMPTS,
TEST_MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS, TEST_OFFER_REFRESH_THRESHOLD,
Expand Down Expand Up @@ -681,6 +682,74 @@ fn expired_static_invoice_fail() {
// doesn't currently provide them with a reply path to do so.
}

#[cfg_attr(feature = "std", ignore)]
#[test]
fn timeout_unreleased_payment() {
// If a server holds a pending HTLC for too long, payment is considered expired.
let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);

let sender = &nodes[0];
let server = &nodes[1];
let recipient = &nodes[2];

let recipient_id = vec![42; 32];
let inv_server_paths =
server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap();
recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap();

let static_invoice =
pass_static_invoice_server_messages(server, recipient, recipient_id.clone()).invoice;
let offer = recipient.node.get_async_receive_offer().unwrap();

let amt_msat = 5000;
let payment_id = PaymentId([1; 32]);
let params = RouteParametersConfig::default();
sender
.node
.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params)
.unwrap();

let invreq_om =
sender.onion_messenger.next_onion_message_for_peer(server.node.get_our_node_id()).unwrap();
server.onion_messenger.handle_onion_message(sender.node.get_our_node_id(), &invreq_om);

let mut events = server.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
let reply_path = match events.pop().unwrap() {
Event::StaticInvoiceRequested { reply_path, .. } => reply_path,
_ => panic!(),
};

server.node.send_static_invoice(static_invoice.clone(), reply_path).unwrap();
let static_invoice_om =
server.onion_messenger.next_onion_message_for_peer(sender.node.get_our_node_id()).unwrap();

// We handle the static invoice to held the pending HTLC
sender.onion_messenger.handle_onion_message(server.node.get_our_node_id(), &static_invoice_om);

// We advance enough time to expire the payment.
// We add 2 hours as is the margin added to remove stale payments in non-std implementation.
let timeout_time_expiry = TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY
+ Duration::from_secs(7200)
+ Duration::from_secs(1);
advance_time_by(timeout_time_expiry, sender);
sender.node.timer_tick_occurred();
let events = sender.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
Event::PaymentFailed { payment_id: ev_payment_id, reason, .. } => {
assert_eq!(reason.unwrap(), PaymentFailureReason::PaymentExpired);
assert_eq!(ev_payment_id, payment_id);
},
_ => panic!(),
}
}

#[test]
fn async_receive_mpp() {
let chanmon_cfgs = create_chanmon_cfgs(4);
Expand Down
39 changes: 36 additions & 3 deletions lightning/src/ln/outbound_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ use crate::sync::Mutex;
/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
pub(crate) const IDEMPOTENCY_TIMEOUT_TICKS: u8 = 7;

#[cfg(async_payments)]
/// The default relative expiration to wait for a pending outbound HTLC to a often-offline
/// payee to fulfill.
const ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24 * 7);

#[cfg(all(async_payments, test))]
pub(crate) const TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY: Duration =
ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY;

/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102
/// and later, also stores information for retrying the payment.
pub(crate) enum PendingOutboundPayment {
Expand Down Expand Up @@ -98,6 +107,10 @@ pub(crate) enum PendingOutboundPayment {
route_params: RouteParameters,
invoice_request: InvoiceRequest,
static_invoice: StaticInvoice,
// Stale time expiration of how much time we will wait to the payment to fulfill.
//
// Defaults to [`ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY`].
expiry_time: StaleExpiration,
},
Retryable {
retry_strategy: Option<Retry>,
Expand Down Expand Up @@ -1164,6 +1177,7 @@ impl OutboundPayments {
abandon_with_entry!(entry, PaymentFailureReason::RouteNotFound);
return Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::OnionPacketSizeExceeded))
}
let absolute_expiry = duration_since_epoch.saturating_add(ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY);

*entry.into_mut() = PendingOutboundPayment::StaticInvoiceReceived {
payment_hash,
Expand All @@ -1176,6 +1190,7 @@ impl OutboundPayments {
.ok_or(Bolt12PaymentError::UnexpectedInvoice)?
.invoice_request,
static_invoice: invoice.clone(),
expiry_time: StaleExpiration::AbsoluteTimeout(absolute_expiry),
};
return Ok(())
},
Expand Down Expand Up @@ -2242,11 +2257,24 @@ impl OutboundPayments {
true
}
},
PendingOutboundPayment::StaticInvoiceReceived { route_params, payment_hash, .. } => {
let is_stale =
PendingOutboundPayment::StaticInvoiceReceived { route_params, payment_hash, expiry_time, .. } => {
let is_stale = match expiry_time {
StaleExpiration::AbsoluteTimeout(expiration_time) => {
*expiration_time < duration_since_epoch
},
StaleExpiration::TimerTicks(timer_ticks_remaining) => {
if *timer_ticks_remaining > 0 {
*timer_ticks_remaining -= 1;
false
} else {
true
}
}
};
let is_static_invoice_stale =
route_params.payment_params.expiry_time.unwrap_or(u64::MAX) <
duration_since_epoch.as_secs();
if is_stale {
if is_stale || is_static_invoice_stale {
let fail_ev = events::Event::PaymentFailed {
payment_id: *payment_id,
payment_hash: Some(*payment_hash),
Expand Down Expand Up @@ -2661,6 +2689,9 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
(6, route_params, required),
(8, invoice_request, required),
(10, static_invoice, required),
// Added in 0.2. Prior versions would have this TLV type defaulted to 0, which is safe because
// the type is not used.
(11, expiry_time, (default_value, StaleExpiration::AbsoluteTimeout(Duration::from_secs(0)))),
},
// Added in 0.1. Prior versions will drop these outbounds on downgrade, which is safe because
// no HTLCs are in-flight.
Expand Down Expand Up @@ -3311,6 +3342,7 @@ mod tests {
route_params,
invoice_request: dummy_invoice_request(),
static_invoice: dummy_static_invoice(),
expiry_time: StaleExpiration::AbsoluteTimeout(Duration::from_secs(absolute_expiry + 2)),
};
outbounds.insert(payment_id, outbound);
core::mem::drop(outbounds);
Expand Down Expand Up @@ -3360,6 +3392,7 @@ mod tests {
route_params,
invoice_request: dummy_invoice_request(),
static_invoice: dummy_static_invoice(),
expiry_time: StaleExpiration::AbsoluteTimeout(now()),
};
outbounds.insert(payment_id, outbound);
core::mem::drop(outbounds);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## API Updates (0.2)

* Upgrading to v0.2.0 will timeout any pending async payment waiting for the often offline peer
come online.
Loading