Skip to content

feat(state): verify VCT roots during header sync (move verification off the committer)#447

Draft
p0mvn wants to merge 8 commits into
roman/zakura-header-anchor-root-validationfrom
split/vct-hs-verify
Draft

feat(state): verify VCT roots during header sync (move verification off the committer)#447
p0mvn wants to merge 8 commits into
roman/zakura-header-anchor-root-validationfrom
split/vct-hs-verify

Conversation

@p0mvn

@p0mvn p0mvn commented Jul 3, 2026

Copy link
Copy Markdown

Reapplication of #366 onto ironwood-main (rebased over the #444 squash-merge of roman/vct-config). The original PR was written against the old feat/pre-release-main feature branch; this ports the same change to the current branch structure.

Goal

Make header-sync commit the single authoritative verify+fold for peer-supplied commitment roots, so a lying peer is caught early and attributably at the network boundary (scored + refetched), and the finalized committer no longer needs the successor at all (no successor look-ahead, no await-successor park, no deadlock).

What changed vs the original #366

The base branch evolved, so the port adapts rather than replays:

  • The original's first commit (parts-based ZIP-221 leaf building) is dropped: it was already merged into this lineage in a refined struct-based form (feat(chain): add history tree parts APIs #439, HistoryTreeBlockParts). The verifier folds via push_from_parts(network, HistoryTreeBlockParts { .. }) instead of the original's many-argument signature.
  • Peer-supplied roots live in the unified commitment_roots_by_height column family on this branch (with per-height tx counts and auth_data_root already carried), so the original's separate zakura_header_commitment_roots_by_height CF does not exist here. Only the new zakura_header_frontier_tree CF is added.
  • The Ironwood pin uses this branch's Nu6_3 activation (verify_supplied_ironwood_root_below_nu6_3 / InvalidPreNu6_3IronwoodRoot), not the original's Nu7 variant.
  • The committer's successor machinery on this branch had grown a VctWriteManager look-ahead and a prevalidation/dedup cache; both are removed along with NextVctBlock and the commit_finalized(_direct) successor threading.

Commits

  1. feat(state): verify supplied roots at header-sync commit (frontier MMR) — extract a header-driven header_commitment_is_valid_for_chain_history core; add verify_supplied_roots_from_parts + the sub-Heartwood/sub-NU5/sub-Nu6_3 direct checks; persist a running header-frontier ZIP-221 MMR (new zakura_header_frontier_tree CF) that each committed range's roots are folded into and verified against the header commitments; reject a bad range with CommitHeaderRangeError::InvalidCommitmentRoots.
  2. feat(state): move VCT verification to header sync; committer trusts pre-verified roots — map InvalidCommitmentRootsInvalidRange peer scoring in the header-sync driver; drop the committer's successor look-ahead and the write-worker await-successor park; remove the successor threading from commit_finalized/commit_finalized_direct. The committer re-checks its own header commitment and folds the (already header-sync-verified) roots.
  3. test(state): rework VCT tests for header-sync verification; remove dead successor machinery — remove the successor/dedup machinery and its tests (deferral, two dedup tests, poisoned-root refill); update the wrong-root test to the one-block-lag expectation (the tip case is header-sync's job); simplify VctState/VctCommitState/VctWriteManager; regenerate CF snapshots.

Test evidence

  • cargo check --workspace --all-targets clean
  • cargo fmt --all -- --check clean
  • cargo clippy -p zebra-state -p zebrad --all-targets clean
  • cargo test -p zebra-state: 289 passed, 0 failed, 2 ignored

AI disclosure

Reapplied and adapted with Claude Code (Fable); the contributor is the responsible author.

@v12-auditor

v12-auditor Bot commented Jul 3, 2026

Copy link
Copy Markdown

Note

Complete: Audit complete. V12 found one issue worth reviewing.

Open the full results here.

FindingSeverityDetails
F-95471 🟠 High
Unauthenticated range-tip roots

A Zakura peer controls the tree_aux_roots carried in a header-range commit, and the driver forwards those roots into state for CommitHeaderRange. The new header-root verifier checks each header commitment against the tree before folding that same height’s supplied roots, and its own documentation states that a range [start..=end] authenticates only roots through end - 1. The header-range batch nevertheless treats the returned tree as verified state and stores root rows for every supplied height, including the unauthenticated range tip. The finalized VCT fast path then trusts those header-sync roots for the current block after the diff removed successor lookahead, folds them without waiting for H + 1, and writes the supplied roots as shielded anchors and history-tree state. A malicious peer can therefore place a bad root at the last height of a committed range and have it consumed before any successor header authenticates it.

And two more auto-invalidated findings.

Analyzed 12 files, diff 2debd4f...0a15345.

@p0mvn
p0mvn force-pushed the split/vct-hs-verify branch from 0a15345 to edbba53 Compare July 3, 2026 20:12
@p0mvn
p0mvn changed the base branch from roman/vct-config to ironwood-main July 3, 2026 20:12
@p0mvn
p0mvn force-pushed the split/vct-hs-verify branch from 0d7efd3 to 9080aa8 Compare July 4, 2026 00:47
@p0mvn
p0mvn changed the base branch from ironwood-main to split/chain-commitment-aux-verify July 4, 2026 00:48
@p0mvn
p0mvn force-pushed the split/vct-hs-verify branch from 9080aa8 to 4ca5e09 Compare July 4, 2026 00:48
p0mvn added 6 commits July 4, 2026 06:22
…isting

Peer-supplied commitment (tree-aux) roots delivered during Zakura header
sync are now verified against block header commitments before they are
written to state. A block's commitment binds the history tree as of its
parent, so a root at height H is confirmed when H+1's header is checked;
each committed range persists only its header-authenticated confirmed
prefix, and forward ranges overlap by one block so the next range confirms
the previous tip.

On startup the ZIP-221 history tree is reconstructed from the durable
confirmed roots up to the highest contiguous frontier, and header sync
resumes from that frontier with an overlapping range that re-validates the
next root. Unauthenticated peer data is therefore never folded into the
header-sync history tree, and a restart never trusts an unconfirmed tip
root.
…struction

Add §6.4 covering the header-sync-layer verify-before-persist gate, the
confirmed-prefix persistence (the range tip's root is confirmed by the next
overlapping range), and the startup reconstruction that resumes from the
highest contiguous confirmed frontier. Update §5.4, §11, and the §12 roadmap
(increment 6d) to match.
…rg, reanchor)

