Skip to content

Commit d32002a

Browse files
shaavancodex
andcommitted
[plumb] Thread forced static invoice refresh through flow
The cache can identify offers whose static invoices should be rebuilt immediately, but callers still need one canonical path that turns those offers into ServeStaticInvoice messages. Thread a forced-refresh entry point through OffersMessageFlow and ChannelManager while reusing the existing static invoice construction code. Accept peer and channel inputs lazily so recipients without a configured static invoice server return before collecting an unnecessary channel snapshot. This keeps the later channel-change behavior focused on deciding when to refresh, while avoiding reads of transitional channel state when no refresh can be produced. No new refresh behavior is activated in this commit. AI-assisted: planning and writing commit Co-Authored-By: OpenAI Codex <codex@openai.com>
1 parent 94d4bc3 commit d32002a

2 files changed

Lines changed: 77 additions & 29 deletions

File tree

lightning/src/ln/channelmanager.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5905,6 +5905,17 @@ impl<
59055905
}
59065906
}
59075907

5908+
fn force_refresh_async_receive_static_invoices(&self) {
5909+
let router = &self.router;
5910+
5911+
// Only collect peers and usable channels when async receiving is configured. This avoids reading
5912+
// channels during state transitions when there is no static invoice to refresh.
5913+
self.flow.force_refresh_async_receive_static_invoices(
5914+
|| (self.get_peers_for_blinded_path(), self.list_usable_channels()),
5915+
router,
5916+
);
5917+
}
5918+
59085919
#[cfg(test)]
59095920
pub(crate) fn test_check_refresh_async_receive_offers(&self) {
59105921
self.check_refresh_async_receive_offer_cache(false);

lightning/src/offers/flow.rs

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,12 +1349,31 @@ impl<MR: MessageRouter, L: Logger> OffersMessageFlow<MR, L> {
13491349
self.check_refresh_async_offers(peers.clone(), timer_tick_occurred)?;
13501350

13511351
if timer_tick_occurred {
1352-
self.check_refresh_static_invoices(peers, usable_channels, router);
1352+
self.check_refresh_static_invoices(peers, usable_channels, router, false);
13531353
}
13541354

13551355
Ok(())
13561356
}
13571357

1358+
/// Enqueues static invoice updates for cached async receive offers after local channel changes.
1359+
pub fn force_refresh_async_receive_static_invoices<R: Router, F>(
1360+
&self, get_refresh_inputs: F, router: R,
1361+
) where
1362+
F: FnOnce() -> (Vec<MessageForwardNode>, Vec<ChannelDetails>),
1363+
{
1364+
// A forced refresh is useful only for an async recipient already configured with a server.
1365+
let cache = self.async_receive_offer_cache.lock().unwrap();
1366+
if cache.paths_to_static_invoice_server().is_empty() {
1367+
return;
1368+
}
1369+
core::mem::drop(cache);
1370+
1371+
// Channel details may be in a short-lived transitional state when this refresh is requested.
1372+
// Collect them only after confirming that async receiving needs the snapshot.
1373+
let (peers, usable_channels) = get_refresh_inputs();
1374+
self.check_refresh_static_invoices(peers, usable_channels, router, true);
1375+
}
1376+
13581377
fn check_refresh_async_offers(
13591378
&self, peers: Vec<MessageForwardNode>, timer_tick_occurred: bool,
13601379
) -> Result<(), ()> {
@@ -1408,41 +1427,59 @@ impl<MR: MessageRouter, L: Logger> OffersMessageFlow<MR, L> {
14081427
/// server, based on the offers provided by the cache.
14091428
fn check_refresh_static_invoices<R: Router>(
14101429
&self, peers: Vec<MessageForwardNode>, usable_channels: Vec<ChannelDetails>, router: R,
1430+
force_refresh: bool,
14111431
) {
14121432
let mut serve_static_invoice_msgs = Vec::new();
14131433
{
14141434
let duration_since_epoch = self.duration_since_epoch();
14151435
let cache = self.async_receive_offer_cache.lock().unwrap();
1416-
for offer_and_metadata in cache.offers_needing_invoice_refresh(duration_since_epoch) {
1417-
let (offer, offer_nonce, update_static_invoice_path) = offer_and_metadata;
1418-
1419-
let (invoice, forward_invreq_path) = match self.create_static_invoice_for_server(
1420-
offer,
1421-
offer_nonce,
1422-
peers.clone(),
1423-
usable_channels.clone(),
1424-
&router,
1425-
) {
1426-
Ok((invoice, path)) => (invoice, path),
1427-
Err(()) => continue,
1428-
};
14291436

1430-
let reply_path_context = {
1431-
MessageContext::AsyncPayments(AsyncPaymentsContext::StaticInvoicePersisted {
1432-
invoice_created_at: invoice.created_at(),
1433-
offer_id: offer.id(),
1434-
})
1435-
};
1437+
// Both timer-driven and forced refreshes build the same update message. Keep the
1438+
// construction in one place so the only difference is which cached offers are selected.
1439+
macro_rules! build_refresh_message {
1440+
($offer: expr, $offer_nonce: expr, $update_static_invoice_path: expr) => {{
1441+
let (invoice, forward_invreq_path) = match self
1442+
.create_static_invoice_for_server(
1443+
$offer,
1444+
$offer_nonce,
1445+
peers.clone(),
1446+
usable_channels.clone(),
1447+
&router,
1448+
) {
1449+
Ok((invoice, path)) => (invoice, path),
1450+
Err(()) => continue,
1451+
};
1452+
1453+
let reply_path_context = MessageContext::AsyncPayments(
1454+
AsyncPaymentsContext::StaticInvoicePersisted {
1455+
invoice_created_at: invoice.created_at(),
1456+
offer_id: $offer.id(),
1457+
},
1458+
);
14361459

1437-
let serve_invoice_message = ServeStaticInvoice {
1438-
invoice,
1439-
forward_invoice_request_path: forward_invreq_path,
1440-
};
1441-
serve_static_invoice_msgs.push((
1442-
serve_invoice_message,
1443-
update_static_invoice_path.clone(),
1444-
reply_path_context,
1445-
));
1460+
let serve_invoice_message = ServeStaticInvoice {
1461+
invoice,
1462+
forward_invoice_request_path: forward_invreq_path,
1463+
};
1464+
serve_static_invoice_msgs.push((
1465+
serve_invoice_message,
1466+
$update_static_invoice_path.clone(),
1467+
reply_path_context,
1468+
));
1469+
}};
1470+
}
1471+
1472+
if force_refresh {
1473+
for offer_and_metadata in cache.offers_needing_forced_invoice_refresh() {
1474+
let (offer, offer_nonce, update_static_invoice_path) = offer_and_metadata;
1475+
build_refresh_message!(offer, offer_nonce, update_static_invoice_path);
1476+
}
1477+
} else {
1478+
for offer_and_metadata in cache.offers_needing_invoice_refresh(duration_since_epoch)
1479+
{
1480+
let (offer, offer_nonce, update_static_invoice_path) = offer_and_metadata;
1481+
build_refresh_message!(offer, offer_nonce, update_static_invoice_path);
1482+
}
14461483
}
14471484
}
14481485

0 commit comments

Comments
 (0)