Skip to content

Commit 34bdf22

Browse files
committed
Absolute expiry or timer tick payment expiration
Pending outbound payments use an absolute expiry to determine when they are considered stale and should be fail. In `no-std`, this may result in long timeouts as the highest seen block time is used. Instead, allow for expiration based on timer ticks. This will be use in an upcoming commit for invoice request expiration.
1 parent ddfeb3f commit 34bdf22

File tree

2 files changed

+106
-25
lines changed

2 files changed

+106
-25
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use crate::ln::onion_utils::HTLCFailReason;
5454
use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
5555
#[cfg(test)]
5656
use crate::ln::outbound_payment;
57-
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs};
57+
use crate::ln::outbound_payment::{OutboundPayments, PaymentAttempts, PendingOutboundPayment, SendAlongPathArgs, StaleExpiration};
5858
use crate::ln::wire::Encode;
5959
use crate::offers::offer::{DerivedMetadata, OfferBuilder};
6060
use crate::offers::parse::Bolt12SemanticError;
@@ -7349,9 +7349,10 @@ where
73497349
.absolute_expiry(absolute_expiry)
73507350
.path(path);
73517351

7352+
let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
73527353
self.pending_outbound_payments
73537354
.add_new_awaiting_invoice(
7354-
payment_id, absolute_expiry, retry_strategy, max_total_routing_fee_msat,
7355+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
73557356
)
73567357
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
73577358

lightning/src/ln/outbound_payment.rs

Lines changed: 103 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub(crate) enum PendingOutboundPayment {
4747
session_privs: HashSet<[u8; 32]>,
4848
},
4949
AwaitingInvoice {
50-
absolute_expiry: Duration,
50+
expiration: StaleExpiration,
5151
retry_strategy: Retry,
5252
max_total_routing_fee_msat: Option<u64>,
5353
},
@@ -378,6 +378,22 @@ impl<T: Time> Display for PaymentAttemptsUsingTime<T> {
378378
}
379379
}
380380

381+
/// How long before a [`PendingOutboundPayment::AwaitingInvoice`] should be considered stale and
382+
/// candidate for removal in [`OutboundPayments::remove_stale_payments`].
383+
#[derive(Clone, Copy)]
384+
pub(crate) enum StaleExpiration {
385+
/// Number of times [`OutboundPayments::remove_stale_payments`] is called.
386+
TimerTicks(u64),
387+
/// Duration since the Unix epoch.
388+
AbsoluteTimeout(core::time::Duration),
389+
}
390+
391+
impl_writeable_tlv_based_enum!(StaleExpiration,
392+
;
393+
(0, TimerTicks),
394+
(2, AbsoluteTimeout)
395+
);
396+
381397
/// Indicates an immediate error on [`ChannelManager::send_payment`]. Further errors may be
382398
/// surfaced later via [`Event::PaymentPathFailed`] and [`Event::PaymentFailed`].
383399
///
@@ -1269,15 +1285,15 @@ impl OutboundPayments {
12691285
}
12701286

