perf(e2e): seed standard contracts at genesis#24568
Open
spalladino wants to merge 1 commit into
Open
Conversation
Make e2e environments start with the standard contracts (AuthRegistry, PublicChecks, HandshakeRegistry) already "published" so the ensure*Published setup helpers skip their two publish txs each. At production cadence setup:auth-registry alone was ~16 min/run (2 sequential txs per process); this collapses it to sub-second. Three parts, all default-off outside e2e: - Genesis nullifiers: per standard contract, seed siloNullifier(ContractClassRegistry, classId) and siloNullifier(ContractInstanceRegistry, instanceAddress) into the fixtures' getGenesisValues call (new 5th param from PR 1a). These are exactly the nullifiers the publish txs would emit, so the AVM deployment-nullifier check passes when the contracts are called. Seeded consistently across all four e2e genesis builders (setup, prover fixture, p2p network, add_rollup) since the latter three recompute a genesis that must match the L1-deployed archive root. - Archiver preload: registerStandardContracts mirrors registerProtocolContracts, seeding class/instance/public-function signatures into the contract store at block 0. Gated behind a new test-only archiver config flag (testPreloadStandardContracts, default false), set from the e2e node config so every spawned node (validators, prover nodes) picks it up. This is what makes the wallet guards (getContractClass / getContract, both archiver-store reads) short-circuit both publish txs. Gating avoids recreating the A-1257 publish-collision bug: production genesis has no matching nullifiers. The two publish txs write only their respective nullifiers plus a class/instance broadcast log (no public data), so no genesisPublicData is needed; the store preload replaces the log-consumption path. The ensure*Published helpers are unchanged, so if seeding ever regresses the tx path re-engages and tests still pass (just slow).
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.
Round-4 e2e speedup, PR 1b (adoption half of the AuthRegistry prize).
Stacked on #24567 (
spl/genesis-prefilled-nullifiers, the genesis-nullifier mechanism). This PR targets that branch; retarget tomerge-train/spartan-v5once #24567 merges.What
Make e2e environments start with the standard contracts (AuthRegistry, PublicChecks, HandshakeRegistry) already "published", so
ensureAuthRegistryPublishedand its two siblings skip their two publish txs each. At production cadencesetup:auth-registryalone was ~16 min/run (2 sequential txs per process, paid per-process across the suite). This collapses that span to sub-second.Mechanism (three parts, all default-off outside e2e)
Genesis nullifiers. Per standard contract, seed
siloNullifier(ContractClassRegistry, classId)andsiloNullifier(ContractInstanceRegistry, instanceAddress)(the real derived address — standard contracts are not magic-address protocol contracts) into the fixtures'getGenesisValuescall, via PR 1a's new 5th param. These are exactly the nullifiers the publish txs would emit, so the AVM's deployment-nullifier check passes when the contracts are called publicly. A single helper (getStandardContractGenesisNullifiers) feeds all four e2e genesis builders (fixtures/setup.ts,e2e_prover_test.ts,p2p/p2p_network.ts,multi-node/governance/add_rollup.test.ts) — the latter three recompute a genesis that must reproduce the L1-deployed archive root, so they must seed the identical set. Non-e2e callers (cli, sandbox/local-network) are untouched.Archiver preload.
registerStandardContractsmirrorsregisterProtocolContracts: it seeds each contract's class (with recomputedpublicBytecodeCommitment), instance, and public-function signatures into the contract store at block 0, reading the bundled@aztec/standard-contractsartifacts. It is idempotent (skips already-registered classes on restart) and gated behind a new archiver config flagtestPreloadStandardContracts(envTEST_PRELOAD_STANDARD_CONTRACTS, default false). The flag is set from the e2e node config so every spawned node (validators, prover nodes) picks it up through the normal config path. This adds@aztec/standard-contractsas an archiver dependency.Guard short-circuit. The
ensure*Publishedhelpers are unchanged. Their guards readwallet.getContractClassMetadata(id).isContractClassPubliclyRegistered(toaztecNode.getContractClass) andwallet.getContractMetadata(addr).isContractPublished(toaztecNode.getContract) — both are archiver-store reads, not nullifier-tree reads. The store preload alone makes both short-circuit; the genesis nullifiers are what make the contract actually callable in the AVM afterwards. Keeping the helpers doubles as a regression check: if seeding ever breaks, the tx path re-engages and tests still pass, just slow.Why the flag is test-only (A-1257 rationale)
Preloading unconditionally in production would recreate the A-1257 collision #24254 fixed: a real on-chain publish of a preloaded class would collide with the block-0 preload, because production genesis will not carry the matching nullifiers. The flag is only set by the e2e fixtures, which also seed the nullifiers, keeping the store and the nullifier tree consistent. A single source of truth (
getPublishableStandardContracts) drives both the preloaded set and the seeded-nullifier set so they cannot drift.State-write verification
Inspected the two publish txs on this base:
ContractClassRegistry.publishpushes one nullifier (classId, siloed to the class-registry address) and broadcasts aContractClassPublishedcontract-class log carrying the packed bytecode. No public-data writes.ContractInstanceRegistry.publish_for_public_executionasserts the class-registration nullifier exists, then pushes one nullifier (address, siloed to the instance-registry address) and broadcasts aContractInstancePublishedprivate log. No public-data writes.So the full state-write set is two nullifiers + two broadcast logs. The nullifiers are replicated via genesis
prefilledNullifiers; the archiver normally learns the class/instance from those two logs (data_store_updater.ts), and the block-0 preload replaces that path exactly. NogenesisPublicDatais required. PXE-side registration (wallet.registerContract) is unaffected and still runs in the helpers.Local verification
automine/accounts/authwit.test.ts(public-authwit path against the standard AuthRegistry), timing on: passes;setup:auth-registryspan = 10 ms (was ~33 s at production cadence). No on-chain AuthRegistry publish (the onlyContractClassRegistry.publishexecutions are the test's own AuthWitTest/GenericProxy deploys); the public AuthRegistry contract executes fine against the seeded nullifier.testPreloadStandardContracts: trueconfirmed in the node config dump.single-node/fees/account_init.test.ts(production sequencer + simulated prover node), timing on: 5/5 pass;setup:auth-registryspan = 10 ms. Prover node syncs against the seeded genesis (no root divergence), and the flag propagates to it. No duplicate-nullifier errors.archiver/src/modules/data_store_updater.test.ts:registerStandardContractspreloads each publishable standard contract's class + instance into the store and is idempotent on a second call.Expected impact
~14–16 min/run from auth-registry alone, plus more from the two untagged siblings (PublicChecks / HandshakeRegistry) wherever used.
Measured impact
setup:auth-registrycollapses from 702.2s of setup time run-wide (39 occurrences, 5–35s each) to482ms total (max 87ms per occurrence) — sub-second run-wide, every shard clean, no seeding or
flag-propagation slow path.
untagged sibling publishes (PublicChecks, HandshakeRegistry) are removed too. Example: account_init
beforeHooks −49.6s vs a 30.9s tagged auth-registry span; the extra ~19s is the two siblings.
private_payments.parallel −41.8s/shard (×8), gas_estimation.parallel −41.5s/shard (×3),
l1_to_l2 −38.1s, l2_to_l1 −37.5s, l1_to_l2_inbox_drift −38.2s, token_bridge −37.7s,
fee_juice_payments −37.4s, bot −34.3s; automine/parallel suites shed 5–15s each (their
fast-cadence publishes).
(wall-clock benefit is smaller, since shards run in parallel across workers).
Baseline CI run 1783389062016888 (#24567
ci/x-fast; mechanism-only, so timing-neutral vsmerge-base). PR CI run 1783391194852197 (#24568
ci/x-fast).Notes for downstream
applyEnsureAuthRegistryPublishednow short-circuits; the fees setup chain loses the ~32 s/shard auth-registry step, so rebase the overlap/batch shape onto what remains (token deploy, FPC, mints).setup:auth-registryaround sub-second across the run (10 ms observed locally per process). Any shard still showing tens of seconds means the guards took the slow path (a seeding or flag-propagation bug), not noise.Fixes A-1404