Skip to content

Commit 10b1988

Browse files
shaavancodex
andcommitted
[feat] Refresh async static invoices after channel changes
Static invoices contain blinded payment paths built from the recipient's current usable channels and counterparty forwarding parameters. These paths can become stale when channels close, complete a splice, or receive changed routing parameters. Mark those path-affecting changes as requiring a forced async static invoice refresh, then process the refresh after channel locks are released. The deferred flag avoids rebuilding invoices while holding peer or channel locks, since rebuilding needs a fresh usable-channel snapshot. Only applied ChannelUpdates for local channels trigger routing-related refreshes. This lets a newly opened channel provide its forwarding parameters before its payment path is rebuilt. Commitment feerate updates remain excluded because they do not affect blinded payment paths. AI-assisted: planning and writing commit Co-Authored-By: OpenAI Codex <codex@openai.com>
1 parent d32002a commit 10b1988

1 file changed

Lines changed: 52 additions & 18 deletions

File tree

lightning/src/ln/channelmanager.rs

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3006,6 +3006,8 @@ pub struct ChannelManager<
30063006
funding_batch_states: Mutex<BTreeMap<Txid, Vec<(ChannelId, PublicKey, bool)>>>,
30073007

30083008
background_events_processed_since_startup: AtomicBool,
3009+
/// Set when a channel change may have made cached async receive static invoices stale.
3010+
async_receive_static_invoice_refresh_pending: AtomicBool,
30093011

30103012
event_persist_notifier: Notifier,
30113013
needs_persist_flag: AtomicBool,
@@ -3766,6 +3768,7 @@ impl<
37663768
pending_background_events: Mutex::new(Vec::new()),
37673769
total_consistency_lock: RwLock::new(()),
37683770
background_events_processed_since_startup: AtomicBool::new(false),
3771+
async_receive_static_invoice_refresh_pending: AtomicBool::new(false),
37693772
event_persist_notifier: Notifier::new(),
37703773
needs_persist_flag: AtomicBool::new(false),
37713774
funding_batch_states: Mutex::new(BTreeMap::new()),
@@ -4564,6 +4567,8 @@ impl<
45644567
));
45654568
}
45664569
}
4570+
self.mark_async_receive_static_invoice_refresh_pending();
4571+
45674572
for (err, counterparty_node_id) in shutdown_results.drain(..) {
45684573
let _ = self.handle_error(err, counterparty_node_id);
45694574
}
@@ -4693,6 +4698,7 @@ impl<
46934698
log_error!(logger, "Closing channel: {}", err_internal.err.err);
46944699

46954700
self.finish_close_channel(shutdown_res);
4701+
self.process_pending_async_receive_static_invoice_refresh();
46964702
if let Some((update, node_id_1, node_id_2)) = update_option {
46974703
let mut pending_broadcast_messages =
46984704
self.pending_broadcast_messages.lock().unwrap();
@@ -5916,6 +5922,19 @@ impl<
59165922
);
59175923
}
59185924

