multi: Add configurable reorg recovery foundation#964
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces significant reorg-safety improvements for the boarding sweep and unroll subsystems. Key changes include adding a chain reconciliation mechanism that verifies persisted anchors against the canonical chain on restart, implementing a fail-closed guard (ReliveUnsafe) to prevent accidental recovery of spent VTXOs, and refining the FSM lifecycle to handle external spend observations and reorgs more robustly. The changes also include comprehensive test coverage for these new safety paths and updated configuration options for reorg safety depth and reconciliation timeouts. As there were no review comments provided, I have no feedback to offer.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
fe01548 to
dfe71da
Compare
292394e to
233abdd
Compare
dfe71da to
f27fde5
Compare
|
Rebased the three recovery commits onto the rewritten #895 head |
f27fde5 to
57fe7a8
Compare
|
@claude please review this PR — Client PR 2 of the reorg-safety stack (configurable reorg recovery foundation), now rebased directly onto |
Review: PR #964 — Configurable reorg recovery foundationI reviewed the three focus areas (unilateral-exit recovery, boarding-sweep recovery, fail-closed-on-missing-evidence, and the Overall this is careful, high-quality work. The reconciliation core is genuinely fail-closed: 🔴 High —
|
57fe7a8 to
74434ea
Compare
|
@claude review this |
|
Claude finished @ellemouton's task in 2m 59s —— View job Review: PR #964 — Configurable reorg recovery foundationI re-read the three focus areas directly (unilateral-exit reconcile/relive, boarding-sweep reorg/restart recovery, fail-closed-on-missing-evidence, and the 🔴 High —
|
|
@claude review this — the release-blocking upgrade finding is fixed in current head The remaining notes are value-safe liveness boundaries, not release blockers for this stack: authoritative-negative clearing is deliberately deferred, and unavailable chain evidence deliberately stalls rather than guesses. The boarding retry/coalescing items should stay follow-up work rather than expanding this reorg foundation. Please re-review the current head. |
|
Claude finished @ellemouton's task in 1m 38s —— View job Re-review: PR #964 — Configurable reorg recovery foundationI re-verified the release-blocking upgrade finding against current head
✅ High finding resolved — legacy Started checkpoints now fail closedThe fix closes the fail-open at the upgrade boundary exactly as it should:
The comments on both the constant ( Remaining notes — agreed, all follow-up workI confirm the outstanding items are value-safe liveness boundaries, not release blockers for this stack, and agree with deferring them:
VerdictThe one release-blocking finding is fixed correctly and pinned by a regression test; everything else is intentional-for-this-PR or fail-closed liveness polish tracked for follow-ups. No blocking issues remain from my review. LGTM. 🎉 Methodology: read the fix diff at |
747f805 to
428fd45
Compare
|
@claude review this |
|
Claude finished @ellemouton's task in 3m 31s —— View job Re-review: PR #964 — Configurable reorg recovery foundationRe-reviewed at current head
✅ What changed and why it's soundThe relive guard is now cleared only by positive evidence, not by absence:
Correctness of the per-tx clear vs. a job-level guard: clearing a job-wide Tests pin all three behaviors:
Remaining notes — unchanged, all fail-closed follow-upsThe value-safe liveness boundaries from prior rounds still stand and remain appropriate to defer:
VerdictThe previously release-blocking upgrade-boundary fail-open is fixed, and the rework replaces "silence-is-not-evidence" with an explicit positive-evidence clear that keeps production fail-closed while making the recoverable path testable. Regression tests pin all three behaviors; Methodology: read the full |
428fd45 to
b6c2fd0
Compare
|
@claude review this — final head is now rebased onto current main, including the round-status follow-up required by Lumos master. |
|
Claude finished @ellemouton's task in 2m 19s —— View job Re-review: PR #964 — Configurable reorg recovery foundation (rebased final head)Re-reviewed at current head
Scope of this rebaseThe PR's own diff is functionally identical to the Re-verified invariants on the rebased tree
Remaining notes — unchanged, all fail-closed follow-upsSame value-safe liveness boundaries as prior rounds, still appropriate to defer: authoritative-negative clearing deferred to the next client PR; genuine-offline-reorg head-of-line stall (correctness-over-liveness); boarding retry/coalescing polish (partially-externally-spent sweep re-broadcast, VerdictThe rebase preserves the evidence-based relive fix intact, introduces no recovery-code changes, and carries no conflict residue; the round-status follow-up is a base-side dependency correctly kept out of this diff. No blocking issues remain. LGTM. 🎉 Note: |
Unilateral exits previously treated confirmation and spend observations as one-way facts. A chain replacement could therefore leave an actor driving side effects from stale proof, checkpoint, or sweep state, especially after a restart replayed that state without first checking the canonical chain. Persist the chain anchors behind each transition and keep confirmation, sweep, and external-spend evidence reversible until policy finality. Reconcile every persisted positive observation before restart side effects, while treating a missing observation or lookup timeout as unknown rather than proof of absence. Keep the source VTXO under a durable unsafe hold whenever an admitted exit has an ambiguous footprint. A failed exit may restore the VTXO only after both the local footprint is empty and the chain boundary supplies objective canonical-absence evidence. Persist terminal exit state before forwarding the corresponding VTXO lifecycle outcome. Use versioned TLV messages and snapshots for the new durable state so rolling upgrades can replay it safely. Extend the actor, registry, reconciliation, restart, and reorg tests to cover reordered and duplicated observations.
Boarding sweeps previously collapsed the first confirmation or spend notification into terminal wallet state. If that evidence was later reorged out, the actor could no longer distinguish a canonical spend from a provisional one and could restore or retire inputs incorrectly. Keep the sweep confirmation and a separate spend watch for every consumed boarding input active until the chain source reports policy finality. Record positive observations provisionally, remove them on Reorged, and finalize each consumed input durably so a later sweep-level failure cannot make already spent value available again. Treat a broadcast failure as an uncertain attempt instead of evidence that no transaction reached the chain. Recovery remains bounded and tip-paced, and a restart reconstructs the outstanding watches from persisted actor state. Add focused actor and store coverage plus a real LND/Bitcoin system test that drives a boarding sweep through confirmed, reorged, and reconfirmed states.
The reorg-aware actors need one explicit policy boundary. Without shared wiring, individual subsystems can finalize at different depths or retain watches indefinitely when a backend allocates a Done channel but never writes to it. Add reorgsafetydepth as the deepest chain replacement the client promises to recover from. Default it to 30 blocks, cap it at the current cross-backend limit of 144, and begin terminal finality one confirmation beyond the selected horizon. Feed that boundary into chain-source Done synthesis and unilateral exit restart reconciliation. Define the reorg-safe Ark protocol version, but deliberately keep client advertisement pinned to v1. One-confirmation VTXO exposure remains disabled until batch lineage, admission gates, server recovery, and end-to-end safety tests land in the remaining stack. Document the configuration boundary and test default, override, validation, finality, and negotiation behavior.
Treat legacy started checkpoints and ambiguous broadcast failures as unsafe. Only an explicit no-broadcast proof may clear the relive guard, so a failed exit cannot restore a coin that may have crossed the chain boundary.
b6c2fd0 to
604b856
Compare
|
@claude review this — current restacked head |
|
Claude finished @ellemouton's task in 1m 44s —— View job Re-review: PR #964 — Configurable reorg recovery foundation (restacked head
|
Roasbeef
left a comment
There was a problem hiding this comment.
In this review, I'm treating #964 as the client-side recovery foundation. The H/H+1 arithmetic and reversible chainsource lifecycle look sound as disabled substrate. The remaining comment is a cross-stack contract issue that needs to be closed before the capability is advertised.
|
|
||
| // reorgSafetyDepth resolves the daemon's configured recovery horizon. | ||
| func (c *Config) reorgSafetyDepth() uint32 { | ||
| if c.ReorgSafetyDepth == 0 { |
There was a problem hiding this comment.
At the client layer, zero means use the 30-block default. Lumos #626 gives an explicit zero a disable/immediate-finality meaning. We can't advertise v2 with two meanings for the same knob: one deployment config can otherwise produce 31-conf client finality and first-conf server finality. This is fine as disabled foundation, but capability negotiation/config validation needs to carry the same nonzero H on both sides and reject a mismatch before activation.
What this PR does
Teaches existing client subsystems that confirmed chain facts can later
disappear. It does not enable one-confirmation VTXO spending — it
establishes the recovery behaviour that sits underneath that feature.
It makes these operations recoverable:
Instead of treating "once confirmed" as permanent, the client persists the
evidence and rechecks it. If the fact disappears, the subsystem returns to the
appropriate earlier state.
A key invariant: broadcast ambiguity cannot resurrect a VTXO. A VTXO is
relived only with positive evidence that its spending transaction was
definitely not broadcast.
It also introduces the configurable reorg-safety horizon
H(default 30);terminal chain finality is computed as
H + 1.reorgsafetydepth=0resolves tothe safe default; the horizon is capped at 144 (the deepest reversible watch
window supported by every current backend).
Review focus
consistently?
Out of scope
Full design & diagrams:
REORG_SAFETY_PR_REVIEW_GUIDE.md§4.1. Overallarchitecture and PR stack: lumos#454.