Refresh async static invoices after channel changes#4753
Conversation
|
👋 Thanks for assigning @jkczyz as a reviewer! |
SummaryOne new issue found in this re-review pass (prior comments on timer double-send and local fee-change rate-limiting still stand):
Cross-cutting note (not re-posted): the establishment channel-open path ( |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4753 +/- ##
==========================================
+ Coverage 84.55% 86.95% +2.39%
==========================================
Files 137 161 +24
Lines 77617 111684 +34067
Branches 77617 111684 +34067
==========================================
+ Hits 65627 97111 +31484
- Misses 9948 12068 +2120
- Partials 2042 2505 +463
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Async receive offers currently decide static invoice refreshes from timer-based freshness only. Channel changes need a separate selector so callers can rebuild server-side invoices without waiting for the normal age threshold. Add a cache helper that returns used and pending offers for forced invoice refresh. Ready offers stay on the existing offer rotation path because they have not been returned to the application yet. AI-assisted: planning and writing commit Co-Authored-By: OpenAI Codex <codex@openai.com>
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>
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>
Used async receive offers may already be published, so changes to their payment paths must update server-side static invoices without waiting for the normal age threshold. Cover channel-opening and counterparty forwarding-update flows. Verify that a newly usable channel adds another payment path and that a changed forwarding fee is encoded in the replacement invoice. Both flows confirm that the server receives the replacement invoice for the same offer slot. Ignore concurrent OfferPathsRequest messages so assertions remain focused on ServeStaticInvoice updates. AI-assisted: planning and writing commit Co-Authored-By: OpenAI Codex <codex@openai.com>
|
Updated .01 → .02
|
| if persist == NotifyOption::DoPersist { | ||
| // Static invoices encode the counterparty's forwarding parameters. Refresh | ||
| // them when an update changes those parameters for a local channel. | ||
| self.mark_async_receive_static_invoice_refresh_pending(); |
There was a problem hiding this comment.
This forced-refresh trigger is remote-peer-controlled, which makes it a stronger amplification concern than the local fee-change trigger. internal_channel_update returns DoPersist whenever the counterparty's channel_update for a private channel actually changed our stored copy (did_change, i.e. a strictly higher timestamp). A counterparty can therefore send a stream of channel_updates with incrementing timestamps, and each one will mark the refresh pending and (via process_... at line 17071) rebuild and re-send ServeStaticInvoice onion messages for every Used/Pending async offer to the static invoice server — bypassing the INVOICE_REFRESH_THRESHOLD rate-limiting entirely.
Consider only marking the refresh pending when the update actually changes a forwarding parameter encoded in the invoice paths (fees / cltv / htlc bounds), and/or debouncing so a chatty peer can't drive unbounded onion traffic.
Implements an async receive follow-up from #4135: refresh async offers/static invoices when a channel opens, closes, or fees change.
Static invoices are built from the recipient's current payment paths. If local channel state changes, an already-published async receive offer can otherwise continue serving stale server-side invoices until the normal refresh threshold passes.
This PR adds a forced refresh path for async receive static invoices. The existing timer-based refresh behavior remains unchanged; channel changes simply add an immediate refresh trigger for affected offers.
The existing invoice construction flow remains the single source of truth. Forced refresh changes when invoices are refreshed, not how they are built. The refresh itself is deferred until after channel locks are released, preserving the existing lock-safety model while ensuring invoices are updated promptly.