5925+
fn mark_async_receive_static_invoice_refresh_pending(&self) {
5926+
self.async_receive_static_invoice_refresh_pending.store(true, Ordering::Release);
5927+
}
5928+
5929+
fn process_pending_async_receive_static_invoice_refresh(&self) {
5930+
// Channel state transitions often happen while a peer's channel lock is held. Defer the
5931+
// actual refresh until after those locks are released, because rebuilding static invoices
5932+
// needs a fresh snapshot of usable channels.
5933+
if self.async_receive_static_invoice_refresh_pending.swap(false, Ordering::AcqRel) {
5934+
self.force_refresh_async_receive_static_invoices();
5935+
}
5936+
}
5937+
59195938
#[cfg(test)]
59205939
pub(crate) fn test_check_refresh_async_receive_offers(&self) {
59215940
self.check_refresh_async_receive_offer_cache(false);
@@ -9141,6 +9160,7 @@ impl<
91419160
.remove_stale_payments(duration_since_epoch, &self.pending_events);
91429161

91439162
self.check_refresh_async_receive_offer_cache(true);
9163+
self.process_pending_async_receive_static_invoice_refresh();
91449164

91459165
if self.check_free_holding_cells() {
91469166
// While we try to ensure we clear holding cells immediately, its possible we miss
@@ -13761,6 +13781,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1376113781
},
1376213782
None,
1376313783
));
13784+
self.mark_async_receive_static_invoice_refresh_pending();
1376413785
splice_promotion.discarded_funding.into_iter().for_each(|funding_info| {
1376513786
let event = Event::DiscardFunding {
1376613787
channel_id: chan.context.channel_id(),
@@ -16464,6 +16485,7 @@ impl<
1646416485
funding_txo: Some(funding_txo.into_bitcoin_outpoint()),
1646516486
channel_type: funded_channel.funding.get_channel_type().clone(),
1646616487
}, None));
16488+
self.mark_async_receive_static_invoice_refresh_pending();
1646716489
discarded_funding.into_iter().for_each(|funding_info| {
1646816490
let event = Event::DiscardFunding {
1646916491
channel_id: funded_channel.context.channel_id(),
@@ -16884,16 +16906,19 @@ impl<
1688416906

1688516907
#[rustfmt::skip]
1688616908
fn handle_splice_locked(&self, counterparty_node_id: PublicKey, msg: &msgs::SpliceLocked) {
16887-
let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
16888-
let res = self.internal_splice_locked(&counterparty_node_id, msg);
16889-
let persist = match &res {
16890-
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
16891-
Err(_) => NotifyOption::SkipPersistHandleEvents,
16892-
Ok(()) => NotifyOption::DoPersist,
16893-
};
16894-
let _ = self.handle_error(res, counterparty_node_id);
16895-
persist
16896-
});
16909+
{
16910+
let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || {
16911+
let res = self.internal_splice_locked(&counterparty_node_id, msg);
16912+
let persist = match &res {
16913+
Err(e) if e.closes_channel() => NotifyOption::DoPersist,
16914+
Err(_) => NotifyOption::SkipPersistHandleEvents,
16915+
Ok(()) => NotifyOption::DoPersist,
16916+
};
16917+
let _ = self.handle_error(res, counterparty_node_id);
16918+
persist
16919+
});
16920+
}
16921+
self.process_pending_async_receive_static_invoice_refresh();
1689716922
}
1689816923

1689916924
fn handle_shutdown(&self, counterparty_node_id: PublicKey, msg: &msgs::Shutdown) {
@@ -17028,14 +17053,22 @@ impl<
1702817053
}
1702917054

1703017055
fn handle_channel_update(&self, counterparty_node_id: PublicKey, msg: &msgs::ChannelUpdate) {
17031-
PersistenceNotifierGuard::optionally_notify(self, || {
17032-
let res = self.internal_channel_update(&counterparty_node_id, msg);
17033-
if let Ok(persist) = self.handle_error(res, counterparty_node_id) {
17034-
persist
17035-
} else {
17036-
NotifyOption::DoPersist
17037-
}
17038-
});
17056+
{
17057+
PersistenceNotifierGuard::optionally_notify(self, || {
17058+
let res = self.internal_channel_update(&counterparty_node_id, msg);
17059+
if let Ok(persist) = self.handle_error(res, counterparty_node_id) {
17060+
if persist == NotifyOption::DoPersist {
17061+
// Static invoices encode the counterparty's forwarding parameters. Refresh
17062+
// them when an update changes those parameters for a local channel.
17063+
self.mark_async_receive_static_invoice_refresh_pending();
17064+
}
17065+
persist
17066+
} else {
17067+
NotifyOption::DoPersist
17068+
}
17069+
});
17070+
}
17071+
self.process_pending_async_receive_static_invoice_refresh();
1703917072
}
1704017073

1704117074
fn handle_channel_reestablish(
@@ -20524,6 +20557,7 @@ impl<
2052420557
pending_background_events: Mutex::new(pending_background_events),
2052520558
total_consistency_lock: RwLock::new(()),
2052620559
background_events_processed_since_startup: AtomicBool::new(false),
20560+
async_receive_static_invoice_refresh_pending: AtomicBool::new(false),
2052720561

2052820562
event_persist_notifier: Notifier::new(),
2052920563
needs_persist_flag: AtomicBool::new(false),

0 commit comments

Comments
 (0)