Skip to content

fix(world-state): make block 0 a first-class historical block#22711

Merged
spalladino merged 2 commits intomerge-train/spartanfrom
spl/fix-genesis-world-state-again
Apr 22, 2026
Merged

fix(world-state): make block 0 a first-class historical block#22711
spalladino merged 2 commits intomerge-train/spartanfrom
spl/fix-genesis-world-state-again

Conversation

@spalladino
Copy link
Copy Markdown
Contributor

Motivation

PXE proves a tx anchored to the genesis block (block 0). When the node has already advanced past block 0, the node's getWorldState(genesisHeaderHash) short-circuited to getCommitted(), which returns the latest committed tip instead of genesis state. The public-data-tree witnesses computed against the tip diverge from the genesis root the kernel circuit checks against, firing assert(is_leaf_in_tree, "Proving public value inclusion failed").

The deeper cause is that block 0 was never a first-class historical block in world state: trees persisted a BlockPayload only from block 1 onwards, and low-level tree ops explicitly threw on blockNumber == 0.

Approach

Persist a BlockPayload for block 0 at tree genesis (captured from meta.initialRoot / meta.initialSize in commit_genesis_state). Remove the read-path blockNumber == 0 throw guards now that get_block_data(0) succeeds and returns the correct initial state. On the aztec-node, resolve the genesis anchor hash to BlockNumber.ZERO and fall through to the standard snapshot + archive-root double-check path instead of short-circuiting to committed.

Changes

  • barretenberg (cached_content_addressed_tree_store): write a block-0 payload in commit_genesis_state, plus a block-index entry for trees with non-empty initial state (NULLIFIER, PUBLIC_DATA, ARCHIVE).
  • barretenberg (append_only_tree, indexed_tree): drop the blockNumber == 0 throw guards on read paths (get_sibling_path, get_leaf, find_leaf_indices_from, find_leaf_sibling_paths, find_low_leaf). Destructive guards (unwind/remove/finalize at block 0) are preserved.
  • aztec-node (server.getWorldState): resolve the initial header hash to BlockNumber.ZERO and reuse the standard snapshot path so genesis-anchored queries see genesis state even after the tip advances.
  • aztec-node (server.test): the initial header hash test now asserts getSnapshot(BlockNumber.ZERO) is called and the archive double-check passes.
  • end-to-end (e2e_genesis_timestamp): unskip the regression test that proves a second genesis-anchored deploy after a prior deploy has modified the public data tree.

spalladino and others added 2 commits April 21, 2026 17:21
Persist a BlockPayload for block 0 at tree genesis so historical queries
pinned to the initial state resolve against the block-0 snapshot instead
of throwing or silently returning the latest committed tip.

- cached_content_addressed_tree_store.hpp: write a block-0 payload
  (root=initialRoot, size=initialSize) in commit_genesis_state so
  get_block_data(0) succeeds.
- append_only_tree / indexed_tree: drop the blockNumber==0 throw guards
  on read paths (get_sibling_path, get_leaf, find_leaf_indices_from,
  find_leaf_sibling_paths, find_low_leaf) now that genesis reads have a
  valid payload to resolve against.
- aztec-node server.getWorldState: stop short-circuiting to getCommitted
  when the anchor matches the genesis header. Resolve to BlockNumber.ZERO
  and fall through to the standard snapshot + archive-root double-check
  so proving against the genesis anchor returns genesis state even after
  the node tip has advanced.
- e2e_genesis_timestamp: unskip the regression test exercising the bug
  (prove a genesis-anchored tx after the chain has advanced with public
  data tree changes).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@spalladino spalladino changed the base branch from next to merge-train/spartan April 21, 2026 20:45
@spalladino spalladino force-pushed the spl/fix-genesis-world-state-again branch from bb08d96 to 0ecbf04 Compare April 21, 2026 21:07
@spalladino spalladino enabled auto-merge (squash) April 21, 2026 21:07
@spalladino spalladino force-pushed the spl/fix-genesis-world-state-again branch from 48236ce to bb08d96 Compare April 21, 2026 21:17
@@ -0,0 +1 @@
{"sessionId":"085655bd-c5a7-430d-b256-9581219d8ab1","pid":14351,"acquiredAt":1776775336648} No newline at end of file
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Did you mean to push this?

Copy link
Copy Markdown
Contributor

@alexghr alexghr left a comment

Choose a reason for hiding this comment

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

Looks good, just an extra file that needs to be gitignore

@spalladino spalladino merged commit 37154e5 into merge-train/spartan Apr 22, 2026
30 of 31 checks passed
@spalladino spalladino deleted the spl/fix-genesis-world-state-again branch April 22, 2026 08:43
BlockPayload genesisBlock{ .size = meta.initialSize, .blockNumber = 0, .root = meta.initialRoot };
dataStore_->write_block_data(0, genesisBlock, *tx);
if (meta.initialSize > 0) {
dataStore_->write_block_index_data(0, meta.initialSize, *tx);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The world state has an API that allows querying for the earliest block number where the tree was at a certain size. This conditional means it would never return block number 0 for index 0. This may be fine, I just wanted to flag it.

PhilWindle pushed a commit that referenced this pull request Apr 22, 2026
…e size (#22724)

## Motivation

Follow-up to #22711 addressing a review flag from PhilWindle:
`commit_genesis_state` only wrote a block-index entry for block 0 when
`initialSize > 0`. This created a minor asymmetry with the regular
commit path, which writes `write_block_index_data` unconditionally for
every block (including zero-size blocks).

## Approach

Drop the `if (meta.initialSize > 0)` guard so genesis state is indexed
the same way as any other block. The read API (`find_block_for_index`)
uses `get_value_or_greater(index + 1)`, so a `sizeAtBlock=0` entry is
unreachable and the extra write is harmless — it just restores the
invariant that every committed block appears in the index-to-block
database.

## Changes

- **barretenberg (cached_content_addressed_tree_store)**: remove the
`initialSize > 0` guard around the block-0 `write_block_index_data` call
so the genesis commit matches the regular commit path.
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.

3 participants