Skip to content

feat(state): add VCT runtime, embedded frontier, and commitment-root source seam#414

Merged
p0mvn merged 1 commit into
ironwood-mainfrom
split/vct-runtime
Jul 2, 2026
Merged

feat(state): add VCT runtime, embedded frontier, and commitment-root source seam#414
p0mvn merged 1 commit into
ironwood-mainfrom
split/vct-runtime

Conversation

@p0mvn

@p0mvn p0mvn commented Jul 2, 2026

Copy link
Copy Markdown

Motivation

Next slice of the verified-commitment-trees (VCT). It lands the VCT runtime and its inputs — everything about where the fast path's data comes from — while staying fully dormant: the committer fast path that consumes it arrives in the next increment, so there is no behavior change in this PR.

Extracted from the #408 reference draft / #409.

Solution

  • finalized_state/vct.rs (new): the VctState runtime — source-mode selection (checkpoint_sync × vct_fast_sync × embedded-frontier presence), the embedded Mainnet handoff frontier (vct/mainnet-frontier.bin, parsed and height-validated at startup), the Regtest VCT_REGTEST_FRONTIER fixture loader, per-height root lookup bounded by the handoff height, the successor-verification policy, invalidate/evict hooks, and run counters. Declared #[allow(dead_code)] at the module level; the committer slice removes the allow.
  • commitment_aux.rs: the CommitmentRootSource seam the committer will read through — one trait, two sources: the production PeerSource (backed purely by the provisional header-ahead roots the DB already stores, with invalidate un-poisoning a rejected root so a different peer can supply a replacement) and a test-only FixtureSource. There is deliberately no in-memory root cache: tests fill roots through the same insert_zakura_header_commitment_roots write path header sync uses, so the tested read path is the production read path. Committed rows are cleaned up by the database's own retention, not through the seam. The handoff frontier is mandatory on every source (final_frontiers() -> &FinalFrontiers, with vct_last_checkpoint_height derived from it as a default method): the fast path only runs on networks with an embedded frontier, so the type encodes that invariant instead of threading an Option that is always Some in production. Also removes the #[allow(dead_code)] on FinalFrontiers::from_bytes, now consumed by the frontier loader.
  • error.rs: the two retryable commit-stall errors the fast path will raise — VctSuppliedRootUnavailable (peer refetch needed) and VctSuppliedRootAwaitingSuccessor (root present, successor not yet buffered) — with vct_retryable_height / vct_supplied_root_unavailable_height accessors distinguishing the two on the commit error types. Nothing constructs these yet.
  • lib.rs: exports validate_final_frontiers_bytes / FinalFrontiersValidationError (used by the frontier-generation tooling in a later PR).

@v12-auditor

v12-auditor Bot commented Jul 2, 2026

Copy link
Copy Markdown

Note

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

Open the full results here.

FindingSeverityDetails
F-95252 🔵 Low
Public frontier validator panics on malformed input

The newly added, crate-root-exported validate_final_frontiers_bytes(bytes, expected_height) -> Result<(), FinalFrontiersValidationError> is documented as a Result-returning validator for serialized checkpoint-handoff frontier bytes, but it aborts the thread with a panic on a large class of malformed inputs. It delegates to parse_final_frontiers_bytes and FinalFrontiers::from_bytes, which carefully bounds-check the outer framing (4-byte height plus three u32-length-prefixed blobs, with typed FinalFrontiersParseError variants for overflow/missing-length/truncation/trailing bytes). However, once each blob is extracted as an in-bounds slice, from_bytes constructs the trees via <sapling|orchard|sprout::tree::NoteCommitmentTree as FromDisk>::from_bytes(blob), and those FromDisk impls call bincode::DefaultOptions::new().deserialize(...).expect("deserialization format should match the serialization format used by IntoDisk"). Because that .expect() panics inside from_bytes before it returns, the .map_err(...) that would produce FinalFrontiersValidationError::InvalidBytes never runs. A minimal input such as [0u8; 16] with expected_height = 0 passes all framing checks (height 0, three zero-length blobs, no trailing bytes) yet panics when the empty sapling blob is handed to bincode; an invalid Option/enum discriminant byte behaves identically.

And one more auto-invalidated finding.

Analyzed five files, diff cc199db...20094f9.

@p0mvn
p0mvn force-pushed the split/vct-runtime branch 3 times, most recently from c995c81 to 670914f Compare July 2, 2026 20:36
@p0mvn

p0mvn commented Jul 2, 2026

Copy link
Copy Markdown
Author

@cursor review

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 670914f. Configure here.

@p0mvn
p0mvn changed the base branch from split/vct-verify-core to ironwood-main July 2, 2026 21:40
@p0mvn
p0mvn force-pushed the split/vct-runtime branch from 3264de3 to cf0d4aa Compare July 2, 2026 21:40
@p0mvn
p0mvn merged commit f698f23 into ironwood-main Jul 2, 2026
44 of 46 checks passed
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.

2 participants