12711287
pub(super) fn add_new_awaiting_invoice(
1272-
&self, payment_id: PaymentId, absolute_expiry: Duration, retry_strategy: Retry,
1288+
&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
12731289
max_total_routing_fee_msat: Option<u64>
12741290
) -> Result<(), ()> {
12751291
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
12761292
match pending_outbounds.entry(payment_id) {
12771293
hash_map::Entry::Occupied(_) => Err(()),
12781294
hash_map::Entry::Vacant(entry) => {
12791295
entry.insert(PendingOutboundPayment::AwaitingInvoice {
1280-
absolute_expiry,
1296+
expiration,
12811297
retry_strategy,
12821298
max_total_routing_fee_msat,
12831299
});
@@ -1547,15 +1563,28 @@ impl OutboundPayments {
15471563
true
15481564
}
15491565
},
1550-
PendingOutboundPayment::AwaitingInvoice { absolute_expiry, .. } => {
1551-
if duration_since_epoch < *absolute_expiry {
1552-
true
1553-
} else {
1566+
PendingOutboundPayment::AwaitingInvoice { expiration, .. } => {
1567+
let is_stale = match expiration {
1568+
StaleExpiration::AbsoluteTimeout(absolute_expiry) => {
1569+
*absolute_expiry <= duration_since_epoch
1570+
},
1571+
StaleExpiration::TimerTicks(timer_ticks_remaining) => {
1572+
if *timer_ticks_remaining > 0 {
1573+
*timer_ticks_remaining -= 1;
1574+
false
1575+
} else {
1576+
true
1577+
}
1578+
},
1579+
};
1580+
if is_stale {
15541581
#[cfg(invreqfailed)]
15551582
pending_events.push_back(
15561583
(events::Event::InvoiceRequestFailed { payment_id: *payment_id }, None)
15571584
);
15581585
false
1586+
} else {
1587+
true
15591588
}
15601589
},
15611590
_ => true,
@@ -1775,7 +1804,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
17751804
(2, payment_hash, required),
17761805
},
17771806
(5, AwaitingInvoice) => {
1778-
(0, absolute_expiry, required),
1807+
(0, expiration, required),
17791808
(2, retry_strategy, required),
17801809
(4, max_total_routing_fee_msat, option),
17811810
},
@@ -1798,7 +1827,7 @@ mod tests {
17981827
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
17991828
use crate::ln::features::{ChannelFeatures, NodeFeatures};
18001829
use crate::ln::msgs::{ErrorAction, LightningError};
1801-
use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure};
1830+
use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure, StaleExpiration};
18021831
use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY;
18031832
use crate::offers::offer::OfferBuilder;
18041833
use crate::offers::test_utils::*;
@@ -2004,17 +2033,18 @@ mod tests {
20042033

20052034
#[test]
20062035
#[cfg(invreqfailed)]
2007-
fn removes_stale_awaiting_invoice() {
2036+
fn removes_stale_awaiting_invoice_using_absolute_timeout() {
20082037
let pending_events = Mutex::new(VecDeque::new());
20092038
let outbound_payments = OutboundPayments::new();
20102039
let payment_id = PaymentId([0; 32]);
20112040
let absolute_expiry = 100;
20122041
let tick_interval = 10;
2042+
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(absolute_expiry));
20132043

20142044
assert!(!outbound_payments.has_pending_payments());
20152045
assert!(
20162046
outbound_payments.add_new_awaiting_invoice(
2017-
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), None
2047+
payment_id, expiration, Retry::Attempts(0), None
20182048
).is_ok()
20192049
);
20202050
assert!(outbound_payments.has_pending_payments());
@@ -2040,14 +2070,64 @@ mod tests {
20402070

20412071
assert!(
20422072
outbound_payments.add_new_awaiting_invoice(
2043-
payment_id, Duration::from_secs(absolute_expiry + 1), Retry::Attempts(0), None
2073+
payment_id, expiration, Retry::Attempts(0), None
20442074
).is_ok()
20452075
);
20462076
assert!(outbound_payments.has_pending_payments());
20472077

20482078
assert!(
20492079
outbound_payments.add_new_awaiting_invoice(
2050-
payment_id, Duration::from_secs(absolute_expiry + 1), Retry::Attempts(0), None
2080+
payment_id, expiration, Retry::Attempts(0), None
2081+
).is_err()
2082+
);
2083+
}
2084+
2085+
#[test]
2086+
#[cfg(invreqfailed)]
2087+
fn removes_stale_awaiting_invoice_using_timer_ticks() {
2088+
let pending_events = Mutex::new(VecDeque::new());
2089+
let outbound_payments = OutboundPayments::new();
2090+
let payment_id = PaymentId([0; 32]);
2091+
let timer_ticks = 3;
2092+
let expiration = StaleExpiration::TimerTicks(timer_ticks);
2093+
2094+
assert!(!outbound_payments.has_pending_payments());
2095+
assert!(
2096+
outbound_payments.add_new_awaiting_invoice(
2097+
payment_id, expiration, Retry::Attempts(0), None
2098+
).is_ok()
2099+
);
2100+
assert!(outbound_payments.has_pending_payments());
2101+
2102+
for i in 0..timer_ticks {
2103+
let duration_since_epoch = Duration::from_secs(i * 60);
2104+
outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
2105+
2106+
assert!(outbound_payments.has_pending_payments());
2107+
assert!(pending_events.lock().unwrap().is_empty());
2108+
}
2109+
2110+
let duration_since_epoch = Duration::from_secs(timer_ticks * 60);
2111+
outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
2112+
2113+
assert!(!outbound_payments.has_pending_payments());
2114+
assert!(!pending_events.lock().unwrap().is_empty());
2115+
assert_eq!(
2116+
pending_events.lock().unwrap().pop_front(),
2117+
Some((Event::InvoiceRequestFailed { payment_id }, None)),
2118+
);
2119+
assert!(pending_events.lock().unwrap().is_empty());
2120+
2121+
assert!(
2122+
outbound_payments.add_new_awaiting_invoice(
2123+
payment_id, expiration, Retry::Attempts(0), None
2124+
).is_ok()
2125+
);
2126+
assert!(outbound_payments.has_pending_payments());
2127+
2128+
assert!(
2129+
outbound_payments.add_new_awaiting_invoice(
2130+
payment_id, expiration, Retry::Attempts(0), None
20512131
).is_err()
20522132
);
20532133
}
@@ -2058,12 +2138,12 @@ mod tests {
20582138
let pending_events = Mutex::new(VecDeque::new());
20592139
let outbound_payments = OutboundPayments::new();
20602140
let payment_id = PaymentId([0; 32]);
2061-
let absolute_expiry = 100;
2141+
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
20622142

20632143
assert!(!outbound_payments.has_pending_payments());
20642144
assert!(
20652145
outbound_payments.add_new_awaiting_invoice(
2066-
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), None
2146+
payment_id, expiration, Retry::Attempts(0), None
20672147
).is_ok()
20682148
);
20692149
assert!(outbound_payments.has_pending_payments());
@@ -2092,11 +2172,11 @@ mod tests {
20922172
let pending_events = Mutex::new(VecDeque::new());
20932173
let outbound_payments = OutboundPayments::new();
20942174
let payment_id = PaymentId([0; 32]);
2095-
let absolute_expiry = 100;
2175+
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
20962176

20972177
assert!(
20982178
outbound_payments.add_new_awaiting_invoice(
2099-
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), None
2179+
payment_id, expiration, Retry::Attempts(0), None
21002180
).is_ok()
21012181
);
21022182
assert!(outbound_payments.has_pending_payments());
@@ -2143,7 +2223,7 @@ mod tests {
21432223
let pending_events = Mutex::new(VecDeque::new());
21442224
let outbound_payments = OutboundPayments::new();
21452225
let payment_id = PaymentId([0; 32]);
2146-
let absolute_expiry = 100;
2226+
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
21472227

21482228
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
21492229
.amount_msats(1000)
@@ -2157,7 +2237,7 @@ mod tests {
21572237

21582238
assert!(
21592239
outbound_payments.add_new_awaiting_invoice(
2160-
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0),
2240+
payment_id, expiration, Retry::Attempts(0),
21612241
Some(invoice.amount_msats() / 100 + 50_000)
21622242
).is_ok()
21632243
);
@@ -2202,7 +2282,7 @@ mod tests {
22022282
let pending_events = Mutex::new(VecDeque::new());
22032283
let outbound_payments = OutboundPayments::new();
22042284
let payment_id = PaymentId([0; 32]);
2205-
let absolute_expiry = 100;
2285+
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
22062286

22072287
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
22082288
.amount_msats(1000)
@@ -2216,7 +2296,7 @@ mod tests {
22162296

22172297
assert!(
22182298
outbound_payments.add_new_awaiting_invoice(
2219-
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0),
2299+
payment_id, expiration, Retry::Attempts(0),
22202300
Some(invoice.amount_msats() / 100 + 50_000)
22212301
).is_ok()
22222302
);
@@ -2261,7 +2341,7 @@ mod tests {
22612341
let pending_events = Mutex::new(VecDeque::new());
22622342
let outbound_payments = OutboundPayments::new();
22632343
let payment_id = PaymentId([0; 32]);
2264-
let absolute_expiry = 100;
2344+
let expiration = StaleExpiration::AbsoluteTimeout(Duration::from_secs(100));
22652345

22662346
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
22672347
.amount_msats(1000)
@@ -2314,7 +2394,7 @@ mod tests {
23142394

23152395
assert!(
23162396
outbound_payments.add_new_awaiting_invoice(
2317-
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), Some(1234)
2397+
payment_id, expiration, Retry::Attempts(0), Some(1234)
23182398
).is_ok()
23192399
);
23202400
assert!(outbound_payments.has_pending_payments());

0 commit comments

Comments
 (0)