feat(state): stitch tree_aux root serving across the vct upgrade height#259
Draft
p0mvn wants to merge 1 commit into
Draft
feat(state): stitch tree_aux root serving across the vct upgrade height#259p0mvn wants to merge 1 commit into
p0mvn wants to merge 1 commit into
Conversation
|
Warning Insufficient credits for auto-review. Keep at least $5.00 of available balance to start a run. Please add credits to continue. |
p0mvn
commented
Jun 25, 2026
Comment on lines
-1578
to
-1585
| let indexed = state.db.commitment_roots_by_height_range(range.clone()); | ||
| if !indexed.is_empty() || state.db.is_vct_synced() { | ||
| // Indexed roots (the common path), or a fast-synced node whose only | ||
| // possible source is the index — never the absent per-height trees. | ||
| indexed | ||
| } else { | ||
| // Pre-index archive database: derive from the per-height trees. | ||
| finalized_state::produce_block_roots(&state.db, range) |
Author
There was a problem hiding this comment.
Note: this was the main bug I suspect
Record a one-time `vct_upgrade_height` marker `U` (the lowest height this binary commits, and the lowest height in the `commitment_roots_by_height` serving index) in a new `vct_upgrade_metadata` column family. Written once on the first committed block and never moved. Root serving (`ReadRequest::BlockRoots`) now stitches the per-height trees below `U` with the serving index at and above `U`, so a node that upgraded mid-chain serves a range crossing `U` as one gap-free batch instead of the short index-only prefix that stalled the fetch client's minimum-progress check. A pre-index archive node (no `U`) still derives the whole range from the trees. Historical note-commitment tree availability is now the band `[U, H)` (H = checkpoint handoff) via a new `vct_tree_absent` helper: trees are present below `U` (pre-upgrade) and at/above `H` (semantic sync), absent only in between. For a genesis fast-sync (`U = 0`) this reduces exactly to the prior `height < H` behaviour.
987508b to
9626164
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
ReadRequest::BlockRootsserved per-block commitment roots with an either-or: if thecommitment_roots_by_heightindex had anything atstart_heightit returned only the index's contiguous prefix, otherwise it derived from the per-height trees. It never stitched the two sources across a boundary.Because the index is written on both commit paths, the only boundary that can exist is the upgrade height
U— the first height the new binary commits: belowUa DB written by the old binary has per-height trees but no index; at/aboveUthe index is present. A peer request straddlingU(e.g.[x, x+500]withU = x+100, default vct mode above so trees stop atU) returned only the ~100-root prefix, which the fetch client rejects under its minimum-progress threshold (¼ of count) — a permanent stall (state.vct.root.stalled.height). This addresses theTODOpreviously sitting in theBlockRootshandler.Solution
vct_upgrade_heightmarkerUin avct_upgrade_metadatacolumn family — the lowest height this binary commits (and the lowest height in the serving index). Written once on the first committed block (both commit paths) and never moved.serve_block_roots): per-height trees belowU+ serving index at/aboveU, concatenated into one gap-free run. A pre-index archive node (noU) still derives the whole range from the trees.[U, H)(H= checkpoint handoff), via a newvct_tree_absenthelper. Trees are present belowU(pre-upgrade) and at/aboveH(semantic sync). Thez_gettreestategate and thesapling/orchard_tree_by_heightguards now use the band. For a genesis fast-sync (U = 0) this reduces exactly to the priorheight < Hbehaviour.U >= H) the band is empty, so every height is servable — matching the design (semantic sync keeps writing trees above the checkpoint).Tests
vct_tree_absent(the[U, H)band, plus the empty-bandU >= Hcase) and an index-sideserve_block_rootstest.U, and assertsserve_block_rootsstill returns the full range gap-free, matching the all-trees reference.U = 0assertion in the genesis fast-sync proptest.zebra-statevct/commitment/tree suite (except one pre-existing failure, see below),zebra-network -- tree_aux,zebra-rpc -- treestate.cargo fmt/clippyclean for the changed code.Specifications & References
verified-commitment-trees design (§4 serving index, §9 tree_aux serving).
Follow-up Work
vct_frozen_frontier_survives_reopenfails on the base branch independently of these changes — the test's custom network has no embedded frontier, so reopening a frozen DB panics atFinalizedState::new. Confirmed it fails identically with this PR's marker write disabled, and for its genesis fast-sync (U = 0)vct_tree_absentis byte-identical to the prior guard.checkpoint_verify=falsemid-run backfills trees only above the current tip, so[U, H)keeps reporting unavailable; full recovery is a fresh reindex from genesis.AI Disclosure
PR Checklist