Skip to content

round: wire round-born VTXOs to the batch-canonicality gate (C6)#818

Closed
ellemouton wants to merge 2 commits into
c5-vtxo-availability-gatefrom
c6-round-canonicality-gate
Closed

round: wire round-born VTXOs to the batch-canonicality gate (C6)#818
ellemouton wants to merge 2 commits into
c5-vtxo-availability-gatefrom
c6-round-canonicality-gate

Conversation

@ellemouton

Copy link
Copy Markdown
Member

C6 — Wire round-born VTXOs to the batch-canonicality gate

Part of the reorg-safety epic (lightninglabs/darepo#454). Stacked on the C5
availability gate (#796).

What this does

When a round's commitment tx confirms and the client materializes its owned
VTXOs, the round actor registers the batch with the BatchCanonicalityManager
(RegisterBatchRequest carrying the batch txid, consumed inputs = boarding
outpoints + forfeited VTXO outpoints, dependent VTXOs = round-born VTXO
outpoints, the batch confirmation pkScript, and the CSV expiry delta). The C5
gate (dormant until producers register batches) then governs those VTXOs:
a reorg-out or input double-spend excludes them from coin selection until the
batch is canonical again. The consumed-input set also seeds the manager's
reorg-aware spend watches, underpinning forfeited-VTXO restore (F6).

Registration is gated behind an optional RoundClientConfig.BatchCanonicality
ref — None keeps the gate dormant and preserves pre-C6 behavior, so existing
hosts/tests are unaffected.

Unlocks acceptance F2/F3 (round-born VTXO reorg → limbo → usable / input
conflict → unavailable) and sets up F6 (forfeit-into-invalidated-round
restore).

Foundation commit

The first commit (round: gate commitment-tx terminal transition on finality)
is cherry-picked from #558 — it makes the round commitment watch
reorg-aware and gates the terminal transition on finality, which C6 builds on.
It is the interim home; #558 should drop it (or stack on C6) to avoid the
duplicate at merge time.

F1 (pre-commitment input gate): safety already holds; proactive abort deferred

F1's safety ("a round must not complete using a double-spent boarding input")
is already structurally guaranteed: an unconfirmed commitment never reaches
Confirmed and so never materializes VTXOs, and the reorg-aware finality gate
never finalizes a reorged/never-confirming commitment. The remaining value is
proactive abort (fail fast instead of waiting for the registration timeout) —
a liveness optimization. At InputSigSent the client is past the point of no
return, so a forced FSM abort from an async spend event is intricate for
marginal gain; this is deferred as a focused follow-up, consistent with the
codebase's current detection-only reorg posture.

Activation (darepod) — deferred

Like C5, the round gate ships dormant. Constructing/starting the canonicality
manager and threading the store/ref to the vtxo + round actors is the
integration step that ties #795 + #796 + C6/C7/C8 together and needs the daemon
harness to validate.

Tests

Unit tests cover the populated RegisterBatchRequest, the dormant no-op when
unwired, and the skip for an empty batch. End-to-end F2/F3/F6 land as client
systests in the acceptance pass.

🤖 Generated with Claude Code

@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 reorg-safety mechanism by gating the round FSM's terminal transition on commitment finality rather than the first provisional confirmation. It adds a cache for provisional confirmation events, wires reorg-aware lifecycle refs to detect reorgs and finality, and registers confirmed batches with a canonicality manager to track consumed inputs and dependent VTXOs. Feedback on these changes highlights a potential memory leak where pendingCommitmentConfs and commitmentTxIndex are not cleaned up when rounds are cancelled or reaped.

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.

Comment thread round/actor.go
// ledger emissions, indexer notifications) because a reorg of the
// confirmation block would otherwise leave that state inconsistent
// with the canonical chain.
pendingCommitmentConfs map[chainhash.Hash]*ConfirmationEvent

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Memory / State Leak on Failed or Cancelled Rounds

While pendingCommitmentConfs is correctly cleaned up on successful finalization in handleCommitmentFinalized, there is currently no cleanup of this map (or commitmentTxIndex) when a round is cancelled via handleCancelRound or reaped via reapFailedRounds.

If a round fails or is cancelled after receiving a provisional confirmation but before finalization, the cached confirmation event will remain in pendingCommitmentConfs indefinitely, leading to a memory leak. Additionally, for cancelled rounds, the TxID is never deleted from commitmentTxIndex because handleCancelRound deletes the round from a.rounds immediately, preventing reapFailedRounds from ever seeing and cleaning it up.

Suggested Fix:

  1. In reapFailedRounds, delete the entry from pendingCommitmentConfs:
    delete(a.pendingCommitmentConfs, roundFSM.TxID)
  2. In handleCancelRound, ensure both commitmentTxIndex and pendingCommitmentConfs are cleaned up:
    delete(a.commitmentTxIndex, targetFSM.TxID)
    delete(a.pendingCommitmentConfs, targetFSM.TxID)

@ellemouton
ellemouton force-pushed the c5-vtxo-availability-gate branch from 9a99bae to e3a9a64 Compare June 29, 2026 20:10
@ellemouton
ellemouton force-pushed the c6-round-canonicality-gate branch from 232a141 to a630201 Compare June 29, 2026 20:10
@ellemouton
ellemouton force-pushed the c5-vtxo-availability-gate branch from e3a9a64 to 3efe711 Compare July 1, 2026 16:16
@ellemouton
ellemouton force-pushed the c6-round-canonicality-gate branch from a630201 to 7f8df41 Compare July 1, 2026 16:16
@levmi levmi added P1 Priority 1 — high reorg round safety Fund-safety: stuck, lost, or mis-counted funds vtxo labels Jul 6, 2026
Squashed for the btcd v2 port. batchcanon Availability vocab
(available_final/provisional/unknown, limbo_reorg/conflict,
invalidated) + CombineAvailability + store-driven LineageBlocked, and
the vtxo.Manager coin-selection/forfeit admission gate that drops
candidates whose batch lineage is limbo/invalidated. Permissive for
unseen/unregistered; no-op when the store is nil.
@ellemouton
ellemouton force-pushed the c5-vtxo-availability-gate branch from 3efe711 to a18c0a4 Compare July 8, 2026 21:07
@ellemouton
ellemouton force-pushed the c6-round-canonicality-gate branch from 7f8df41 to b317f3c Compare July 8, 2026 21:09
Squashed for the btcd v2 port. The round registers its round-born
batch + consumed inputs with the canonicality manager, and gates
pre-commitment progression on consumed-input canonicality (finality
gate kept as interim safety).
@ellemouton

Copy link
Copy Markdown
Member Author

Superseded by #896 as part of condensing the reorg-safety client stack (epic lightninglabs/darepo#454) from 12 PRs into 3. The commits are carried over unchanged; see #896. Branch retained as a backup.

@ellemouton ellemouton closed this Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1 Priority 1 — high reorg round safety Fund-safety: stuck, lost, or mis-counted funds vtxo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants