Skip to content

multi: make invoice loading async, only block callers when they wish to read the invoice state#168

Open
Roasbeef wants to merge 2 commits intolightninglabs:masterfrom
Roasbeef:async-load
Open

multi: make invoice loading async, only block callers when they wish to read the invoice state#168
Roasbeef wants to merge 2 commits intolightninglabs:masterfrom
Roasbeef:async-load

Conversation

@Roasbeef
Copy link
Copy Markdown
Member

@Roasbeef Roasbeef commented Apr 2, 2025

In this PR, we refactor the LND challenger's invoice loading to be fully
asynchronous, so aperture can start serving requests while the historical
invoice set is still being populated in the background.

Previously, LndChallenger.Start would call ListInvoices with
NumMaxInvoices: math.MaxUint64, attempting to fetch the entire invoice
history in a single RPC call. For nodes with a large number of historical
invoices (think 10+ minutes of loading on older nodes), this would block
startup entirely. The proxy couldn't serve any traffic until every last
invoice was loaded into memory.

We fix this by splitting the work into two concurrent goroutines that run
in the background: one paginates through the historical invoice set in
configurable batches (via the new InvoiceBatchSize config option,
defaulting to 1000), while the other subscribes to real-time invoice
updates starting from the latest known indices. This way aperture is ready
to accept connections almost immediately after launch, and callers that
actually need to verify an invoice state will block only until the initial
load completes (with a configurable timeout).

The core abstraction enabling this is a new InvoiceStateStore type that
wraps the invoice states map with a sync.Cond for signaling waiters. It
tracks whether the initial historical load has finished, and exposes a
WaitForState method that first waits for the load to complete (if it
hasn't yet), then waits for the specific invoice to reach the desired
state. VerifyInvoiceStatus delegates to this store, which keeps the
challenger code itself clean. The store also respects the quit channel
throughout, so shutdown during a long load is handled gracefully.

When strictVerify is false, we skip the entire loading and subscription
process, since we don't need the invoice state map at all in that mode.

See each commit message for a detailed description w.r.t the incremental
changes.

Fixes #167

This will be used to simplify some of the logic the upcoming commit. We
retain the existing condition variable usage, while also adding some
methods that will be of use for the upcoming async background load.
@Roasbeef Roasbeef marked this pull request as ready for review February 25, 2026 03:09
This commit refactors the LND challenger's invoice handling mechanism
to improve performance, reliability, and resource usage, especially
for nodes with a large number of historical invoices.

Previously, the challenger attempted to load all historical invoices
in a single `ListInvoices` call during startup. This could lead to
long startup times, high memory consumption, and potential timeouts
or failures for nodes with extensive invoice history. Additionally,
the state management relied on a mutex and condition variable, which
could be complex to manage correctly.

The key changes include:

- **Concurrent Loading and Subscription:** The challenger now starts
  two background goroutines concurrently: one to load historical
  invoices and another to subscribe to new invoice updates using the
  latest known indices.

- **Paginated Historical Loading:** Historical invoices are now fetched
  in batches using `ListInvoices` with `IndexOffset`. The batch size
  is configurable via the new `InvoiceBatchSize` config option
  (defaulting to 1000) and passed down from the main Aperture config.

- **InvoiceStateStore:** A new `InvoiceStateStore` type is introduced
  to manage the invoice states map. This store handles thread-safe
  access, tracks the completion of the initial historical load, and
  provides a `WaitForState` method that correctly handles waiting for
  the initial load before checking for a specific invoice state.

- **Improved Shutdown/Cancellation:** Context cancellation and handling
  of the quit signal are improved throughout the loading and
  subscription processes to ensure cleaner shutdowns.

- **Refactored VerifyInvoiceStatus:** This method now delegates waiting
  logic to the `InvoiceStateStore`, simplifying the challenger code
  and ensuring it waits for the initial load if necessary.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aperture: add support for async invoice loading on start up

1 participant