Skip to content

recovery: L402 and static address recovery from local backup file#1121

Open
hieblmi wants to merge 33 commits into
lightninglabs:masterfrom
hieblmi:recover-l402-static
Open

recovery: L402 and static address recovery from local backup file#1121
hieblmi wants to merge 33 commits into
lightninglabs:masterfrom
hieblmi:recover-l402-static

Conversation

@hieblmi

@hieblmi hieblmi commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator

only last commit is relevant for this PR, the rest is rebased on the dyn-conf-tracker PR

Adds encrypted local recovery for static-address/L402 state.

The recovery backup is written once per paid L402 generation and contains the paid l402.token, Bitcoin network, L402 token metadata, the L402-bound static-address server key, protocol/expiry, main/change key families, first address height, and the V0 client pubkey needed to recreate the current concrete static-address row.

On fresh installs, Loop restores the latest valid backup before creating a new paid L402 generation. Existing installs backfill the immutable backup for their active generation. loop recover restores a specific backup file, or the latest valid backup in the active network directory when no file is provided.

The backup intentionally does not store mutable address cursors, per-address rows, server xpubs, pkScript, Taproot address strings, deposit FSM state, or scan gap/lookahead policy. These values are either derivable or recovered through wallet/chain scanning and reconciliation.

@hieblmi hieblmi marked this pull request as draft April 14, 2026 10:09
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the resilience of Loop by introducing a robust local recovery system. It allows users to restore their static address and L402 client state from an encrypted backup, ensuring continuity of operations even after data loss or a fresh installation. The changes include automated backup creation on startup, a new CLI command for restoration, and a dedicated recovery service that handles encryption, key derivation, and integration with existing static address and deposit management functionalities.

Highlights

  • Local Recovery for Static Address and L402 State: Introduced a phase-1 local recovery mechanism for Loop's static address and L402 client state, allowing restoration from an encrypted backup file.
  • Automated Backup Creation: Implemented automatic creation of an encrypted backup file in the active Loop data directory when loopd starts and finds existing static address or L402 token state.
  • New Recovery Package and CLI Command: Added a dedicated recovery package to orchestrate backup/restore operations, handle file formats and encryption, and introduced a loop recover CLI command for manual restoration.
  • Integrated Restoration Process: Enabled the static-address manager to support restoration by reusing existing address import logic and integrated a deposit-manager reconciliation entrypoint for best-effort deposit discovery after state import.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a local recovery mechanism for Loop static addresses and L402 authentication state. It includes a new recovery package for managing encrypted backups, a recover CLI command, and a gRPC service to trigger the restoration process. On the daemon side, backups are automatically generated during startup. Feedback suggests that backup failures during startup should not prevent the daemon from running and recommends enhancing the atomic file writing logic with explicit synchronization and better temporary file cleanup.

Comment thread loopd/daemon.go Outdated
Comment thread recovery/service.go Outdated
@hieblmi hieblmi force-pushed the recover-l402-static branch 3 times, most recently from a2e5f02 to 83e2547 Compare April 14, 2026 14:53
@hieblmi hieblmi force-pushed the recover-l402-static branch 8 times, most recently from 5790124 to dddf020 Compare April 20, 2026 10:11
@hieblmi hieblmi force-pushed the recover-l402-static branch from dddf020 to 5dfa10c Compare April 30, 2026 12:07
@hieblmi hieblmi marked this pull request as ready for review April 30, 2026 12:10
@hieblmi hieblmi force-pushed the recover-l402-static branch 13 times, most recently from d71f3a2 to 405e610 Compare May 11, 2026 09:34
hieblmi added 27 commits July 1, 2026 12:28
Add a TxOutChecker interface for checking whether a selected deposit
outpoint is still available before signing the HTLC transaction.

Back the implementation with lnd wallet transaction data so known
confirmed and mempool spends mark the outpoint unavailable.
Document the lock-order invariant between Manager.mu and individual
deposit locks.

Later changes need both locks in the same path, so make the rule
explicit before the locking surface grows.
Move active-deposit block notification fan-out into a helper.

This keeps the event loop small and gives later startup replay logic
a single path for notifying recovered deposit FSMs.
Guard reconcileDeposits with a dedicated mutex.

Polling and block-driven reconciliation can overlap, so serialize the
path before it updates confirmation data and active FSM state.
Reject nil deposits and final-state deposits before sending FSM
events.

