Skip to content

[v0.1.x-branch] Backport #895: multi: reorg-aware chain observation (chainsource + backends) - #981

Closed
github-actions[bot] wants to merge 6 commits into
v0.1.x-branchfrom
backport-895-to-v0.1.x-branch
Closed

[v0.1.x-branch] Backport #895: multi: reorg-aware chain observation (chainsource + backends)#981
github-actions[bot] wants to merge 6 commits into
v0.1.x-branchfrom
backport-895-to-v0.1.x-branch

Conversation

@github-actions

Copy link
Copy Markdown

Backport of #895


Summary

PR 1 of the six-PR basic-v1 reorg-safety stack tracked by
lightninglabs/lumos#454.

This PR is the chain-observation layer. It makes confirmation and spend
observation reversible end to end:

Observed → Reorged → Observed → Done

For txconfirm:

TxConfirmed → TxReorged → TxConfirmed → TxFinalized

The first positive observation is no longer terminal. Higher layers can keep a
fact provisional, react when it leaves the best chain, and observe it again
after reconfirmation.

What is included

  • chainsource: multi-shot confirmation/spend actors, reorg and done
    events, ordered delivery, and height-based finality synthesis for transports
    that do not provide native Done. Transport boundaries explicitly defer a
    buffered Done until the positive confirmation/spend identity has crossed
    first.
  • txconfirm: reversible confirmation notifications, a distinct terminal
    TxFinalized state, and correct terminal-only replay for late subscribers.
  • LND/lndclient backend: preserve reorg-capable observation instead of
    terminating after the first positive event. Today this transport supplies a
    payload-less reorg ping, so chainsource synthesizes finality from block
    height.
  • lwwallet/Esplora: detect same-height, shorter, deeper, and mid-catch-up
    replacements, then forward disconnected/connected blocks in order.
  • Neutrino: forward confirmation/spend reorg and done notifications.
  • Wallet subscriber compatibility: adapt the boarding-sweep txconfirm
    subscriber to the new four-event lifecycle. Without this, TxReorged and
    TxFinalized fall through the old mapper as failure-shaped notifications.
    Broader boarding spend-watch recovery belongs to Client PR 2.
  • Harness and systests: real chainsource and txconfirm reorg round trips.

Native LND transport completion

This PR remains compatible with today's released lnd/lndclient by synthesizing
terminal Done at the chainsource boundary. The proper native transport path
is being completed in parallel:

After both land and Wavelength bumps the dependencies, the LND backend can use
native depth/Done directly while height synthesis remains a compatibility
fallback for older transports.

Deliberate boundary

This PR does not implement:

  • batch/VTXO canonicality or lineage storage;
  • registration-before-exposure;
  • client/server admission gates;
  • conditional restore;
  • unroll lineage gating;
  • one-confirmation VTXO exposure; or
  • the final configured policy-finality boundary.

FinalityDepth is configurable observation policy. This PR retains the
interim default of 6 because no one-confirmation lineage is exposed by it.
Client PR 2 makes the shared basic-v1 terminal boundary configurable so a
deployment can choose how deep a reorg remains recoverable.

This is necessary observation plumbing; it does not, by itself, claim
end-to-end reorg safety through the configured policy depth.

Basic-v1 stack

  1. this PR — reorg-aware chain observation and required subscriber
    compatibility.
  2. client — configurable-depth boarding/unroll recovery foundation and a
    disabled capability.
  3. client — durable complete-lineage batch canonicality, fail-closed
    restart reconciliation, and objective conditional restore.
  4. client — registration-before-exposure and every one-confirmation
    lineage admission/pre-side-effect gate.
  5. server — configurable provisional safety, reversible operations, and
    complete evidence/admission/pre-side-effect gates.
  6. server itests — prove running and offline recovery through a configured
    test depth, sealing at that policy boundary, and the final S1-S5/R2/F9
    contract across both sides.

Generic automatic replay, durable lineage waiters, per-operation usability
depths, and detection after the configured safety horizon remain follow-ups.

Validation

At 233abdd6:

  • go test ./lwwallet ./txconfirm -count=1
  • go test -race ./lwwallet -run TestTipPollerReorgDuringMultiBlockAdvance -count=10
  • go test -race ./lwwallet -run TestTipPollerDeeperForwardReorg -count=100
  • go test -race ./lwwallet -count=10
  • go test -race ./txconfirm -run TestLateFinalizedSubscriberRetrySkipsConfirmed -count=10
  • go test -race ./chainsource ./chainbackends ./btcwbackend -count=5
  • focused positive-before-Done regressions across those three packages,
    -count=20
  • make lint-changed-local — 0 issues

The prior unit-race failure exposed nondeterminism in the deeper-forward
TipPoller test harness: its background poll could observe the deliberately
mutated stub chain between the test's two intended snapshots. That test now
drives the two observations manually and passed 100 focused race runs plus ten
full-package race runs. Fresh full CI is running for this rewritten head. A
human approving review remains the final merge gate.

Add the reorg-aware chain-observation substrate the rest of the
reorg-safety stack consumes: a seq-ordered conf/spend watch lifecycle
(Confirmed -> Reorged -> re-Confirmed -> Done) with height-based
finality synthesis for backends that cannot deliver a native Done
(gRPC lndclient and lwwallet).

Finality synthesis is armed off the select loop, tied to the
sub-actor's long-lived context (a per-attempt timeout would tear the
in-process block-epoch forwarder down the instant it armed), retried
with a capped exponential backoff until it succeeds or the watch's
context is cancelled (a single-confirmation tx has no later event to
piggy-back a retry on), and evaluated against the best height captured
at arm time so a tx already buried past FinalityDepth when the watch
arms finalizes immediately instead of hanging for the next block.

(cherry picked from commit 583663c)
Deliver the full TxConfirmed/TxReorged/re-TxConfirmed/TxFinalized/
TxFailed cycle while a terminal seal keeps reversible notifications
from trailing finality.

Keep the initial TxConfirmed delivery reliable across mailbox failures.
A subscriber attaching after finality receives only the authoritative
TxFinalized result. This prevents a retry from inventing a provisional
confirmation after the transaction has already sealed.

(cherry picked from commit 8faf207)
Forward the lnd and lndclient chain-notifier reorg/finality signals
(NegativeConf -> reorg) into the reorg-aware chainsource lifecycle with
buffered forwarding.

(cherry picked from commit 3340fed)
Detect same-height, shorter, deeper, and mid-catch-up reorgs using a
bounded hash history and PrevBlock continuity checks at every walked
height. Checking every height prevents a chain replacement that lands
mid-walk from permanently stitching together blocks from two forks.

Publish replacements through one ordered ChainEvent stream. This lets
btcwallet see every BlockDisconnected before the new canonical TipBlock
events.

(cherry picked from commit 0db4ff2)
Forward Neutrino chain-notifier reorg signals into the reorg-aware
chainsource lifecycle.

(cherry picked from commit 7a7fa36)
Consume the reorg-aware substrate at the edges: make the wallet
boarding sweep reorg-aware, enable height-based finality on the darepod
chainsource actor, add the harness GetRawTransaction / SignedV3Tx
helpers, and add the end-to-end reorg systests for chainsource and
txconfirm.

(cherry picked from commit 233abdd)
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.

1 participant