Skip to content

Commit a9fed13

Browse files
Async receive: update static invoices when stale
Previously, for every one of our async receive offers that are in use, we would send a fresh invoice once on every timer tick/once a minute. It'd be better to check if the invoice is actually kinda-stale before updating it to avoid bombarding the server, so we do so here using the new invoice_created field added in the previous commit.
1 parent a403980 commit a9fed13

File tree

3 files changed

+73
-15
lines changed

3 files changed

+73
-15
lines changed

lightning/src/ln/async_payments_tests.rs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::ln::outbound_payment::{
2727
PendingOutboundPayment, Retry, TEST_ASYNC_PAYMENT_TIMEOUT_RELATIVE_EXPIRY,
2828
};
2929
use crate::offers::async_receive_offer_cache::{
30-
TEST_MAX_CACHED_OFFERS_TARGET, TEST_MAX_UPDATE_ATTEMPTS,
30+
TEST_INVOICE_REFRESH_THRESHOLD, TEST_MAX_CACHED_OFFERS_TARGET, TEST_MAX_UPDATE_ATTEMPTS,
3131
TEST_MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS, TEST_OFFER_REFRESH_THRESHOLD,
3232
};
3333
use crate::offers::flow::{
@@ -1680,11 +1680,53 @@ fn offer_cache_round_trip_ser() {
16801680
assert_eq!(cached_offers_pre_ser, cached_offers_post_ser);
16811681
}
16821682

1683+
#[test]
1684+
fn refresh_static_invoices_for_pending_offers() {
1685+
// Check that an invoice for an offer that is pending persistence with the server will be updated
1686+
// every timer tick.
1687+
let chanmon_cfgs = create_chanmon_cfgs(2);
1688+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1689+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None, None]);
1690+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1691+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
1692+
let server = &nodes[0];
1693+
let recipient = &nodes[1];
1694+
1695+
let recipient_id = vec![42; 32];
1696+
let inv_server_paths =
1697+
server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap();
1698+
recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap();
1699+
expect_offer_paths_requests(&nodes[1], &[&nodes[0]]);
1700+
1701+
// Set up the recipient to have one offer pending with the static invoice server.
1702+
invoice_flow_up_to_send_serve_static_invoice(server, recipient);
1703+
1704+
// Every timer tick, we'll send a fresh invoice to the server.
1705+
for _ in 0..10 {
1706+
recipient.node.timer_tick_occurred();
1707+
let pending_oms = recipient.onion_messenger.release_pending_msgs();
1708+
pending_oms
1709+
.get(&server.node.get_our_node_id())
1710+
.unwrap()
1711+
.into_iter()
1712+
.find(|msg| match server.onion_messenger.peel_onion_message(&msg).unwrap() {
1713+
PeeledOnion::AsyncPayments(AsyncPaymentsMessage::ServeStaticInvoice(_), _, _) => {
1714+
true
1715+
},
1716+
PeeledOnion::AsyncPayments(AsyncPaymentsMessage::OfferPathsRequest(_), _, _) => {
1717+
false
1718+
},
1719+
_ => panic!("Unexpected message"),
1720+
})
1721+
.unwrap();
1722+
}
1723+
}
1724+
16831725
#[cfg_attr(feature = "std", ignore)]
16841726
#[test]
1685-
fn refresh_static_invoices() {
1686-
// Check that an invoice for a particular offer stored with the server will be updated once per
1687-
// timer tick.
1727+
fn refresh_static_invoices_for_used_offers() {
1728+
// Check that an invoice for a used offer stored with the server will be updated every
1729+
// INVOICE_REFRESH_THRESHOLD.
16881730
let chanmon_cfgs = create_chanmon_cfgs(3);
16891731
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
16901732

@@ -1709,19 +1751,20 @@ fn refresh_static_invoices() {
17091751
// Set up the recipient to have one offer and an invoice with the static invoice server.
17101752
let flow_res = pass_static_invoice_server_messages(server, recipient, recipient_id.clone());
17111753
let original_invoice = flow_res.invoice;
1712-
// Mark the offer as used so we'll update the invoice on timer tick.
1754+
// Mark the offer as used so we'll update the invoice after INVOICE_REFRESH_THRESHOLD.
17131755
let _offer = recipient.node.get_async_receive_offer().unwrap();
17141756

17151757
// Force the server and recipient to send OMs directly to each other for testing simplicity.
17161758
server.message_router.peers_override.lock().unwrap().push(recipient.node.get_our_node_id());
17171759
recipient.message_router.peers_override.lock().unwrap().push(server.node.get_our_node_id());
17181760

1719-
assert!(recipient
1720-
.onion_messenger
1721-
.next_onion_message_for_peer(server.node.get_our_node_id())
1722-
.is_none());
1761+
// Prior to INVOICE_REFRESH_THRESHOLD, we won't refresh the invoice.
1762+
advance_time_by(TEST_INVOICE_REFRESH_THRESHOLD, recipient);
1763+
recipient.node.timer_tick_occurred();
1764+
expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]);
17231765

1724-
// Check that we'll refresh the invoice on the next timer tick.
1766+
// After INVOICE_REFRESH_THRESHOLD, we will refresh the invoice.
1767+
advance_time_by(Duration::from_secs(1), recipient);
17251768
recipient.node.timer_tick_occurred();
17261769
let pending_oms = recipient.onion_messenger.release_pending_msgs();
17271770
let serve_static_invoice_om = pending_oms

lightning/src/offers/async_receive_offer_cache.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ const MAX_UPDATE_ATTEMPTS: u8 = 3;
199199
#[cfg(async_payments)]
200200
const OFFER_REFRESH_THRESHOLD: Duration = Duration::from_secs(2 * 60 * 60);
201201

202+
/// Invoices stored with the static invoice server may become stale due to outdated channel and fee
203+
/// info, so they should be updated regularly.
204+
const INVOICE_REFRESH_THRESHOLD: Duration = Duration::from_secs(2 * 60 * 60);
205+
202206
// Require offer paths that we receive to last at least 3 months.
203207
#[cfg(async_payments)]
204208
const MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS: u64 = 3 * 30 * 24 * 60 * 60;
@@ -210,6 +214,8 @@ pub(crate) const TEST_MAX_UPDATE_ATTEMPTS: u8 = MAX_UPDATE_ATTEMPTS;
210214
#[cfg(all(test, async_payments))]
211215
pub(crate) const TEST_OFFER_REFRESH_THRESHOLD: Duration = OFFER_REFRESH_THRESHOLD;
212216
#[cfg(all(test, async_payments))]
217+
pub(crate) const TEST_INVOICE_REFRESH_THRESHOLD: Duration = INVOICE_REFRESH_THRESHOLD;
218+
#[cfg(all(test, async_payments))]
213219
pub(crate) const TEST_MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS: u64 =
214220
MIN_OFFER_PATHS_RELATIVE_EXPIRY_SECS;
215221

@@ -416,13 +422,21 @@ impl AsyncReceiveOfferCache {
416422
/// Returns an iterator over the list of cached offers where we need to send an updated invoice to
417423
/// the static invoice server.
418424
pub(super) fn offers_needing_invoice_refresh(
419-
&self,
425+
&self, duration_since_epoch: Duration,
420426
) -> impl Iterator<Item = (&Offer, Nonce, u16, &Responder)> {
421427
// For any offers which are either in use or pending confirmation by the server, we should send
422428
// them a fresh invoice on each timer tick.
423-
self.offers_with_idx().filter_map(|(idx, offer)| {
424-
let needs_invoice_update =
425-
matches!(offer.status, OfferStatus::Used { .. } | OfferStatus::Pending);
429+
self.offers_with_idx().filter_map(move |(idx, offer)| {
430+
let needs_invoice_update = match offer.status {
431+
OfferStatus::Used { invoice_created_at } => {
432+
invoice_created_at.saturating_add(INVOICE_REFRESH_THRESHOLD)
433+
< duration_since_epoch
434+
},
435+
OfferStatus::Pending => true,
436+
// Don't bother updating `Ready` offers' invoices on a timer because the offers themselves
437+
// are regularly rotated anyway.
438+
OfferStatus::Ready { .. } => false,
439+
};
426440
if needs_invoice_update {
427441
let offer_slot = idx.try_into().unwrap_or(u16::MAX);
428442
Some((

lightning/src/offers/flow.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1343,8 +1343,9 @@ where
13431343
{
13441344
let mut serve_static_invoice_msgs = Vec::new();
13451345
{
1346+
let duration_since_epoch = self.duration_since_epoch();
13461347
let cache = self.async_receive_offer_cache.lock().unwrap();
1347-
for offer_and_metadata in cache.offers_needing_invoice_refresh() {
1348+
for offer_and_metadata in cache.offers_needing_invoice_refresh(duration_since_epoch) {
13481349
let (offer, offer_nonce, slot_number, update_static_invoice_path) =
13491350
offer_and_metadata;
13501351

0 commit comments

Comments
 (0)