Skip to content

chore: add a regeneration flow for the genesis constants - #25503

Merged
spalladino merged 1 commit into
spl/a-1264-genesis-protocol-nullifiersfrom
spl/a-1264-genesis-constants-regen
Sep 18, 2026
Merged

spalladino merged 1 commit into
spl/a-1264-genesis-protocol-nullifiersfrom
spl/a-1264-genesis-constants-regen

Conversation

@spalladino

Copy link
Copy Markdown
Contributor

Stacked on #25497 — review that one first; this PR's diff is only the two commits on top.

Answers the review comment on #25497: "Is there a signposted automated regeneration flow that a person/ai can do to easily update them? I wonder if a git commit hook might be useful to warn people about this."

The problem

The genesis nullifier tree is seeded with the protocol contracts' registration nullifiers, which derive from their class ids. Anything that rotates a class id — a protocol contract change, a compiler bump, a transpiler change — moves the genesis roots, and several tracked files record them. Before this PR that was five hand-pinned locations, one of them inside the labs patch series, discoverable only via a late CI failure.

Reduce the pins first, then automate

Rather than automate the rewriting of five duplicated literals, most of them are removed:

  • New GENESIS_NULLIFIER_TREE_ROOT in constants.nr. The existing codegen already projects it into aztec_constants.hpp, ConstantsGen.sol and constants.gen.ts, so the Noir, C++ and TypeScript genesis tests now all read one constant instead of repeating the literal.
  • The six seed nullifiers move out of world_state.test.cpp into a generated genesis_protocol_nullifiers.hpp.

Two hand-pinned locations remain (constants.nr and that header), and none inside labs/ — which matters, because a file inside the patch series cannot be rewritten by a script that "never commits": persisting a labs edit requires a commit plus bootstrap.sh export.

The regeneration flow

noir-projects/fnd/scripts/regenerate_genesis_constants.sh --fixtures

Single source of truth, modelled on noir/scripts/bump_noir_compiler.sh. Phases: preflight → measure → validate → update → regenerate consumers → verify → stage. It does not commit.

flag effect
(none) rewrites the constants and the seed header; reports the L1 fixtures as outstanding
--fixtures also regenerates the six L1 checkpoint fixtures (needs anvil)
--check measures and compares only, writes nothing, non-zero exit if stale

Three deliberate design points, all from review:

  • Fail-fast, not best-effort. The Noir bump script degrades gracefully because a partial bump still leaves a useful diff. Here a half-applied regeneration leaves constants that disagree with each other, so a missing dependency, a failed measurement or a replacement that did not match is a hard error. A replacement must match its named declaration exactly once.
  • It rebuilds constants-codegen before the remake scripts. Those read the inputs embedded when the package was built, so running them against an edited constants.nr without rebuilding will cheerfully reproduce the previous values and report success. This bit me during development.
  • It refuses to run against a build older than the protocol contract sources, rather than measuring stale artifacts.

aztec compute-genesis-values now also reports the canonical measurement, through the same shared measureCanonicalGenesis helper the script reads, so there is one computation rather than two that can drift.

Precommit reminder

Fires when staged changes touch the protocol contract sources, the Noir submodule pin or the transpiler. It names the regeneration command and never blocks.

It is explicitly a reminder, not evidence of freshness — deciding whether a class id actually moved means compiling the contracts and computing the ids, far too slow for a hook, so the path predicate is a cheap and deliberately incomplete proxy (a shared library the contracts pull in can move an id without tripping it). It also does not go quiet merely because constants.nr is staged: only a staged edit to the GENESIS_ declarations themselves counts as handled.

What it deliberately does not touch

mainnet_compatibility.test.ts and testnet_compatibility.test.ts pin what a live network was actually deployed with. A class id rotation fails those too, and that is correct — they change only at a governance upgrade. The script never rewrites them, and both the skill and the CLAUDE.md block say so explicitly, because the tempting "fix" is to paste in the new values, which silently claims the deployed network moved.