This keeps callers from transitioning stale or completed deposits and
uses the no-lock state helper while deposits are already locked.
Add a duration helper that falls back to the default payment timeout.

Recovered legacy swaps can have a zero persisted timeout, so later
deadline logic can use this without treating zero as immediate expiry.
A block notification can queue OnExpiry before a deposit reaches a final
state. If the final transition wins that race first, the stale expiry event
must not overwrite the terminal outcome.

Keep LoopedIn and Withdrawn as self-loops on OnExpiry, matching the other
final states. Add a focused FSM test that sends OnExpiry directly to each
final state and verifies the state is preserved.
Document deposit lock ownership for mutable confirmation state and
route production reads through deposit accessors.

Keep store persistence on no-lock helpers while callers hold the
deposit lock, preserving the existing transition behavior without
leaving direct field reads in user-facing paths.
A shutdown while publishing or monitoring the HTLC timeout sweep should not
transition the loop-in to Failed.

Return NoOp on context cancellation in those actions so the persisted
state remains a recovery point. Add focused tests for shutdown during
publication retry and confirmation monitoring.
After the client gives the server HTLC signatures, shutdown must not drive the monitor state through the generic error path. That path cancels the invoice and attempts to unlock deposits even though the server can still publish the HTLC.

Return NoOp for monitor-state cancellation races and cover shutdown with a regression test that asserts no invoice cancellation or deposit unlock occurs.
Allow golangci-lint more time for the larger static-address test
suite.

The dynamic-confirmation stack adds several focused tests, and the
default timeout is tight on slower CI workers.
Retain static-address deposits as soon as lnd reports the UTXO, even when the output is still unconfirmed. Store the first confirmation height once the output confirms.

Replay the startup block to recovered deposit FSMs so expiry handling can run immediately after restart. Derive confirmation heights from a stable wallet view because lnd reports confirmation counts.
Build list and summary responses from tracked deposit records instead of
raw wallet UTXOs so RPC clients see the manager availability state.
Split unconfirmed value from confirmed deposited value in summaries.

Keep withdrawal and channel-open flows on confirmed inputs by rejecting
unconfirmed selected deposits in those paths.
Treat unconfirmed static-address deposits as swappable because their CSV
timeout has not started yet. Keep confirmed deposits ahead of
unconfirmed ones during automatic selection, then sort by value and
remaining lifetime within each confirmation group.

Share the expiry calculation with the dynamic-programming selector so
unconfirmed deposits do not look like the earliest-expiring candidates.
Treat lnd wallet view as the source of spendable static-address
outputs while keeping historical deposit records in the DB. Reconcile
active FSMs against the current wallet view and reactivate known
deposits when their outpoints are visible again.

Refresh deposits before selection, withdrawal, loop-in, and channel-open
paths, and filter list and summary responses through the live active set
so stale Deposited records are not exposed as available funds.
Check the originally selected deposit outpoints before signing a static
loop-in HTLC transaction. If any selected outpoint is no longer
available, cancel the swap invoice and fail the signing action instead
of producing signatures for stale inputs.

Wire the lnd-backed checker through loopd and make invoice-monitoring
handle closed subscription channels without spinning.
Subscribe to static loop-in confirmation-risk notifications before
starting the payment deadline. Start that deadline only after server
acceptance or the legacy confirmation fallback, and cancel the swap
invoice when the server rejects the risk wait.

Refresh selected deposits before the legacy fallback so recovered
monitors use current confirmation heights.
Store server confirmation-risk decisions with static loop-in swaps and
recover accepted payment-deadline timers after restart. Wire
notification persistence through loopd so recovered swaps do not lose
pending risk state.

Deduplicate notification fanout cache entries by swap hash.
Warn before dispatching a static loop-in that selects deposits below
the conservative six-confirmation threshold. Mirror automatic coin
selection before prompting so the warning reflects both manual and
auto-selected deposits.

Cover manual and auto-selected warning paths in CLI tests.
Refresh static loop-in replay sessions for the low-confirmation warning
and payment-timeout prompts. Add replay coverage for the warning prompt
and update fee and payment-timeout variants for the new interaction
sequence.
@hieblmi hieblmi force-pushed the recover-l402-static branch 2 times, most recently from 8a1217e to 8ce8d9b Compare July 2, 2026 11:36
@lightninglabs-deploy

Copy link
Copy Markdown

@hieblmi, remember to re-request review from reviewers when ready

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants