Skip to content

Commit fe09ff9

Browse files
committed
Introduce Retry Invoice Flow
Description: - Add functionality to handle retrying the sending of invoice_request messages on new reply_paths that are still awaiting invoices. Changes: 1. Introduced invoice_request as an optional field in the `PendingOutboundPayments::AwaitingInvoice` variant to accommodate instances without invoice requests. 2. Refactored logic from `pay_for_offer` to create invoice request messages into a separate function for reuse with retry message flow. 3. Implemented `retry_tick_occurred` function in ChannelManager to handle generating invoice request messages for AwaitingInvoice payments and enqueueing them. 4. Added `retry_tick_occurred` to ln_background_processor with a timer duration of 5 seconds for timely retries without overwhelming the system with too many onion_messages.
1 parent bc4a5ea commit fe09ff9

File tree

4 files changed

+98
-38
lines changed

4 files changed

+98
-38
lines changed

lightning-background-processor/src/lib.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ use alloc::vec::Vec;
6464
/// * Monitoring whether the [`ChannelManager`] needs to be re-persisted to disk, and if so,
6565
/// writing it to disk/backups by invoking the callback given to it at startup.
6666
/// [`ChannelManager`] persistence should be done in the background.
67-
/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChainMonitor::rebroadcast_pending_claims`]
68-
/// and [`PeerManager::timer_tick_occurred`] at the appropriate intervals.
67+
/// * Calling [`ChannelManager::timer_tick_occurred`], [`ChannelManager::retry_tick_occurred`]
68+
/// [`ChainMonitor::rebroadcast_pending_claims`] and [`PeerManager::timer_tick_occurred`]
69+
/// at the appropriate intervals.
6970
/// * Calling [`NetworkGraph::remove_stale_channels_and_tracking`] (if a [`GossipSync`] with a
7071
/// [`NetworkGraph`] is provided to [`BackgroundProcessor::start`]).
7172
///
@@ -81,6 +82,7 @@ use alloc::vec::Vec;
8182
///
8283
/// [`ChannelManager`]: lightning::ln::channelmanager::ChannelManager
8384
/// [`ChannelManager::timer_tick_occurred`]: lightning::ln::channelmanager::ChannelManager::timer_tick_occurred
85+
/// [`ChannelManager::retry_tick_occurred`]: lightning::ln::channelmanager::ChannelManager::retry_tick_occurred
8486
/// [`ChannelMonitor`]: lightning::chain::channelmonitor::ChannelMonitor
8587
/// [`Event`]: lightning::events::Event
8688
/// [`PeerManager::timer_tick_occurred`]: lightning::ln::peer_handler::PeerManager::timer_tick_occurred
@@ -97,6 +99,11 @@ const FRESHNESS_TIMER: u64 = 60;
9799
#[cfg(test)]
98100
const FRESHNESS_TIMER: u64 = 1;
99101

102+
#[cfg(not(test))]
103+
const RETRY_TIMER: u64 = 5;
104+
#[cfg(test)]
105+
const RETRY_TIMER: u64 = 1;
106+
100107
#[cfg(all(not(test), not(debug_assertions)))]
101108
const PING_TIMER: u64 = 10;
102109
/// Signature operations take a lot longer without compiler optimisations.
@@ -134,7 +141,7 @@ const REBROADCAST_TIMER: u64 = 1;
134141
/// core::cmp::min is not currently const, so we define a trivial (and equivalent) replacement
135142
const fn min_u64(a: u64, b: u64) -> u64 { if a < b { a } else { b } }
136143
#[cfg(feature = "futures")]
137-
const FASTEST_TIMER: u64 = min_u64(min_u64(FRESHNESS_TIMER, PING_TIMER),
144+
const FASTEST_TIMER: u64 = min_u64(min_u64(RETRY_TIMER, min_u64(FRESHNESS_TIMER, PING_TIMER)),
138145
min_u64(SCORER_PERSIST_TIMER, min_u64(FIRST_NETWORK_PRUNE_TIMER, REBROADCAST_TIMER)));
139146

140147
/// Either [`P2PGossipSync`] or [`RapidGossipSync`].
@@ -291,6 +298,7 @@ macro_rules! define_run_body {
291298
$chain_monitor.rebroadcast_pending_claims();
292299

293300
let mut last_freshness_call = $get_timer(FRESHNESS_TIMER);
301+
let mut last_retry_call = $get_timer(RETRY_TIMER);
294302
let mut last_onion_message_handler_call = $get_timer(ONION_MESSAGE_HANDLER_TIMER);
295303
let mut last_ping_call = $get_timer(PING_TIMER);
296304
let mut last_prune_call = $get_timer(FIRST_NETWORK_PRUNE_TIMER);
@@ -346,6 +354,11 @@ macro_rules! define_run_body {
346354
$channel_manager.get_cm().timer_tick_occurred();
347355
last_freshness_call = $get_timer(FRESHNESS_TIMER);
348356
}
357+
if $timer_elapsed(&mut last_retry_call, RETRY_TIMER) {
358+
log_trace!($logger, "Calling ChannelManager's retry_tick_occurred");
359+
$channel_manager.get_cm().retry_tick_occurred();
360+
last_retry_call = $get_timer(RETRY_TIMER);
361+
}
349362
if $timer_elapsed(&mut last_onion_message_handler_call, ONION_MESSAGE_HANDLER_TIMER) {
350363
log_trace!($logger, "Calling OnionMessageHandler's timer_tick_occurred");
351364
$peer_manager.onion_message_handler().timer_tick_occurred();
@@ -1444,6 +1457,7 @@ mod tests {
14441457
// - `ChainMonitor::rebroadcast_pending_claims` is called every `REBROADCAST_TIMER`,
14451458
// - `PeerManager::timer_tick_occurred` is called every `PING_TIMER`, and
14461459
// - `OnionMessageHandler::timer_tick_occurred` is called every `ONION_MESSAGE_HANDLER_TIMER`.
1460+
// - `ChannelManager::retry_tick_occurred` is called every `RETRY_TIMER`.
14471461
let (_, nodes) = create_nodes(1, "test_timer_tick_called");
14481462
let data_dir = nodes[0].kv_store.get_data_dir();
14491463
let persister = Arc::new(Persister::new(data_dir));
@@ -1455,10 +1469,12 @@ mod tests {
14551469
let desired_log_2 = "Calling PeerManager's timer_tick_occurred".to_string();
14561470
let desired_log_3 = "Rebroadcasting monitor's pending claims".to_string();
14571471
let desired_log_4 = "Calling OnionMessageHandler's timer_tick_occurred".to_string();
1472+
let desired_log_5 = "Calling ChannelManager's retry_tick_occurred".to_string();
14581473
if log_entries.get(&("lightning_background_processor", desired_log_1)).is_some() &&
14591474
log_entries.get(&("lightning_background_processor", desired_log_2)).is_some() &&
14601475
log_entries.get(&("lightning_background_processor", desired_log_3)).is_some() &&
1461-
log_entries.get(&("lightning_background_processor", desired_log_4)).is_some() {
1476+
log_entries.get(&("lightning_background_processor", desired_log_4)).is_some() &&
1477+
log_entries.get(&("lightning_background_processor", desired_log_5)).is_some() {
14621478
break
14631479
}
14641480
}

lightning/src/ln/channelmanager.rs

+19-23
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ use crate::util::string::UntrustedString;
7676
use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter};
7777
use crate::util::logger::{Level, Logger, WithContext};
7878
use crate::util::errors::APIError;
79+
use super::onion_utils::construct_invoice_request_message;
80+
7981
#[cfg(not(c_bindings))]
8082
use {
8183
crate::offers::offer::DerivedMetadata,
@@ -6010,6 +6012,20 @@ where
60106012
});
60116013
}
60126014

6015+
pub fn retry_tick_occurred(&self) {
6016+
let invoice_requests = self.pending_outbound_payments.get_invoice_request_awaiting_invoice();
6017+
6018+
if invoice_requests.is_empty() { return; }
6019+
6020+
if let Ok(reply_path) = self.create_blinded_path() {
6021+
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
6022+
6023+
for invoice_request in invoice_requests {
6024+
pending_offers_messages.extend(construct_invoice_request_message(invoice_request, reply_path.clone()));
6025+
}
6026+
}
6027+
}
6028+
60136029
/// Indicates that the preimage for payment_hash is unknown or the received amount is incorrect
60146030
/// after a PaymentClaimable event, failing the HTLC back to its origin and freeing resources
60156031
/// along the path (including in our own channel on which we received it).
@@ -8623,7 +8639,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
86238639
let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
86248640
$self.pending_outbound_payments
86258641
.add_new_awaiting_invoice(
8626-
payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
8642+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat, None
86278643
)
86288644
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
86298645

@@ -8740,32 +8756,12 @@ where
87408756
let expiration = StaleExpiration::TimerTicks(1);
87418757
self.pending_outbound_payments
87428758
.add_new_awaiting_invoice(
8743-
payment_id, expiration, retry_strategy, max_total_routing_fee_msat
8759+
payment_id, expiration, retry_strategy, max_total_routing_fee_msat, Some(invoice_request)
87448760
)
87458761
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
87468762

87478763
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
8748-
if offer.paths().is_empty() {
8749-
let message = new_pending_onion_message(
8750-
OffersMessage::InvoiceRequest(invoice_request),
8751-
Destination::Node(offer.signing_pubkey()),
8752-
Some(reply_path),
8753-
);
8754-
pending_offers_messages.push(message);
8755-
} else {
8756-
// Send as many invoice requests as there are paths in the offer (with an upper bound).
8757-
// Using only one path could result in a failure if the path no longer exists. But only
8758-
// one invoice for a given payment id will be paid, even if more than one is received.
8759-
const REQUEST_LIMIT: usize = 10;
8760-
for path in offer.paths().into_iter().take(REQUEST_LIMIT) {
8761-
let message = new_pending_onion_message(
8762-
OffersMessage::InvoiceRequest(invoice_request.clone()),
8763-
Destination::BlindedPath(path.clone()),
8764-
Some(reply_path.clone()),
8765-
);
8766-
pending_offers_messages.push(message);
8767-
}
8768-
}
8764+
pending_offers_messages.extend(construct_invoice_request_message(invoice_request, reply_path));
87698765

87708766
Ok(())
87718767
}

lightning/src/ln/onion_utils.rs

+30
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
// You may not use this file except in accordance with one or both of these
88
// licenses.
99

10+
use crate::blinded_path::BlindedPath;
1011
use crate::crypto::chacha20::ChaCha20;
1112
use crate::crypto::streams::ChaChaReader;
1213
use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields};
1314
use crate::ln::msgs;
1415
use crate::ln::wire::Encode;
1516
use crate::ln::{PaymentHash, PaymentPreimage};
17+
use crate::offers::invoice_request::InvoiceRequest;
18+
use crate::onion_message::messenger::{new_pending_onion_message, Destination, PendingOnionMessage};
19+
use crate::onion_message::offers::OffersMessage;
1620
use crate::routing::gossip::NetworkUpdate;
1721
use crate::routing::router::{BlindedTail, Path, RouteHop};
1822
use crate::sign::NodeSigner;
@@ -1235,6 +1239,32 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(
12351239
}
12361240
}
12371241

1242+
pub fn construct_invoice_request_message(invoice_request: InvoiceRequest, reply_path: BlindedPath) -> Vec<PendingOnionMessage<OffersMessage>> {
1243+
let mut messages = vec![];
1244+
if invoice_request.paths().is_empty() {
1245+
let message = new_pending_onion_message(
1246+
OffersMessage::InvoiceRequest(invoice_request),
1247+
Destination::Node(invoice_request.signing_pubkey()),
1248+
Some(reply_path),
1249+
);
1250+
messages.push(message);
1251+
} else {
1252+
// Send as many invoice requests as there are paths in the offer (with an upper bound).
1253+
// Using only one path could result in a failure if the path no longer exists. But only
1254+
// one invoice for a given payment id will be paid, even if more than one is received.
1255+
const REQUEST_LIMIT: usize = 10;
1256+
for path in invoice_request.paths().into_iter().take(REQUEST_LIMIT) {
1257+
let message = new_pending_onion_message(
1258+
OffersMessage::InvoiceRequest(invoice_request.clone()),
1259+
Destination::BlindedPath(path.clone()),
1260+
Some(reply_path.clone()),
1261+
);
1262+
messages.push(message);
1263+
}
1264+
}
1265+
messages
1266+
}
1267+
12381268
#[cfg(test)]
12391269
mod tests {
12401270
use crate::io;

lightning/src/ln/outbound_payment.rs

+29-11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use bitcoin::hashes::Hash;
1313
use bitcoin::hashes::sha256::Hash as Sha256;
1414
use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
1515

16+
use crate::offers::invoice_request::InvoiceRequest;
1617
use crate::sign::{EntropySource, NodeSigner, Recipient};
1718
use crate::events::{self, PaymentFailureReason};
1819
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
@@ -50,6 +51,7 @@ pub(crate) enum PendingOutboundPayment {
5051
expiration: StaleExpiration,
5152
retry_strategy: Retry,
5253
max_total_routing_fee_msat: Option<u64>,
54+
invoice_request: Option<InvoiceRequest>,
5355
},
5456
InvoiceReceived {
5557
payment_hash: PaymentHash,
@@ -1291,7 +1293,7 @@ impl OutboundPayments {
12911293

12921294
pub(super) fn add_new_awaiting_invoice(
12931295
&self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry,
1294-
max_total_routing_fee_msat: Option<u64>
1296+
max_total_routing_fee_msat: Option<u64>, invoice_request: Option<InvoiceRequest>
12951297
) -> Result<(), ()> {
12961298
let mut pending_outbounds = self.pending_outbound_payments.lock().unwrap();
12971299
match pending_outbounds.entry(payment_id) {
@@ -1301,6 +1303,7 @@ impl OutboundPayments {
13011303
expiration,
13021304
retry_strategy,
13031305
max_total_routing_fee_msat,
1306+
invoice_request,
13041307
});
13051308

13061309
Ok(())
@@ -1766,6 +1769,20 @@ impl OutboundPayments {
17661769
pub fn clear_pending_payments(&self) {
17671770
self.pending_outbound_payments.lock().unwrap().clear()
17681771
}
1772+
1773+
pub fn get_invoice_request_awaiting_invoice(&self) -> Vec<InvoiceRequest> {
1774+
let pending_outbound_payments = self.pending_outbound_payments.lock().unwrap();
1775+
1776+
pending_outbound_payments.iter().filter_map(
1777+
|(_, payment)| {
1778+
if let PendingOutboundPayment::AwaitingInvoice { invoice_request, .. } = payment {
1779+
invoice_request.clone()
1780+
} else {
1781+
None
1782+
}
1783+
}
1784+
).collect()
1785+
}
17691786
}
17701787

17711788
/// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a
@@ -1821,6 +1838,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
18211838
(0, expiration, required),
18221839
(2, retry_strategy, required),
18231840
(4, max_total_routing_fee_msat, option),
1841+
(6, invoice_request, option),
18241842
},
18251843
(7, InvoiceReceived) => {
18261844
(0, payment_hash, required),
@@ -2058,7 +2076,7 @@ mod tests {
20582076
assert!(!outbound_payments.has_pending_payments());
20592077
assert!(
20602078
outbound_payments.add_new_awaiting_invoice(
2061-
payment_id, expiration, Retry::Attempts(0), None
2079+
payment_id, expiration, Retry::Attempts(0), None, None
20622080
).is_ok()
20632081
);
20642082
assert!(outbound_payments.has_pending_payments());
@@ -2084,14 +2102,14 @@ mod tests {
20842102

20852103
assert!(
20862104
outbound_payments.add_new_awaiting_invoice(
2087-
payment_id, expiration, Retry::Attempts(0), None
2105+
payment_id, expiration, Retry::Attempts(0), None, None
20882106
).is_ok()
20892107
);
20902108
assert!(outbound_payments.has_pending_payments());
20912109

20922110
assert!(
20932111
outbound_payments.add_new_awaiting_invoice(
2094-
payment_id, expiration, Retry::Attempts(0), None
2112+
payment_id, expiration, Retry::Attempts(0), None, None
20952113
).is_err()
20962114
);
20972115
}
@@ -2107,7 +2125,7 @@ mod tests {
21072125
assert!(!outbound_payments.has_pending_payments());
21082126
assert!(
21092127
outbound_payments.add_new_awaiting_invoice(
2110-
payment_id, expiration, Retry::Attempts(0), None
2128+
payment_id, expiration, Retry::Attempts(0), None, None
21112129
).is_ok()
21122130
);
21132131
assert!(outbound_payments.has_pending_payments());
@@ -2133,14 +2151,14 @@ mod tests {
21332151

21342152
assert!(
21352153
outbound_payments.add_new_awaiting_invoice(
2136-
payment_id, expiration, Retry::Attempts(0), None
2154+
payment_id, expiration, Retry::Attempts(0), None, None
21372155
).is_ok()
21382156
);
21392157
assert!(outbound_payments.has_pending_payments());
21402158

21412159
assert!(
21422160
outbound_payments.add_new_awaiting_invoice(
2143-
payment_id, expiration, Retry::Attempts(0), None
2161+
payment_id, expiration, Retry::Attempts(0), None, None
21442162
).is_err()
21452163
);
21462164
}
@@ -2155,7 +2173,7 @@ mod tests {
21552173
assert!(!outbound_payments.has_pending_payments());
21562174
assert!(
21572175
outbound_payments.add_new_awaiting_invoice(
2158-
payment_id, expiration, Retry::Attempts(0), None
2176+
payment_id, expiration, Retry::Attempts(0), None, None
21592177
).is_ok()
21602178
);
21612179
assert!(outbound_payments.has_pending_payments());
@@ -2188,7 +2206,7 @@ mod tests {
21882206

21892207
assert!(
21902208
outbound_payments.add_new_awaiting_invoice(
2191-
payment_id, expiration, Retry::Attempts(0), None
2209+
payment_id, expiration, Retry::Attempts(0), None, None
21922210
).is_ok()
21932211
);
21942212
assert!(outbound_payments.has_pending_payments());
@@ -2250,7 +2268,7 @@ mod tests {
22502268
assert!(
22512269
outbound_payments.add_new_awaiting_invoice(
22522270
payment_id, expiration, Retry::Attempts(0),
2253-
Some(invoice.amount_msats() / 100 + 50_000)
2271+
Some(invoice.amount_msats() / 100 + 50_000), None
22542272
).is_ok()
22552273
);
22562274
assert!(outbound_payments.has_pending_payments());
@@ -2347,7 +2365,7 @@ mod tests {
23472365

23482366
assert!(
23492367
outbound_payments.add_new_awaiting_invoice(
2350-
payment_id, expiration, Retry::Attempts(0), Some(1234)
2368+
payment_id, expiration, Retry::Attempts(0), Some(1234), None
23512369
).is_ok()
23522370
);
23532371
assert!(outbound_payments.has_pending_payments());

0 commit comments

Comments
 (0)