Signposting

.claude/skills/genesis-constants/ plus a <genesis_constants> block in the root CLAUDE.md, mirroring how bump_noir_compiler.sh is surfaced through noir-sync-update.

Validation

  • --check against the current tree: measures independently and confirms all three constants, the seed header and the fixtures agree.
  • Red/green on the write path: corrupted GENESIS_ARCHIVE_ROOT--check exits 1 naming the stale constant → a plain run restores it and regenerates the derived outputs. The verify phase caught a real bug in its own Solidity check (grouped digits) during that run.
  • Precommit reminder: fires on a staged protocol contract edit, silent once the GENESIS_ constants are staged too.
  • nargo test --package types hash_of_genesis — 1 passed.
  • world_state_tests --gtest_filter='*GetInitialTreeInfo*' — 3 passed.
  • stdlib/src/block/l2_block.test.ts (3), world-state/src/testing.test.ts (7), protocol-contracts/src/genesis_data.test.ts (2) — passed.
  • labs-patches/bootstrap.sh check — clean. Patch 0009 is unchanged; the labs changes here are a new 0010.

Relates to A-1264.

Rotating a protocol contract class id moves the genesis roots, and until
now the only way to find out was a late CI failure, with no documented way
to fix it beyond re-deriving the numbers by hand.

Reduce the pins before automating them. The genesis nullifier-tree root was
repeated as a literal in the Noir block-header test, the C++ world-state
test and the TypeScript l2_block test, and the six seeded nullifiers were
duplicated between the generated protocol contract data and the C++ test.
GENESIS_NULLIFIER_TREE_ROOT now lives in constants.nr, which the existing
codegen projects into aztec_constants.hpp, ConstantsGen.sol and
constants.gen.ts, and all three tests read it; the C++ seed vector moves
into a generated header. That leaves two hand-pinned locations rather than
five, and none inside the labs patch series, so regenerating no longer
means editing a patch.

`noir-projects/fnd/scripts/regenerate_genesis_constants.sh` is then the
single source of truth: it measures the canonical genesis from a real
ephemeral world state, rewrites the constants and the C++ seed vector,
re-runs the generators, verifies they picked up the new values, and with
--fixtures regenerates the six L1 checkpoint fixtures. --check reports
staleness without writing. Bash orchestrates; a small node helper does the
JSON validation and the constants.nr rewrite, since both need real parsing
and must fail loudly rather than silently no-op.

Unlike the Noir bump script it is fail-fast rather than best-effort: a
missing dependency, a failed measurement or a replacement that did not
match aborts, because a half-applied regeneration leaves constants that
disagree with each other. It also rebuilds constants-codegen before the
remake scripts, which otherwise reproduce the previous values from the
inputs embedded at build time.

Two things only an end-to-end run surfaced. The build-freshness preflight
warns rather than aborts: the fnd precommit hook rewrites every protocol
contract .nr through `nargo fmt` on any commit that stages one, so their
mtimes move without their content changing and a hard check fires on a
perfectly fresh tree. And the checkpoint fixture producer picks a
wall-clock timestamp and a random coinbase and fee recipient per run, so
--fixtures rewrites all six whether or not the genesis moved; only
lastArchiveRoot is stable, which is what --check keys on.