Add integration tests derisking the verified note-commitment-tree-root
header-sync flows that previously had no automated cross-component coverage:

- restart-resume: driver startup reconstructs the history tree at the
  confirmed frontier and resumes header sync at frontier+1 (zebrad)
- block reorg below the header frontier drops conflicting provisional
  headers/roots so reconstruction folds nothing stale (zebra-state)
- reanchor dispatches the frontier-tree rebuild and forward sync resumes
  after the reload (zebra-network)
- append_confirmed_roots resumes from a non-empty post-Heartwood base tree
  (zebra-chain)
- CommitHeaderRange rejects a full-length roots vector through the real
  request path, enforcing the confirmed-prefix boundary (zebra-state)

Also import NetworkKind in the Zakura handler to fix an unrelated build break.
…lidation

Header-sync commit routed every range through the forward-only
header-auxiliary commitment validation. Backward checkpoint-backfill ranges
fold onto the previous checkpoint's tree, not the forward frontier the reactor
caches, so they returned MissingHeaderHistoryTree and retried forever, never
committing their headers.

Forward ranges (finalized or not) are still ZIP-221 aux-validated. Backward
ranges are authenticated by the checkpoint hash and now commit with no
provisional roots: CommitHeaderRange.verified_roots is Option, None for the
checkpoint-authenticated path, and prepare_header_range_batch_with_roots
accepts an empty roots vector while still rejecting a full-length (tip-included)
vector, so the trust boundary is unchanged.

Fixes the two regressed backfill-commit tests; fixes a stale forward-genesis
fixture that supplied empty roots for a Sapling-active range.
@p0mvn
p0mvn marked this pull request as ready for review July 4, 2026 07:31
@p0mvn
p0mvn changed the base branch from split/chain-commitment-aux-verify to ironwood-main July 4, 2026 07:32
@p0mvn
p0mvn marked this pull request as draft July 4, 2026 07:32
…lls behind (#459)

When this node is ahead of its Zakura header-sync overlay peers (they are
behind) and legacy body sync keeps committing, the verified body tip can
outrun the header-sync frontier, leaving header sync stuck below the local
verified state with no peer to fetch from. Those blocks are locally verified,
so once the verified tip leads the frontier by more than
HEADER_SYNC_FOLLOW_VERIFIED_TIP_GAP, header sync now re-anchors up to the
verified tip (reusing the existing re-anchor path, which rebuilds the frontier
history tree there). The gap gate keeps the re-anchor's status broadcast well
under the inbound status-spam interval even while the verified tip climbs fast.
@p0mvn
p0mvn force-pushed the split/vct-hs-verify branch from 4ca5e09 to 473eaac Compare July 4, 2026 20:58
@p0mvn
p0mvn changed the base branch from ironwood-main to roman/zakura-header-anchor-root-validation July 4, 2026 20:59
@p0mvn
p0mvn force-pushed the split/vct-hs-verify branch from 473eaac to 04554bd Compare July 4, 2026 21:51
@p0mvn
p0mvn force-pushed the roman/zakura-header-anchor-root-validation branch 2 times, most recently from 5f44263 to 46f41c6 Compare July 6, 2026 04:36
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