Skip to content

Commit 2bc67a3

Browse files
committed
Await for invoices using an absolute expiry
PendingOutboundPayment::AwaitingInvoice counts the number of timer ticks that have passed awaiting a Bolt12Invoice for an InvoiceRequest. When a constant INVOICE_REQUEST_TIMEOUT_TICKS has passed, the payment is forgotten. However, this mechanism is insufficient for the Refund scenario, where the Refund's expiration should be used instead. Change AwaitingInvoice to store an absolute expiry instead. When removing stale payments, pass the `SystemTime` in `std` and the highest block time minus two hours in `no-std`.
1 parent b485c21 commit 2bc67a3

File tree

2 files changed

+76
-37
lines changed

2 files changed

+76
-37
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4791,6 +4791,10 @@ where
47914791
/// with the current [`ChannelConfig`].
47924792
/// * Removing peers which have disconnected but and no longer have any channels.
47934793
/// * Force-closing and removing channels which have not completed establishment in a timely manner.
4794+
/// * Forgetting about stale outbound payment, either those that have already been fulfilled or
4795+
/// those awaiting an invoice that hasn't been delivered in the necessary amount of time. The
4796+
/// latter is determined using the system clock in `std` and the block time minus two hours
4797+
/// in `no-std`.
47944798
///
47954799
/// Note that this may cause reentrancy through [`chain::Watch::update_channel`] calls or feerate
47964800
/// estimate fetches.
@@ -5019,7 +5023,18 @@ where
50195023
self.finish_close_channel(shutdown_res);
50205024
}
50215025

5022-
self.pending_outbound_payments.remove_stale_payments(&self.pending_events);
5026+
#[cfg(feature = "std")]
5027+
let duration_since_epoch = std::time::SystemTime::now()
5028+
.duration_since(std::time::SystemTime::UNIX_EPOCH)
5029+
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
5030+
#[cfg(not(feature = "std"))]
5031+
let duration_since_epoch = Duration::from_secs(
5032+
self.highest_seen_timestamp.load(Ordering::Acquire).saturating_sub(7200) as u64
5033+
);
5034+
5035+
self.pending_outbound_payments.remove_stale_payments(
5036+
duration_since_epoch, &self.pending_events
5037+
);
50235038

50245039
// Technically we don't need to do this here, but if we have holding cell entries in a
50255040
// channel that need freeing, it's better to do that here and block a background task

lightning/src/ln/outbound_payment.rs

Lines changed: 60 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use crate::util::ser::ReadableArgs;
2929

3030
use core::fmt::{self, Display, Formatter};
3131
use core::ops::Deref;
32+
use core::time::Duration;
3233

3334
use crate::prelude::*;
3435
use crate::sync::Mutex;
@@ -39,20 +40,14 @@ use crate::sync::Mutex;
3940
/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
4041
pub(crate) const IDEMPOTENCY_TIMEOUT_TICKS: u8 = 7;
4142

42-
/// The number of ticks of [`ChannelManager::timer_tick_occurred`] until an invoice request without
43-
/// a response is timed out.
44-
///
45-
/// [`ChannelManager::timer_tick_occurred`]: crate::ln::channelmanager::ChannelManager::timer_tick_occurred
46-
const INVOICE_REQUEST_TIMEOUT_TICKS: u8 = 3;
47-
4843
/// Stores the session_priv for each part of a payment that is still pending. For versions 0.0.102
4944
/// and later, also stores information for retrying the payment.
5045
pub(crate) enum PendingOutboundPayment {
5146
Legacy {
5247
session_privs: HashSet<[u8; 32]>,
5348
},
5449
AwaitingInvoice {
55-
timer_ticks_without_response: u8,
50+
absolute_expiry: Duration,
5651
retry_strategy: Retry,
5752
max_total_routing_fee_msat: Option<u64>,
5853
},
@@ -1274,15 +1269,16 @@ impl OutboundPayments {
12741269
}
12751270

12761271
#[allow(unused)]
1277-
pub(super) fn add_new_awaiting_invoice(
1278-
&self, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
1272+
fn add_new_awaiting_invoice(
1273+
&self, payment_id: PaymentId, absolute_expiry: Duration, retry_strategy: Retry,
1274+
max_total_routing_fee_msat: Option<u64>
12791275
) -> Result<(), ()> {
12801276
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
12811277
match pending_outbounds.entry(payment_id) {
12821278
hash_map::Entry::Occupied(_) => Err(()),
12831279
hash_map::Entry::Vacant(entry) => {
12841280
entry.insert(PendingOutboundPayment::AwaitingInvoice {
1285-
timer_ticks_without_response: 0,
1281+
absolute_expiry,
12861282
retry_strategy,
12871283
max_total_routing_fee_msat,
12881284
});
@@ -1511,22 +1507,23 @@ impl OutboundPayments {
15111507
}
15121508

15131509
pub(super) fn remove_stale_payments(
1514-
&self, pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
1510+
&self, duration_since_epoch: Duration,
1511+
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>)
15151512
{
15161513
let mut pending_outbound_payments = self.pending_outbound_payments.lock().unwrap();
15171514
#[cfg(not(invreqfailed))]
15181515
let pending_events = pending_events.lock().unwrap();
15191516
#[cfg(invreqfailed)]
15201517
let mut pending_events = pending_events.lock().unwrap();
1521-
pending_outbound_payments.retain(|payment_id, payment| {
1518+
pending_outbound_payments.retain(|payment_id, payment| match payment {
15221519
// If an outbound payment was completed, and no pending HTLCs remain, we should remove it
15231520
// from the map. However, if we did that immediately when the last payment HTLC is claimed,
15241521
// this could race the user making a duplicate send_payment call and our idempotency
15251522
// guarantees would be violated. Instead, we wait a few timer ticks to do the actual
15261523
// removal. This should be more than sufficient to ensure the idempotency of any
15271524
// `send_payment` calls that were made at the same time the `PaymentSent` event was being
15281525
// processed.
1529-
if let PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } = payment {
1526+
PendingOutboundPayment::Fulfilled { session_privs, timer_ticks_without_htlcs, .. } => {
15301527
let mut no_remaining_entries = session_privs.is_empty();
15311528
if no_remaining_entries {
15321529
for (ev, _) in pending_events.iter() {
@@ -1550,9 +1547,9 @@ impl OutboundPayments {
15501547
*timer_ticks_without_htlcs = 0;
15511548
true
15521549
}
1553-
} else if let PendingOutboundPayment::AwaitingInvoice { timer_ticks_without_response, .. } = payment {
1554-
*timer_ticks_without_response += 1;
1555-
if *timer_ticks_without_response <= INVOICE_REQUEST_TIMEOUT_TICKS {
1550+
},
1551+
PendingOutboundPayment::AwaitingInvoice { absolute_expiry, .. } => {
1552+
if duration_since_epoch < *absolute_expiry {
15561553
true
15571554
} else {
15581555
#[cfg(invreqfailed)]
@@ -1561,7 +1558,8 @@ impl OutboundPayments {
15611558
);
15621559
false
15631560
}
1564-
} else { true }
1561+
},
1562+
_ => true,
15651563
});
15661564
}
15671565

@@ -1778,7 +1776,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
17781776
(2, payment_hash, required),
17791777
},
17801778
(5, AwaitingInvoice) => {
1781-
(0, timer_ticks_without_response, required),
1779+
(0, absolute_expiry, required),
17821780
(2, retry_strategy, required),
17831781
(4, max_total_routing_fee_msat, option),
17841782
},
@@ -1794,14 +1792,14 @@ mod tests {
17941792
use bitcoin::network::constants::Network;
17951793
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
17961794

1795+
use core::time::Duration;
1796+
17971797
use crate::events::{Event, PathFailure, PaymentFailureReason};
17981798
use crate::ln::PaymentHash;
17991799
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
18001800
use crate::ln::features::{ChannelFeatures, NodeFeatures};
18011801
use crate::ln::msgs::{ErrorAction, LightningError};
18021802
use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure};
1803-
#[cfg(invreqfailed)]
1804-
use crate::ln::outbound_payment::INVOICE_REQUEST_TIMEOUT_TICKS;
18051803
use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY;
18061804
use crate::offers::offer::OfferBuilder;
18071805
use crate::offers::test_utils::*;
@@ -2011,20 +2009,28 @@ mod tests {
20112009
let pending_events = Mutex::new(VecDeque::new());
20122010
let outbound_payments = OutboundPayments::new();
20132011
let payment_id = PaymentId([0; 32]);
2012+
let absolute_expiry = 100;
2013+
let tick_interval = 10;
20142014

20152015
assert!(!outbound_payments.has_pending_payments());
20162016
assert!(
2017-
outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
2017+
outbound_payments.add_new_awaiting_invoice(
2018+
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), None
2019+
).is_ok()
20182020
);
20192021
assert!(outbound_payments.has_pending_payments());
20202022

2021-
for _ in 0..INVOICE_REQUEST_TIMEOUT_TICKS {
2022-
outbound_payments.remove_stale_payments(&pending_events);
2023+
for seconds_since_epoch in (0..absolute_expiry).step_by(tick_interval) {
2024+
let duration_since_epoch = Duration::from_secs(seconds_since_epoch);
2025+
outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
2026+
20232027
assert!(outbound_payments.has_pending_payments());
20242028
assert!(pending_events.lock().unwrap().is_empty());
20252029
}
20262030

2027-
outbound_payments.remove_stale_payments(&pending_events);
2031+
let duration_since_epoch = Duration::from_secs(absolute_expiry);
2032+
outbound_payments.remove_stale_payments(duration_since_epoch, &pending_events);
2033+
20282034
assert!(!outbound_payments.has_pending_payments());
20292035
assert!(!pending_events.lock().unwrap().is_empty());
20302036
assert_eq!(
@@ -2034,13 +2040,16 @@ mod tests {
20342040
assert!(pending_events.lock().unwrap().is_empty());
20352041

20362042
assert!(
2037-
outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
2043+
outbound_payments.add_new_awaiting_invoice(
2044+
payment_id, Duration::from_secs(absolute_expiry + 1), Retry::Attempts(0), None
2045+
).is_ok()
20382046
);
20392047
assert!(outbound_payments.has_pending_payments());
20402048

20412049
assert!(
2042-
outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None)
2043-
.is_err()
2050+
outbound_payments.add_new_awaiting_invoice(
2051+
payment_id, Duration::from_secs(absolute_expiry + 1), Retry::Attempts(0), None
2052+
).is_err()
20442053
);
20452054
}
20462055

@@ -2050,10 +2059,13 @@ mod tests {
20502059
let pending_events = Mutex::new(VecDeque::new());
20512060
let outbound_payments = OutboundPayments::new();
20522061
let payment_id = PaymentId([0; 32]);
2062+
let absolute_expiry = 100;
20532063

20542064
assert!(!outbound_payments.has_pending_payments());
20552065
assert!(
2056-
outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
2066+
outbound_payments.add_new_awaiting_invoice(
2067+
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), None
2068+
).is_ok()
20572069
);
20582070
assert!(outbound_payments.has_pending_payments());
20592071

@@ -2081,9 +2093,12 @@ mod tests {
20812093
let pending_events = Mutex::new(VecDeque::new());
20822094
let outbound_payments = OutboundPayments::new();
20832095
let payment_id = PaymentId([0; 32]);
2096+
let absolute_expiry = 100;
20842097

20852098
assert!(
2086-
outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), None).is_ok()
2099+
outbound_payments.add_new_awaiting_invoice(
2100+
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), None
2101+
).is_ok()
20872102
);
20882103
assert!(outbound_payments.has_pending_payments());
20892104

@@ -2129,6 +2144,7 @@ mod tests {
21292144
let pending_events = Mutex::new(VecDeque::new());
21302145
let outbound_payments = OutboundPayments::new();
21312146
let payment_id = PaymentId([0; 32]);
2147+
let absolute_expiry = 100;
21322148

21332149
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
21342150
.amount_msats(1000)
@@ -2140,9 +2156,11 @@ mod tests {
21402156
.build().unwrap()
21412157
.sign(recipient_sign).unwrap();
21422158

2143-
assert!(outbound_payments.add_new_awaiting_invoice(
2144-
payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000))
2145-
.is_ok()
2159+
assert!(
2160+
outbound_payments.add_new_awaiting_invoice(
2161+
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0),
2162+
Some(invoice.amount_msats() / 100 + 50_000)
2163+
).is_ok()
21462164
);
21472165
assert!(outbound_payments.has_pending_payments());
21482166

@@ -2185,6 +2203,7 @@ mod tests {
21852203
let pending_events = Mutex::new(VecDeque::new());
21862204
let outbound_payments = OutboundPayments::new();
21872205
let payment_id = PaymentId([0; 32]);
2206+
let absolute_expiry = 100;
21882207

21892208
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
21902209
.amount_msats(1000)
@@ -2196,9 +2215,11 @@ mod tests {
21962215
.build().unwrap()
21972216
.sign(recipient_sign).unwrap();
21982217

2199-
assert!(outbound_payments.add_new_awaiting_invoice(
2200-
payment_id, Retry::Attempts(0), Some(invoice.amount_msats() / 100 + 50_000))
2201-
.is_ok()
2218+
assert!(
2219+
outbound_payments.add_new_awaiting_invoice(
2220+
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0),
2221+
Some(invoice.amount_msats() / 100 + 50_000)
2222+
).is_ok()
22022223
);
22032224
assert!(outbound_payments.has_pending_payments());
22042225

@@ -2241,6 +2262,7 @@ mod tests {
22412262
let pending_events = Mutex::new(VecDeque::new());
22422263
let outbound_payments = OutboundPayments::new();
22432264
let payment_id = PaymentId([0; 32]);
2265+
let absolute_expiry = 100;
22442266

22452267
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
22462268
.amount_msats(1000)
@@ -2292,7 +2314,9 @@ mod tests {
22922314
assert!(pending_events.lock().unwrap().is_empty());
22932315

22942316
assert!(
2295-
outbound_payments.add_new_awaiting_invoice(payment_id, Retry::Attempts(0), Some(1234)).is_ok()
2317+
outbound_payments.add_new_awaiting_invoice(
2318+
payment_id, Duration::from_secs(absolute_expiry), Retry::Attempts(0), Some(1234)
2319+
).is_ok()
22962320
);
22972321
assert!(outbound_payments.has_pending_payments());
22982322

0 commit comments

Comments
 (0)