A precommit reminder fires when staged changes can rotate a class id. It
is explicitly a reminder and not evidence of freshness: deciding whether
an id actually moved means compiling the contracts, which is far too slow
for a hook, so its path predicate is a cheap and incomplete proxy.
@spalladino
spalladino force-pushed the spl/a-1264-genesis-constants-regen branch from 65ffb86 to 064e570 Compare September 17, 2026 18:34
@spalladino
spalladino merged commit 833ee8c into next Sep 18, 2026
17 checks passed
@spalladino
spalladino deleted the spl/a-1264-genesis-constants-regen branch September 18, 2026 18:09
charlielye added a commit that referenced this pull request Sep 18, 2026
Moves the world-state DB service (world_state engine, persistent
content-addressed merkle storage, lmdb tree store, IPC server) out of
barretenberg into native-packages/wsdb, compiling with zero barretenberg
headers: bb is linked only as a prebuilt archive for the poseidon2 c_bind and
for the new bb_wsref_* C ABI over the in-memory reference world state
(world_state_reference), against which this package's conformance test drives
its WorldState and asserts agreement on roots, sibling paths, low-leaf
lookups, preimages and checkpointing.

Constants stay in lockstep via a wsdb-local remake-constants hook on the
protocol constants-codegen (generated header, no longer checked in).

Rebased onto next after the @aztec-foundation scope rename (#25328). wsdb keeps
the name next gave it; the new kvdb package follows the same scope rather than
introducing an @aztec-scoped foundation package. The labs series is re-exported
against the current pin: the wsdb/kvdb consumption patch is rewritten for the new
scope, and the indexed/nullifier tree reference update is dropped because
upstream removed that references frontmatter, leaving only its cspell additions.

Rebased onto next: the labs patch series is re-derived against the current pin
(42d7d24b) and renumbered -- next's own scope patch has been absorbed upstream and
dropped out. scripts/labs_fnd_hashes.sh keeps next's acvm -> noir-execute rename
alongside the native-packages/{wsdb,kvdb} component entries.

A test pins the on-disk lmdb key bytes, which nothing covered: the bb-linked
parity target compiles only field_element.test.cpp, so it checks fr's hash,
msgpack and ordering but not how a key reaches disk -- and lmdblib's concrete
serialise_key(const uint256_t&) became a generic template that memcpys from
&key rather than uint256_t::data, which agree only while data[4] is the sole
member at offset 0.

Rebased onto next: the labs series is re-derived against the current pin and our
three patches renumbered to 0016-0018, since next's own series has grown into
0002-0015 and the old numbering collided. The barretenberg-side wsdb bootstrap
and CMakeLists carry #25403's flag removal into their native-packages homes,
which next applied to the paths this moves.

The NAPI scaffolding moves rather than being copied. lmdb_store_wrapper was its
only consumer -- msgpack_client includes just ipc_client.hpp and napi.h, and
nothing outside the module referenced barretenberg/messaging -- so extracting the
store orphaned util/{promise,async_op,message_processor} and messaging/{dispatcher,
header} on the bb side. Deleting them there leaves nodejs_module as init_module.cpp
plus msgpack_client (still needed until #25362 removes it), and lets the diff read
as renames instead of ~450 lines of apparently new code. stream_parser.hpp goes
with them: it was already unreferenced and only header.hpp kept it compiling.

The five bin32 wire aliases (Fr, Nullifier, PublicDataSlot, PublicDataValue,
BlockHeaderHash) are the same 32 canonical bytes as FieldElement under a nominal
tag, so FieldElement now converts to and from any of them implicitly, detected by
the marker typedef codegen stamps on ipc::Bin32Alias (no generated header reaches
field_element.hpp). That removes the ten per-alias converter functions and the
fr-vector helpers from the wsdb converter; the composite converters reduce to
aggregate init. bb's copy gets the equivalent for bb::fr without touching field.hpp:
a to_wire proxy convertible to whichever alias the call site expects, and one
from_wire template.

Rebased onto next: the genesis seeding (#25497, #25503) landed in bb's world_state,
so its generated seed header moves with the rest of world_state into
native-packages/wsdb in wsdb's own types, regenerate_genesis_constants.sh and the
genesis-constants skill point at the new path, GENESIS_NULLIFIER_TREE_ROOT joins
wsdb's constants selection, and the three labs patches renumber to 0021-0023 behind
next's grown series.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EUKia6wteDk9kZZuT2Gju
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