Skip to content

feat(world-state): support prefilled nullifiers in genesis state#24567

Open
spalladino wants to merge 1 commit into
merge-train/spartan-v5from
spl/genesis-prefilled-nullifiers
Open

feat(world-state): support prefilled nullifiers in genesis state#24567
spalladino wants to merge 1 commit into
merge-train/spartan-v5from
spl/genesis-prefilled-nullifiers

Conversation

@spalladino

@spalladino spalladino commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What

Adds opt-in plumbing to seed nullifier leaves into the genesis nullifier tree, threaded through the C++ world state and the TypeScript world-state layer. The field is optional and defaults to empty, so production genesis is unchanged — every existing genesis root stays bit-identical to today.

  • C++: WorldState constructors, the napi binding, create_canonical_fork, and the standalone aztec-wsdb IPC server gain a prefilled_nullifiers input, inserted as the nullifier tree's initial leaves. The indexed nullifier tree requires those leaves to be unique and strictly increasing (and distinct from the padding leaves), which the tree already enforces; the TS side additionally checks it before the native call for a friendlier error.
  • TS: GenesisData.prefilledNullifiers (optional, defaults to []) threads through native_world_state_instance.ts. getGenesisValues gains an optional prefilledNullifiers param (sorted ascending on the way in), and the GENESIS_ARCHIVE_ROOT fast-path in generateGenesisValues now also checks the nullifier list is empty, so the short-circuit only fires for the canonical empty genesis.

Why

Mechanism for round-4 e2e speedup PR 1b, which will seed the standard-contract (AuthRegistry / PublicChecks / HandshakeRegistry) registration nullifiers at genesis in the e2e fixtures so their publish txs short-circuit. This PR carries no consumers and is intentionally behavior- and timing-neutral: with the field empty (the only value ever passed in production), the native tree, all genesis roots, and the archive root are identical to before.

Relationship to #24254

This is a deliberate subset port of #24254 ("seed protocol contract registration nullifiers at genesis"), kept shape- and signature-identical wherever it does not diverge, so that when #24254 itself reaches the v5 line the mechanism merges as "already applied". The intentional divergences:

  • prefilledNullifiers is optional and defaults to empty here (feat(protocol)!: seed protocol contract registration nullifiers at genesis #24254 made it required and non-empty by default via DEFAULT_GENESIS_DATA).
  • This PR does not port DEFAULT_GENESIS_DATA, the protocol-contract nullifier generation (protocol-contracts/src/genesis_data.ts, scripts/generate_data.ts), or any constant recomputation (GENESIS_ARCHIVE_ROOT, constants.nr, constants.gen.ts, ConstantsGen.sol, aztec_constants.hpp, checkpoint / AVM golden fixtures). With the field empty, every genesis root is unchanged, which is the invariant CI must prove.

Testing (local)

  • C++ world_state_tests: GetInitialTreeInfoForAllTrees (unchanged — still asserts the current empty-nullifier root 0x18935581… and GENESIS_ARCHIVE_ROOT, proving empty-field behavior is bit-identical), GetInitialTreeInfoWithPrefilledPublicData (updated to pass an empty nullifier vector), and a new GetInitialTreeInfoWithPrefilledNullifiers (seeding changes the root, tree size stays 128, seeded leaves are present) — 3/3 pass.
  • @aztec/world-state testing.test.ts (7/7): empty genesis returns GENESIS_ARCHIVE_ROOT via the fast path; the empty-genesis computed root matches that constant; an explicit empty prefilledNullifiers yields the same root as omitting the field; a non-empty sorted list yields a deterministic root that differs from empty; getGenesisValues sorts and seeds; and non-strictly-increasing (descending / duplicate) lists are rejected by the defensive check.
  • Regression: @aztec/world-state native_world_state.test.ts (53/53), @aztec/stdlib l2_block.test.ts (3/3, confirms the genesis nullifier root is unchanged), @aztec/prover-client lightweight_checkpoint_builder.test.ts (7/7).
  • Full yarn build, plus yarn format/yarn lint on the touched packages, are clean.

Notes

This PR is intentionally behavior- and timing-neutral; no e2e behavior changes until PR 1b adopts the mechanism.

Measured impact

None expected, and none observed. prefilledNullifiers defaults to [], so the production genesis
payload and roots are bit-identical (asserted by the unchanged C++ empty-nullifier root and
GENESIS_ARCHIVE_ROOT tests); no fixture opts into a non-empty list in this PR.

CI timings confirm neutrality: per-suite beforeHooks deltas scatter in both directions within the
per-suite noise floor — largest were runtime_config −12.8s, token_bridge −12.7s, account_init +11.5s,
failures +11.0s — with the fees suites essentially flat (private_payments −3.4s, gas_estimation
−0.2s) and no systematic direction. The summed beforeHooks delta over all 276 suites present in both
runs is −122.8s (~−2%), well inside accumulated run-to-run variance.

Baseline CI run 1783374468714213 (merge-base e1711d3, full). PR CI run 1783389062016888 (x-fast).

Part of A-1404

Add opt-in plumbing to seed nullifier leaves into the genesis nullifier
tree, without changing production genesis. The field defaults to empty,
so every existing genesis root stays bit-identical to today.

- C++ WorldState constructors, the napi binding, create_canonical_fork
  and the standalone wsdb server gain a prefilled_nullifiers input,
  inserted as the nullifier tree's initial leaves. The indexed tree
  requires those leaves to be unique and strictly increasing.
- GenesisData.prefilledNullifiers (optional, defaults to []) threads
  through the native world state; the TS side enforces the ordering
  invariant before the native call, and getGenesisValues gains an
  optional prefilledNullifiers param (sorted ascending) plus a fast-path
  guard so the GENESIS_ARCHIVE_ROOT short-circuit only fires for the
  canonical empty genesis.

This is a subset port of #24254 kept shape-identical to it: it
deliberately omits DEFAULT_GENESIS_DATA, the protocol-contract nullifier
generation and all genesis-constant recomputation. When #24254 reaches
the v5 line the mechanism will already exist.

Coverage: a new C++ WorldStateTest.GetInitialTreeInfoWithPrefilledNullifiers
plus world-state testing.test.ts cases proving empty-field equivalence,
deterministic non-empty roots, and rejection of unsorted/duplicate leaves.
@spalladino spalladino added the ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure label Jul 7, 2026
@spalladino spalladino added the wip Work in progress label Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure wip Work in progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant