From f6558939dce08c3c1eb83748279fb9858bff9a32 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 5 May 2026 06:04:37 -0300 Subject: [PATCH 01/15] refactor(archiver)!: simplify L2BlockSource block lookups (#22809) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit :warning: **This PR includes #22870. Reviewers should review only the first commit.** :warning: ## Motivation Consolidates the block-related lookup surface on `L2BlockSource` from ~17 narrow methods returning ~9 different shapes down to 4 methods returning 2 shapes (`L2Block` and `BlockData`). Replaces the per-shape getters with discriminated query objects that carry both the lookup discriminant and a single `onlyCheckpointed` filter, removing the parallel `Checkpointed*` API and the throwaway wrapper types. Additionally, this refactor centralizes block-zero handling in the archiver and threads the dynamic initial header through every component that previously hard-coded the constant, eliminating the divergence and removing the special-case branches in callers. ## Approach `L2BlockSource` exposes 4 methods that take query objects: ```ts getBlock(query: BlockQuery): Promise getBlocks(query: BlocksQuery): Promise getBlockData(query: BlockQuery): Promise getBlocksData(query: BlocksQuery): Promise type BlockQuery = ({number} | {hash} | {archive}) & { onlyCheckpointed?: boolean } type BlocksQuery = ({from, limit} | {epoch}) & { onlyCheckpointed?: boolean } ``` On-disk format is unchanged — the archiver already stored block metadata, tx bodies, and per-checkpoint L1/attestation data in separate LMDB maps; `CheckpointedL2Block` was only an in-memory join produced at read time. **Includes changes from https://github.com/AztecProtocol/aztec-packages/pull/22870** ## API surface change ### Methods removed from `L2BlockSource` `getL2Block`, `getL2BlockByHash`, `getL2BlockByArchive`, `getCheckpointedBlock`, `getCheckpointedBlockByHash`, `getCheckpointedBlockByArchive`, `getCheckpointedBlocks`, `getCheckpointedBlocksForEpoch`, `getCheckpointedBlockHeadersForEpoch`, `getBlock(number)`, `getBlocks(from, limit)`, `getBlockData(number)`, `getBlockDataByArchive`, `getBlockDataWithCheckpointContext`, `getBlockHeader`, `getBlockHeaderByHash`, `getBlockHeaderByArchive`. ### Types deleted `CheckpointedL2Block`, `BlockDataWithCheckpointContext` — both removed entirely (file + schema + re-exports). Callers that previously read `.l1` / `.attestations` off these now do `getBlockData(...)` followed by `getCheckpointData(blockData.checkpointNumber)` and read those fields off `CheckpointData`. ### Types added `BlockQuery`, `BlocksQuery` (and matching Zod schemas) on `L2BlockSource`. No new domain types — `L2Block`, `BlockData`, `BlockHeader` are unchanged. ### AztecNode public RPC Method names preserved (`getBlock`, `getBlockHeader`, `getCheckpointedBlocks`, etc. — bodies delegate internally to the new `L2BlockSource` methods). One wire-level change: `AztecNode.getCheckpointedBlocks` element type goes `CheckpointedL2Block[]` → `BlockResponse[]`, forced by the type deletion. Older RPC clients that parse the old shape will need to update. ## Changes - **stdlib**: `BlockQuery` / `BlocksQuery` types + Zod schemas next to `L2BlockSource`. `CheckpointedL2Block` file deleted; `BlockDataWithCheckpointContext` removed from `block_data.ts`. `ArchiverApiSchema` and `MockArchiver` shrunk; new `it()` blocks cover each query discriminant. `L2BlockStream` migrated. - **archiver**: `BlockStore` consolidates to four query-object reads plus iterators. `data_source_base.ts` adds `resolveBlocksQuery` that translates `{ epoch }` → `{ from, limit }` (returns `null` for empty epochs so callers short-circuit to `[]`). Mocks honor `onlyCheckpointed`. - **aztec-node**: `server.ts` keeps the public RPC method names but delegates to the new query methods. `getCheckpointedBlocks` adds a per-call `Map` cache to avoid an N+1. - **consumer migrations**: `world-state`, `txe`, `p2p` block-txs handler, `validator-client` (`validator.ts`, `proposal_handler.ts`), `pxe` block-stream source (honors `onlyCheckpointed` via `node.getL2Tips`), `prover-node`, `sequencer-client`, `telemetry-client`, `aztec/testing`, `L2BlockStream` in stdlib. - **tests**: per-package mocks updated for the new shapes; new test covers `getBlocks({ epoch })` empty-epoch returning `[]`. --- .../archiver/src/archiver-misc.test.ts | 7 +- .../archiver/src/archiver-store.test.ts | 283 +++++- .../archiver/src/archiver-sync.test.ts | 37 +- yarn-project/archiver/src/archiver.ts | 50 +- yarn-project/archiver/src/errors.ts | 24 +- yarn-project/archiver/src/factory.ts | 16 +- .../modules/contract_data_source_adapter.ts | 8 +- .../archiver/src/modules/data_source_base.ts | 311 ++++-- .../src/modules/data_store_updater.test.ts | 2 +- .../src/modules/data_store_updater.ts | 8 +- .../archiver/src/modules/l1_synchronizer.ts | 16 +- .../archiver/src/store/block_store.test.ts | 908 ++++++++++++++---- .../archiver/src/store/block_store.ts | 314 ++---- .../archiver/src/store/l2_tips_cache.ts | 19 +- .../archiver/src/test/mock_l2_block_source.ts | 395 ++++---- .../archiver/src/test/noop_l1_archiver.ts | 19 +- .../src/aztec-node/block_response_helpers.ts | 25 +- .../aztec-node/src/aztec-node/server.test.ts | 165 ++-- .../aztec-node/src/aztec-node/server.ts | 332 +++---- .../aztec-node/src/sentinel/sentinel.test.ts | 6 +- .../aztec-node/src/sentinel/sentinel.ts | 4 +- .../aztec/src/cli/aztec_start_action.ts | 3 - .../aztec/src/cli/aztec_start_options.ts | 6 - .../aztec/src/cli/cmds/start_archiver.ts | 56 -- .../aztec/src/testing/epoch_test_settler.ts | 5 +- .../src/composed/ha/e2e_ha_full.test.ts | 12 +- .../src/e2e_epochs/epochs_ha_sync.test.ts | 4 +- .../epochs_high_tps_block_building.test.ts | 17 +- .../e2e_epochs/epochs_mbps.parallel.test.ts | 8 +- .../e2e_l1_publisher/e2e_l1_publisher.test.ts | 82 +- .../e2e_multi_validator_node.test.ts | 10 +- .../src/e2e_p2p/gossip_network.test.ts | 5 +- .../e2e_p2p/gossip_network_no_cheat.test.ts | 6 +- .../e2e_p2p/preferred_gossip_network.test.ts | 5 +- .../end-to-end/src/e2e_synching.test.ts | 11 +- .../src/branded-types/block_number.test.ts | 19 + .../src/branded-types/block_number.ts | 22 +- .../kv-store/src/stores/l2_tips_store.test.ts | 3 +- .../kv-store/src/stores/l2_tips_store.ts | 5 +- yarn-project/p2p/src/client/factory.ts | 5 +- .../p2p/src/client/p2p_client.test.ts | 31 +- yarn-project/p2p/src/client/p2p_client.ts | 21 +- .../proposal_tx_collector_worker.ts | 1 + .../block_txs/block_txs_handler.test.ts | 12 +- .../protocols/block_txs/block_txs_handler.ts | 4 +- .../src/test-helpers/make-test-p2p-clients.ts | 1 + .../testbench/p2p_client_testbench_worker.ts | 1 + .../src/job/epoch-proving-job.test.ts | 8 +- .../prover-node/src/job/epoch-proving-job.ts | 4 +- .../src/monitors/epoch-monitor.test.ts | 11 +- .../prover-node/src/monitors/epoch-monitor.ts | 2 +- .../prover-node/src/prover-node.test.ts | 8 +- yarn-project/prover-node/src/prover-node.ts | 11 +- .../block_stream_source.test.ts | 85 ++ .../block_synchronizer/block_stream_source.ts | 40 +- .../block_synchronizer.test.ts | 41 +- yarn-project/pxe/src/pxe.test.ts | 12 +- yarn-project/pxe/src/pxe.ts | 12 +- .../src/sequencer/sequencer.test.ts | 9 +- .../src/sequencer/sequencer.ts | 39 +- yarn-project/stdlib/src/block/block_data.ts | 17 - .../stdlib/src/block/block_parameter.test.ts | 49 + .../stdlib/src/block/block_parameter.ts | 59 +- .../stdlib/src/block/checkpointed_l2_block.ts | 70 -- yarn-project/stdlib/src/block/index.ts | 1 - .../stdlib/src/block/l2_block_source.ts | 200 ++-- .../l2_block_stream/l2_block_stream.test.ts | 166 ++-- .../block/l2_block_stream/l2_block_stream.ts | 60 +- .../l2_block_stream/l2_tips_memory_store.ts | 5 + .../l2_block_stream/l2_tips_store_base.ts | 11 +- .../stdlib/src/interfaces/archiver.test.ts | 307 ++---- .../stdlib/src/interfaces/archiver.ts | 41 +- .../stdlib/src/interfaces/aztec-node.test.ts | 68 +- .../stdlib/src/interfaces/aztec-node.ts | 5 +- .../src/interfaces/checkpoint_parameter.ts | 6 +- yarn-project/stdlib/src/tests/mocks.ts | 32 +- .../stdlib/src/world-state/genesis_data.ts | 12 + .../src/wrappers/l2_block_stream.ts | 5 +- .../oracle/txe_oracle_top_level_context.ts | 2 +- .../txe/src/state_machine/archiver.ts | 35 +- .../src/proposal_handler.test.ts | 33 +- .../validator-client/src/proposal_handler.ts | 17 +- .../src/validator.integration.test.ts | 20 +- .../validator-client/src/validator.test.ts | 45 +- .../validator-client/src/validator.ts | 2 +- .../world-state/src/synchronizer/factory.ts | 8 +- .../server_world_state_synchronizer.test.ts | 14 +- .../server_world_state_synchronizer.ts | 15 +- .../world-state/src/test/integration.test.ts | 13 +- 89 files changed, 2820 insertions(+), 2084 deletions(-) delete mode 100644 yarn-project/aztec/src/cli/cmds/start_archiver.ts create mode 100644 yarn-project/pxe/src/block_synchronizer/block_stream_source.test.ts create mode 100644 yarn-project/stdlib/src/block/block_parameter.test.ts delete mode 100644 yarn-project/stdlib/src/block/checkpointed_l2_block.ts diff --git a/yarn-project/archiver/src/archiver-misc.test.ts b/yarn-project/archiver/src/archiver-misc.test.ts index 3b6bbd7b7e2a..433b83dc0f2e 100644 --- a/yarn-project/archiver/src/archiver-misc.test.ts +++ b/yarn-project/archiver/src/archiver-misc.test.ts @@ -10,6 +10,7 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; +import { BlockHeader } from '@aztec/stdlib/tx'; import { getTelemetryClient } from '@aztec/telemetry-client'; import { EventEmitter } from 'events'; @@ -57,7 +58,9 @@ describe('Archiver misc', () => { const instrumentation = mock({ isEnabled: () => true, tracer }); const archiverStore = createArchiverDataStores(await openTmpStore('archiver_misc_test'), { logsMaxPageSize: 1000 }); const events = new EventEmitter() as ArchiverEmitter; - const l2TipsCache = new L2TipsCache(archiverStore.blocks); + const initialHeader = BlockHeader.empty(); + const initialBlockHash = await initialHeader.hash(); + const l2TipsCache = new L2TipsCache(archiverStore.blocks, initialBlockHash); archiver = new Archiver( publicClient, @@ -77,6 +80,8 @@ describe('Archiver misc', () => { l1Constants, synchronizer, events, + initialHeader, + initialBlockHash, l2TipsCache, ); }); diff --git a/yarn-project/archiver/src/archiver-store.test.ts b/yarn-project/archiver/src/archiver-store.test.ts index ff03a8c4d262..2f1a0b770390 100644 --- a/yarn-project/archiver/src/archiver-store.test.ts +++ b/yarn-project/archiver/src/archiver-store.test.ts @@ -1,5 +1,4 @@ import type { BlobClientInterface } from '@aztec/blob-client/client'; -import { GENESIS_ARCHIVE_ROOT } from '@aztec/constants'; import type { EpochCache, EpochCommitteeInfo } from '@aztec/epoch-cache'; import { RollupContract } from '@aztec/ethereum/contracts'; import type { ViemPublicClient } from '@aztec/ethereum/types'; @@ -18,6 +17,7 @@ import { L2Block } from '@aztec/stdlib/block'; import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import { makeStateReference } from '@aztec/stdlib/testing'; import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees'; +import { BlockHeader } from '@aztec/stdlib/tx'; import { getTelemetryClient } from '@aztec/telemetry-client'; import { EventEmitter } from 'events'; @@ -28,6 +28,7 @@ import { BlockNumberNotSequentialError } from './errors.js'; import type { ArchiverInstrumentation } from './modules/instrumentation.js'; import { ArchiverL1Synchronizer } from './modules/l1_synchronizer.js'; import { type ArchiverDataStores, createArchiverDataStores, getArchiverSynchPoint } from './store/data_stores.js'; +import { L2TipsCache } from './store/l2_tips_cache.js'; import { makeChainedCheckpoints } from './test/mock_structs.js'; describe('Archiver Store', () => { @@ -44,10 +45,17 @@ describe('Archiver Store', () => { let epochCache: MockProxy; let archiverStore: ArchiverDataStores; let l1Constants: L1RollupConstants & { l1StartBlockHash: Buffer32; genesisArchiveRoot: Fr }; + let initialHeader: BlockHeader; + let genesisArchiveRoot: Fr; let archiver: Archiver; beforeEach(async () => { const now = +new Date(); + // Build a non-trivial initial header so we can distinguish it from BlockHeader.empty(). + initialHeader = BlockHeader.empty({ lastArchive: new AppendOnlyTreeSnapshot(Fr.fromString('0x1234'), 1) }); + // Genesis archive root is the post-block-0 archive root from L1, distinct from + // initialHeader.lastArchive.root (which is the pre-block-0 archive, always empty in practice). + genesisArchiveRoot = Fr.fromString('0xabcd'); publicClient = mock(); debugClient = publicClient; @@ -73,7 +81,7 @@ describe('Archiver Store', () => { proofSubmissionEpochs: 1, targetCommitteeSize: 48, rollupManaLimit: Number.MAX_SAFE_INTEGER, - genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT), + genesisArchiveRoot, }; const contractAddresses = { @@ -94,6 +102,8 @@ describe('Archiver Store', () => { const events = new EventEmitter() as ArchiverEmitter; const synchronizer = mock(); + const initialBlockHash = await initialHeader.hash(); + const l2TipsCache = new L2TipsCache(archiverStore.blocks, initialBlockHash); archiver = new Archiver( publicClient, debugClient, @@ -106,6 +116,9 @@ describe('Archiver Store', () => { l1Constants, synchronizer, events, + initialHeader, + initialBlockHash, + l2TipsCache, ); }); @@ -115,7 +128,7 @@ describe('Archiver Store', () => { describe('getCheckpoints', () => { it('returns published checkpoints with full checkpoint data', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive }); await archiverStore.blocks.addCheckpoints(testCheckpoints); @@ -131,7 +144,7 @@ describe('Archiver Store', () => { }); it('respects the limit parameter', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive }); await archiverStore.blocks.addCheckpoints(testCheckpoints); @@ -142,7 +155,7 @@ describe('Archiver Store', () => { }); it('respects the starting checkpoint number', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive }); await archiverStore.blocks.addCheckpoints(testCheckpoints); @@ -162,7 +175,7 @@ describe('Archiver Store', () => { describe('getCheckpointsForEpoch', () => { it('returns checkpoints for a specific epoch based on slot numbers', async () => { // l1Constants has epochDuration: 4, so epoch 0 has slots 0-3, epoch 1 has slots 4-7 - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive, makeCheckpointOptions: cpNumber => { @@ -183,7 +196,7 @@ describe('Archiver Store', () => { }); it('returns empty array for epoch with no checkpoints', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); const testCheckpoints = await makeChainedCheckpoints(1, { previousArchive: genesisArchive, makeCheckpointOptions: () => ({ slotNumber: SlotNumber(2) }), // Epoch 0 @@ -196,7 +209,7 @@ describe('Archiver Store', () => { it('returns checkpoints in correct order (ascending by checkpoint number)', async () => { // Create multiple checkpoints all in epoch 0 - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive, makeCheckpointOptions: cpNumber => { @@ -228,14 +241,17 @@ describe('Archiver Store', () => { ...(previousArchive ? { lastArchive: previousArchive } : {}), }); - // Genesis archive for the first block - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + // Genesis archive for the first block — bound in beforeEach so it picks up the suite-level genesisArchiveRoot. + let genesisArchive: AppendOnlyTreeSnapshot; + beforeEach(() => { + genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); + }); it('adds a block to the store', async () => { const block = await makeBlock(BlockNumber(1), IndexWithinCheckpoint(0), genesisArchive); await archiver.addBlock(block); - const retrievedBlock = await archiver.getL2Block(BlockNumber(1)); + const retrievedBlock = await archiver.getBlock({ number: BlockNumber(1) }); expect(retrievedBlock).toBeDefined(); expect(retrievedBlock!.number).toEqual(BlockNumber(1)); expect((await retrievedBlock!.header.hash()).toString()).toEqual((await block.header.hash()).toString()); @@ -250,9 +266,9 @@ describe('Archiver Store', () => { await archiver.addBlock(block2); await archiver.addBlock(block3); - const retrievedBlock1 = await archiver.getL2Block(BlockNumber(1)); - const retrievedBlock2 = await archiver.getL2Block(BlockNumber(2)); - const retrievedBlock3 = await archiver.getL2Block(BlockNumber(3)); + const retrievedBlock1 = await archiver.getBlock({ number: BlockNumber(1) }); + const retrievedBlock2 = await archiver.getBlock({ number: BlockNumber(2) }); + const retrievedBlock3 = await archiver.getBlock({ number: BlockNumber(3) }); expect(retrievedBlock1!.number).toEqual(BlockNumber(1)); expect(retrievedBlock2!.number).toEqual(BlockNumber(2)); @@ -292,7 +308,7 @@ describe('Archiver Store', () => { await archiver.addBlock(block1); - const retrievedBlock = await archiver.getL2Block(BlockNumber(1)); + const retrievedBlock = await archiver.getBlock({ number: BlockNumber(1) }); expect(retrievedBlock).toBeDefined(); expect(retrievedBlock!.number).toEqual(BlockNumber(1)); }); @@ -306,7 +322,7 @@ describe('Archiver Store', () => { await archiver.addBlock(block2); await archiver.addBlock(block3); - const blocks = await archiver.getBlocks(BlockNumber(1), 3); + const blocks = await archiver.getBlocks({ from: BlockNumber(1), limit: 3 }); expect(blocks.length).toEqual(3); expect(await blocks[0].hash()).toEqual(await block1.hash()); expect(await blocks[1].hash()).toEqual(await block2.hash()); @@ -323,7 +339,7 @@ describe('Archiver Store', () => { await archiver.addBlock(block3); // Request only 2 blocks starting from block 1 - const blocks = await archiver.getBlocks(BlockNumber(1), 2); + const blocks = await archiver.getBlocks({ from: BlockNumber(1), limit: 2 }); expect(blocks.length).toEqual(2); expect(await blocks[0].hash()).toEqual(await block1.hash()); expect(await blocks[1].hash()).toEqual(await block2.hash()); @@ -339,7 +355,7 @@ describe('Archiver Store', () => { await archiver.addBlock(block3); // Start from block 2 - const blocks = await archiver.getBlocks(BlockNumber(2), 2); + const blocks = await archiver.getBlocks({ from: BlockNumber(2), limit: 2 }); expect(blocks.length).toEqual(2); expect(await blocks[0].hash()).toEqual(await block2.hash()); expect(await blocks[1].hash()).toEqual(await block3.hash()); @@ -351,7 +367,7 @@ describe('Archiver Store', () => { await archiver.addBlock(block1); // Request blocks starting from block 5 (which doesn't exist) - const blocks = await archiver.getBlocks(BlockNumber(5), 3); + const blocks = await archiver.getBlocks({ from: BlockNumber(5), limit: 3 }); expect(blocks).toEqual([]); }); @@ -363,20 +379,20 @@ describe('Archiver Store', () => { await archiver.addBlock(block2); // Request 10 blocks but only 2 are available - const blocks = await archiver.getBlocks(BlockNumber(1), 10); + const blocks = await archiver.getBlocks({ from: BlockNumber(1), limit: 10 }); expect(blocks.length).toEqual(2); expect(await blocks[0].hash()).toEqual(await block1.hash()); expect(await blocks[1].hash()).toEqual(await block2.hash()); }); }); - describe('getCheckpointedBlocks', () => { + describe('getBlocks with onlyCheckpointed', () => { it('returns checkpointed blocks with checkpoint info', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive }); await archiverStore.blocks.addCheckpoints(testCheckpoints); - const result = await archiver.getCheckpointedBlocks(BlockNumber(1), 100); + const result = await archiver.getBlocks({ from: BlockNumber(1), limit: 100, onlyCheckpointed: true }); const expectedBlocks = testCheckpoints.flatMap(c => c.checkpoint.blocks); expect(result.length).toBe(expectedBlocks.length); @@ -389,11 +405,12 @@ describe('Archiver Store', () => { const cb = result[blockIndex]; const expectedBlock = checkpoint.checkpoint.blocks[i]; - expect(cb.block.number).toBe(expectedBlock.number); + expect(cb.number).toBe(expectedBlock.number); expect(cb.checkpointNumber).toBe(checkpoint.checkpoint.number); - expect(cb.block.archive.root.toString()).toBe(expectedBlock.archive.root.toString()); - expect(cb.l1).toBeDefined(); - expect(cb.l1.blockNumber).toBeGreaterThan(0n); + expect(cb.archive.root.toString()).toBe(expectedBlock.archive.root.toString()); + const checkpointData = await archiverStore.blocks.getCheckpointData(cb.checkpointNumber); + expect(checkpointData?.l1).toBeDefined(); + expect(checkpointData!.l1.blockNumber).toBeGreaterThan(0n); blockIndex++; } @@ -401,40 +418,141 @@ describe('Archiver Store', () => { }); it('respects the limit parameter', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive }); await archiverStore.blocks.addCheckpoints(testCheckpoints); - const result = await archiver.getCheckpointedBlocks(BlockNumber(1), 2); + const result = await archiver.getBlocks({ from: BlockNumber(1), limit: 2, onlyCheckpointed: true }); expect(result.length).toBe(2); - expect(result[0].block.number).toBe(BlockNumber(1)); - expect(result[1].block.number).toBe(BlockNumber(2)); + expect(result[0].number).toBe(BlockNumber(1)); + expect(result[1].number).toBe(BlockNumber(2)); expect(result[0].checkpointNumber).toBe(1); expect(result[1].checkpointNumber).toBe(2); }); it('returns blocks starting from specified block number', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive }); await archiverStore.blocks.addCheckpoints(testCheckpoints); - const result = await archiver.getCheckpointedBlocks(BlockNumber(2), 10); + const result = await archiver.getBlocks({ from: BlockNumber(2), limit: 10, onlyCheckpointed: true }); expect(result.length).toBe(2); - expect(result[0].block.number).toBe(BlockNumber(2)); - expect(result[1].block.number).toBe(BlockNumber(3)); + expect(result[0].number).toBe(BlockNumber(2)); + expect(result[1].number).toBe(BlockNumber(3)); expect(result[0].checkpointNumber).toBe(2); expect(result[1].checkpointNumber).toBe(3); }); it('returns empty array when no checkpointed blocks exist', async () => { - const result = await archiver.getCheckpointedBlocks(BlockNumber(1), 10); + const result = await archiver.getBlocks({ from: BlockNumber(1), limit: 10, onlyCheckpointed: true }); expect(result).toEqual([]); }); }); + describe('getBlocks / getBlocksData with epoch query', () => { + it('returns empty array for epoch with no checkpoints', async () => { + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); + // Checkpoint 1 is in epoch 0 (slot 1, epochDuration=4) + const testCheckpoints = await makeChainedCheckpoints(1, { + previousArchive: genesisArchive, + makeCheckpointOptions: () => ({ slotNumber: SlotNumber(1) }), + }); + await archiverStore.blocks.addCheckpoints(testCheckpoints); + + // Epoch 1 has no checkpoints — both methods must return [] without throwing + await expect(archiver.getBlocks({ epoch: EpochNumber(1), onlyCheckpointed: true })).resolves.toEqual([]); + await expect(archiver.getBlocksData({ epoch: EpochNumber(1), onlyCheckpointed: true })).resolves.toEqual([]); + }); + + it('returns blocks for epoch with checkpoints', async () => { + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); + const testCheckpoints = await makeChainedCheckpoints(2, { + previousArchive: genesisArchive, + makeCheckpointOptions: cpNumber => ({ + slotNumber: SlotNumber(Number(cpNumber) - 1), // Slots 0 and 1, both in epoch 0 + }), + }); + await archiverStore.blocks.addCheckpoints(testCheckpoints); + + const blocks = await archiver.getBlocks({ epoch: EpochNumber(0), onlyCheckpointed: true }); + expect(blocks.length).toBe(2); + expect(blocks[0].number).toBe(BlockNumber(1)); + expect(blocks[1].number).toBe(BlockNumber(2)); + + const blocksData = await archiver.getBlocksData({ epoch: EpochNumber(0), onlyCheckpointed: true }); + expect(blocksData.length).toBe(2); + }); + }); + + describe('getBlock / getBlockData with tag', () => { + it('returns the genesis block for any tag when chain is empty', async () => { + for (const tag of ['proposed', 'checkpointed', 'proven', 'finalized'] as const) { + const block = await archiver.getBlock({ tag }); + expect(block?.number).toBe(BlockNumber.ZERO); + const data = await archiver.getBlockData({ tag }); + expect(data?.header.globalVariables.blockNumber).toBe(BlockNumber.ZERO); + } + }); + + it('resolves proposed to the latest block', async () => { + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); + const testCheckpoints = await makeChainedCheckpoints(3, { + previousArchive: genesisArchive, + blocksPerCheckpoint: 2, + }); + await archiverStore.blocks.addCheckpoints(testCheckpoints); + + const block = await archiver.getBlock({ tag: 'proposed' }); + expect(block?.number).toBe(BlockNumber(6)); + const data = await archiver.getBlockData({ tag: 'proposed' }); + expect(data?.header.globalVariables.blockNumber).toBe(BlockNumber(6)); + }); + + it('resolves checkpointed, proven, and finalized to the corresponding block', async () => { + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); + const testCheckpoints = await makeChainedCheckpoints(3, { + previousArchive: genesisArchive, + blocksPerCheckpoint: 2, + }); + await archiverStore.blocks.addCheckpoints(testCheckpoints); + + // Checkpoint 1 = blocks 1-2, checkpoint 2 = blocks 3-4, checkpoint 3 = blocks 5-6. + // All checkpoints are added so checkpointed L2 block = 6. + await archiverStore.blocks.setProvenCheckpointNumber(CheckpointNumber(2)); + await archiverStore.blocks.setFinalizedCheckpointNumber(CheckpointNumber(1)); + + const checkpointedBlock = await archiver.getBlock({ tag: 'checkpointed' }); + expect(checkpointedBlock?.number).toBe(BlockNumber(6)); + + const provenBlock = await archiver.getBlock({ tag: 'proven' }); + expect(provenBlock?.number).toBe(BlockNumber(4)); + + const finalizedBlock = await archiver.getBlock({ tag: 'finalized' }); + expect(finalizedBlock?.number).toBe(BlockNumber(2)); + + const provenData = await archiver.getBlockData({ tag: 'proven' }); + expect(provenData?.header.globalVariables.blockNumber).toBe(BlockNumber(4)); + }); + + it('returns the genesis block when proven tag points to genesis', async () => { + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); + const testCheckpoints = await makeChainedCheckpoints(1, { + previousArchive: genesisArchive, + blocksPerCheckpoint: 1, + }); + await archiverStore.blocks.addCheckpoints(testCheckpoints); + + // No proven checkpoint set — proven block number is 0 → genesis. + const block = await archiver.getBlock({ tag: 'proven' }); + expect(block?.number).toBe(BlockNumber.ZERO); + const data = await archiver.getBlockData({ tag: 'proven' }); + expect(data?.header.globalVariables.blockNumber).toBe(BlockNumber.ZERO); + }); + }); + describe('rollbackTo', () => { beforeEach(() => { publicClient.getBlock.mockImplementation( @@ -444,7 +562,7 @@ describe('Archiver Store', () => { }); it('rejects rollback to a block that is not at a checkpoint boundary', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); // Checkpoint 1: 3 blocks (1, 2, 3). Checkpoint 2: 3 blocks (4, 5, 6). const testCheckpoints = await makeChainedCheckpoints(2, { previousArchive: genesisArchive, @@ -463,8 +581,29 @@ describe('Archiver Store', () => { ); }); + it('rejects rollback to a proposed but not yet checkpointed block', async () => { + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); + const checkpoints1 = await makeChainedCheckpoints(1, { + previousArchive: genesisArchive, + blocksPerCheckpoint: 2, + }); + const checkpoints2 = await makeChainedCheckpoints(1, { + previousArchive: checkpoints1[0].checkpoint.blocks.at(-1)!.archive, + startCheckpointNumber: CheckpointNumber(2), + startBlockNumber: 3, + startL1BlockNumber: 20, + blocksPerCheckpoint: 2, + }); + await archiverStore.blocks.addCheckpoints(checkpoints1); + for (const block of checkpoints2[0].checkpoint.blocks) { + await archiverStore.blocks.addProposedBlock(block); + } + + await expect(archiver.rollbackTo(BlockNumber(3))).rejects.toThrow(/Target L2 block 3 is not checkpointed yet/); + }); + it('allows rollback to the last block of a checkpoint and updates sync points', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); // Checkpoint 1: 3 blocks (1, 2, 3), L1 block 10. Checkpoint 2: 3 blocks (4, 5, 6), L1 block 20. const testCheckpoints = await makeChainedCheckpoints(2, { previousArchive: genesisArchive, @@ -484,7 +623,7 @@ describe('Archiver Store', () => { }); it('includes correct boundary info in error for mid-checkpoint rollback', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); // Checkpoint 1: 2 blocks (1, 2). Checkpoint 2: 3 blocks (3, 4, 5). const checkpoints1 = await makeChainedCheckpoints(1, { previousArchive: genesisArchive, @@ -507,7 +646,7 @@ describe('Archiver Store', () => { }); it('rolls back proven checkpoint number when target is before proven block', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); // Checkpoint 1: blocks 1-2, Checkpoint 2: blocks 3-4, Checkpoint 3: blocks 5-6 const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive, @@ -527,7 +666,7 @@ describe('Archiver Store', () => { }); it('preserves proven checkpoint number when target is after proven block', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); // Checkpoint 1: blocks 1-2, Checkpoint 2: blocks 3-4, Checkpoint 3: blocks 5-6 const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive, @@ -547,7 +686,7 @@ describe('Archiver Store', () => { }); it('rolls back finalized checkpoint number when target is before finalized block', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); // Checkpoint 1: blocks 1-2, Checkpoint 2: blocks 3-4, Checkpoint 3: blocks 5-6 const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive, @@ -568,7 +707,7 @@ describe('Archiver Store', () => { }); it('preserves finalized checkpoint number when target is after finalized block', async () => { - const genesisArchive = new AppendOnlyTreeSnapshot(new Fr(GENESIS_ARCHIVE_ROOT), 1); + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); // Checkpoint 1: blocks 1-2, Checkpoint 2: blocks 3-4, Checkpoint 3: blocks 5-6 const testCheckpoints = await makeChainedCheckpoints(3, { previousArchive: genesisArchive, @@ -588,4 +727,62 @@ describe('Archiver Store', () => { expect(await archiver.getFinalizedL2BlockNumber()).toEqual(BlockNumber(2)); }); }); + + describe('genesis block handling', () => { + it('getBlock({number:0}) returns the synthetic genesis block', async () => { + const block = await archiver.getBlock({ number: BlockNumber.ZERO }); + expect(block).toBeDefined(); + expect(block!.header).toEqual(initialHeader); + }); + + it('getBlock({hash:initialHeaderHash}) returns the synthetic genesis block', async () => { + const initialHeaderHash = await initialHeader.hash(); + const block = await archiver.getBlock({ hash: initialHeaderHash }); + expect(block).toBeDefined(); + expect(block!.header).toEqual(initialHeader); + }); + + it('getBlock({archive:genesisArchiveRoot}) returns the synthetic genesis block', async () => { + const block = await archiver.getBlock({ archive: genesisArchiveRoot }); + expect(block).toBeDefined(); + expect(block!.header).toEqual(initialHeader); + expect(block!.archive.root).toEqual(genesisArchiveRoot); + expect(block!.archive.nextAvailableLeafIndex).toEqual(1); + }); + + it('getBlock({archive:initialHeader.lastArchive.root}) does NOT match genesis (it is the pre-block-0 archive)', async () => { + const block = await archiver.getBlock({ archive: initialHeader.lastArchive.root }); + expect(block).toBeUndefined(); + }); + + it('getBlock({tag:"finalized"}) returns the synthetic genesis block when no blocks synced', async () => { + // With an empty store the finalized tip is INITIAL_L2_BLOCK_NUM - 1 = 0 → resolves to genesis. + const block = await archiver.getBlock({ tag: 'finalized' }); + expect(block).toBeDefined(); + expect(block!.header).toEqual(initialHeader); + }); + + it('getBlockData({number:0}) returns the synthetic genesis block data', async () => { + const data = await archiver.getBlockData({ number: BlockNumber.ZERO }); + expect(data).toBeDefined(); + expect(data!.header).toEqual(initialHeader); + expect(data!.blockHash).toEqual(await initialHeader.hash()); + }); + + it('getBlockNumber({hash:initialHeaderHash}) returns 0', async () => { + const initialHeaderHash = await initialHeader.hash(); + const number = await archiver.getBlockNumber({ hash: initialHeaderHash }); + expect(number).toEqual(BlockNumber.ZERO); + }); + + it('getBlocks({from:0, limit:5}) throws — range queries do not support genesis', async () => { + await expect(archiver.getBlocks({ from: BlockNumber.ZERO, limit: 5 })).rejects.toThrow(/from/); + }); + + it('returns the same block instance on consecutive calls (caching invariant)', async () => { + const block1 = await archiver.getBlock({ number: BlockNumber.ZERO }); + const block2 = await archiver.getBlock({ number: BlockNumber.ZERO }); + expect(block1).toBe(block2); + }); + }); }); diff --git a/yarn-project/archiver/src/archiver-sync.test.ts b/yarn-project/archiver/src/archiver-sync.test.ts index 4c20a13d7052..81c820ddcb3b 100644 --- a/yarn-project/archiver/src/archiver-sync.test.ts +++ b/yarn-project/archiver/src/archiver-sync.test.ts @@ -20,6 +20,7 @@ import type { ProposedCheckpointInput } from '@aztec/stdlib/checkpoint'; import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import { computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging'; import { CheckpointHeader } from '@aztec/stdlib/rollup'; +import { BlockHeader } from '@aztec/stdlib/tx'; import { getTelemetryClient } from '@aztec/telemetry-client'; import { jest } from '@jest/globals'; @@ -123,7 +124,9 @@ describe('Archiver Sync', () => { const events = new EventEmitter() as ArchiverEmitter; // Create L2 tips cache shared by archiver and synchronizer - const l2TipsCache = new L2TipsCache(archiverStore.blocks); + const initialHeader = BlockHeader.empty(); + const initialBlockHash = await initialHeader.hash(); + const l2TipsCache = new L2TipsCache(archiverStore.blocks, initialBlockHash); // Create the L1 synchronizer synchronizer = new ArchiverL1Synchronizer( @@ -156,6 +159,8 @@ describe('Archiver Sync', () => { l1Constants, synchronizer, events, + initialHeader, + initialBlockHash, l2TipsCache, ); }); @@ -224,7 +229,7 @@ describe('Archiver Sync', () => { const expectedTotalNumLogs = (name: 'private' | 'public' | 'contractClass') => sum(block.body.txEffects.map(txEffect => txEffect[`${name}Logs`].length)); - const privateLogs = (await archiver.getBlock(blockNumber))!.getPrivateLogs(); + const privateLogs = (await archiver.getBlock({ number: blockNumber }))!.getPrivateLogs(); expect(privateLogs.length).toBe(expectedTotalNumLogs('private')); const publicLogs = (await archiver.getPublicLogs({ fromBlock: blockNumber, toBlock: blockNumber + 1 })).logs; @@ -1527,7 +1532,7 @@ describe('Archiver Sync', () => { const lastBlockInCheckpoint2 = cp2.blocks[cp2.blocks.length - 1].number; expect(await archiver.getBlockNumber()).toEqual(lastBlockInCheckpoint2); expect(await archiver.getSynchedCheckpointNumber()).toEqual(CheckpointNumber(1)); - expect((await archiver.getL2Block(cp2.blocks[0].number))!.equals(cp2.blocks[0])).toBe(true); + expect((await archiver.getBlock({ number: cp2.blocks[0].number }))!.equals(cp2.blocks[0])).toBe(true); // Verify L2Tips after adding blocks: proposed advances but checkpointed stays at checkpoint 1 const tipsAfterAddBlock = await archiver.getL2Tips(); @@ -1535,13 +1540,17 @@ describe('Archiver Sync', () => { expect(tipsAfterAddBlock.checkpointed.block.number).toEqual(lastBlockInCheckpoint1); expect(tipsAfterAddBlock.checkpointed.checkpoint.number).toEqual(CheckpointNumber(1)); - // getCheckpointedBlock should return undefined for the new blocks since checkpoint 2 hasn't synced + // getBlocks with onlyCheckpointed should return empty for the new blocks since checkpoint 2 hasn't synced const firstNewBlockNumber = BlockNumber(lastBlockInCheckpoint1 + 1); - const uncheckpointedBlock = await archiver.getCheckpointedBlock(firstNewBlockNumber); - expect(uncheckpointedBlock).toBeUndefined(); + const uncheckpointedBlocks = await archiver.getBlocks({ + from: firstNewBlockNumber, + limit: 1, + onlyCheckpointed: true, + }); + expect(uncheckpointedBlocks).toHaveLength(0); - // But getL2Block should work (it retrieves both checkpointed and uncheckpointed blocks) - const block = await archiver.getL2Block(firstNewBlockNumber); + // But getBlock should work (it retrieves both checkpointed and uncheckpointed blocks) + const block = await archiver.getBlock({ number: firstNewBlockNumber }); expect(block).toBeDefined(); // Now advance L1 so checkpoint 2 becomes visible @@ -1561,10 +1570,14 @@ describe('Archiver Sync', () => { expect(tipsAfterCheckpoint2.checkpointed.block.number).toEqual(lastBlockInCheckpoint2); expect(tipsAfterCheckpoint2.checkpointed.checkpoint.number).toEqual(CheckpointNumber(2)); - // getCheckpointedBlock should now work for the new blocks - const checkpointedBlock = await archiver.getCheckpointedBlock(firstNewBlockNumber); - expect(checkpointedBlock).toBeDefined(); - expect(checkpointedBlock!.checkpointNumber).toEqual(2); + // getBlocks with onlyCheckpointed should now include the new blocks + const checkpointedBlocks = await archiver.getBlocks({ + from: firstNewBlockNumber, + limit: 1, + onlyCheckpointed: true, + }); + expect(checkpointedBlocks).toHaveLength(1); + expect(checkpointedBlocks[0].checkpointNumber).toEqual(2); }, 10_000); it('rejects adding blocks that are already checkpointed', async () => { diff --git a/yarn-project/archiver/src/archiver.ts b/yarn-project/archiver/src/archiver.ts index 257a34f28b7c..23f417c088e3 100644 --- a/yarn-project/archiver/src/archiver.ts +++ b/yarn-project/archiver/src/archiver.ts @@ -14,6 +14,7 @@ import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/runni import { DateProvider, elapsed } from '@aztec/foundation/timer'; import { type ArchiverEmitter, + type BlockHash, L2Block, type L2BlockSink, type L2Tips, @@ -28,6 +29,7 @@ import { getTimestampForSlot, getTimestampRangeForEpoch, } from '@aztec/stdlib/epoch-helpers'; +import type { BlockHeader } from '@aztec/stdlib/tx'; import { type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client'; import { type ArchiverConfig, mapArchiverConfig } from './config.js'; @@ -140,17 +142,19 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra }, synchronizer: ArchiverL1Synchronizer, events: ArchiverEmitter, - l2TipsCache?: L2TipsCache, + initialHeader: BlockHeader, + initialBlockHash: BlockHash, + l2TipsCache: L2TipsCache, private readonly log: Logger = createLogger('archiver'), ) { - super(dataStores, l1Constants); + super(dataStores, l1Constants, initialHeader, initialBlockHash, l1Constants.genesisArchiveRoot); this.tracer = instrumentation.tracer; this.instrumentation = instrumentation; this.initialSyncPromise = promiseWithResolvers(); this.synchronizer = synchronizer; this.events = events; - this.l2TipsCache = l2TipsCache ?? new L2TipsCache(this.dataStores.blocks); + this.l2TipsCache = l2TipsCache; this.updater = new ArchiverDataStoreUpdater(this.dataStores, this.l2TipsCache, { rollupManaLimit: l1Constants.rollupManaLimit, }); @@ -455,8 +459,7 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra // The epoch is complete if the current checkpointed L2 block is the last one in the epoch (or later). // We use the checkpointed block number (synced from L1) instead of 'latest' to avoid returning true // prematurely when proposed blocks have been pushed to the archiver but not yet checkpointed on L1. - const checkpointedBlockNumber = await this.getCheckpointedL2BlockNumber(); - const header = checkpointedBlockNumber > 0 ? await this.getBlockHeader(checkpointedBlockNumber) : undefined; + const header = (await this.getBlockData({ tag: 'checkpointed' }))?.header; const slot = header ? header.globalVariables.slotNumber : undefined; const [_startSlot, endSlot] = getSlotRangeForEpoch(epochNumber, this.l1Constants); if (slot && slot >= endSlot) { @@ -512,29 +515,34 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra if (targetL2BlockNumber >= currentL2Block) { throw new Error(`Target L2 block ${targetL2BlockNumber} must be less than current L2 block ${currentL2Block}`); } - const targetL2Block = await this.stores.blocks.getCheckpointedBlock(targetL2BlockNumber); - if (!targetL2Block) { + const checkpointedTip = await this.stores.blocks.getCheckpointedL2BlockNumber(); + if (targetL2BlockNumber > checkpointedTip) { + throw new Error(`Target L2 block ${targetL2BlockNumber} is not checkpointed yet`); + } + const targetBlockData = await this.stores.blocks.getBlockData({ number: targetL2BlockNumber }); + if (!targetBlockData) { throw new Error(`Target L2 block ${targetL2BlockNumber} not found`); } - const targetCheckpointNumber = targetL2Block.checkpointNumber; + const targetCheckpointNumber = targetBlockData.checkpointNumber; // Rollback operates at checkpoint granularity: the target block must be the last block of its checkpoint. const checkpointData = await this.stores.blocks.getCheckpointData(targetCheckpointNumber); - if (checkpointData) { - const lastBlockInCheckpoint = BlockNumber(checkpointData.startBlock + checkpointData.blockCount - 1); - if (targetL2BlockNumber !== lastBlockInCheckpoint) { - const previousCheckpointBoundary = - checkpointData.startBlock > 1 ? BlockNumber(checkpointData.startBlock - 1) : BlockNumber(0); - throw new Error( - `Target L2 block ${targetL2BlockNumber} is not at a checkpoint boundary. ` + - `Checkpoint ${targetCheckpointNumber} spans blocks ${checkpointData.startBlock} to ${lastBlockInCheckpoint}. ` + - `Use block ${lastBlockInCheckpoint} to roll back to this checkpoint, ` + - `or block ${previousCheckpointBoundary} to roll back to the previous one.`, - ); - } + if (!checkpointData) { + throw new Error(`Checkpoint ${targetCheckpointNumber} not found for block ${targetL2BlockNumber}`); + } + const lastBlockInCheckpoint = BlockNumber(checkpointData.startBlock + checkpointData.blockCount - 1); + if (targetL2BlockNumber !== lastBlockInCheckpoint) { + const previousCheckpointBoundary = + checkpointData.startBlock > 1 ? BlockNumber(checkpointData.startBlock - 1) : BlockNumber(0); + throw new Error( + `Target L2 block ${targetL2BlockNumber} is not at a checkpoint boundary. ` + + `Checkpoint ${targetCheckpointNumber} spans blocks ${checkpointData.startBlock} to ${lastBlockInCheckpoint}. ` + + `Use block ${lastBlockInCheckpoint} to roll back to this checkpoint, ` + + `or block ${previousCheckpointBoundary} to roll back to the previous one.`, + ); } - const targetL1BlockNumber = targetL2Block.l1.blockNumber; + const targetL1BlockNumber = checkpointData.l1.blockNumber; const targetL1Block = await this.publicClient.getBlock({ blockNumber: targetL1BlockNumber, includeTransactions: false, diff --git a/yarn-project/archiver/src/errors.ts b/yarn-project/archiver/src/errors.ts index 1b42d82d66d6..64fae2aa69dd 100644 --- a/yarn-project/archiver/src/errors.ts +++ b/yarn-project/archiver/src/errors.ts @@ -29,25 +29,31 @@ export class InitialCheckpointNumberNotSequentialError extends Error { } } -export class BlockCheckpointNumberNotSequentialError extends Error { +export class CheckpointNumberNotSequentialError extends Error { constructor( - blockNumber: BlockNumber, - blockCheckpointNumber: CheckpointNumber, + newCheckpointNumber: CheckpointNumber, previous: CheckpointNumber | undefined, + source?: 'proposed' | 'confirmed', ) { + const qualifier = source ? `${source} ` : ''; super( - `Cannot insert new block ${blockNumber} for checkpoint ${blockCheckpointNumber} given previous checkpoint number is ${previous ?? 'undefined'}`, + `Cannot insert new checkpoint ${newCheckpointNumber} given previous ${qualifier}checkpoint number is ${previous ?? 'undefined'}`, ); - this.name = 'BlockCheckpointNumberNotSequentialError'; + this.name = 'CheckpointNumberNotSequentialError'; } } -export class CheckpointNumberNotSequentialError extends Error { - constructor(newCheckpointNumber: CheckpointNumber, previous: CheckpointNumber | undefined) { +/** Thrown when a proposed block carries a checkpoint number that does not follow the latest one. */ +export class BlockCheckpointNumberNotSequentialError extends Error { + constructor( + blockNumber: BlockNumber, + blockCheckpointNumber: CheckpointNumber, + previous: CheckpointNumber | undefined, + ) { super( - `Cannot insert new checkpoint ${newCheckpointNumber} given previous checkpoint number is ${previous ?? 'undefined'}`, + `Cannot insert new block ${blockNumber} for checkpoint ${blockCheckpointNumber} given previous checkpoint number is ${previous ?? 'undefined'}`, ); - this.name = 'CheckpointNumberNotSequentialError'; + this.name = 'BlockCheckpointNumberNotSequentialError'; } } diff --git a/yarn-project/archiver/src/factory.ts b/yarn-project/archiver/src/factory.ts index 53a555485d87..e5eb2eb1bac4 100644 --- a/yarn-project/archiver/src/factory.ts +++ b/yarn-project/archiver/src/factory.ts @@ -12,9 +12,10 @@ import { createStore } from '@aztec/kv-store/lmdb-v2'; import { protocolContractNames } from '@aztec/protocol-contracts'; import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle'; import { FunctionType, decodeFunctionSignature } from '@aztec/stdlib/abi'; -import type { ArchiverEmitter } from '@aztec/stdlib/block'; +import type { ArchiverEmitter, BlockHash } from '@aztec/stdlib/block'; import { type ContractClassPublicWithCommitment, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; import type { DataStoreConfig } from '@aztec/stdlib/kv-store'; +import type { BlockHeader } from '@aztec/stdlib/tx'; import { getTelemetryClient } from '@aztec/telemetry-client'; import { EventEmitter } from 'events'; @@ -46,12 +47,17 @@ export async function createArchiverStore( * @param config - The archiver configuration. * @param deps - The archiver dependencies (blobClient, epochCache, dateProvider, telemetry). * @param opts - The options. + * @param initialHeader - The genesis block header from world-state, used to answer block-0 queries. + * @param initialBlockHash - Precomputed hash of `initialHeader`. Hoisted to the caller so the archiver + * can expose `getGenesisBlockHash()` synchronously. * @returns The local archiver. */ export async function createArchiver( config: ArchiverConfig & DataStoreConfig, deps: ArchiverDeps, opts: { blockUntilSync: boolean } = { blockUntilSync: true }, + initialHeader: BlockHeader, + initialBlockHash: BlockHash, ): Promise { const archiverStore = await createArchiverStore(config); await registerProtocolContracts(archiverStore); @@ -133,8 +139,10 @@ export async function createArchiver( // Create the event emitter that will be shared by archiver and synchronizer const events = new EventEmitter() as ArchiverEmitter; - // Create L2 tips cache shared by archiver and synchronizer - const l2TipsCache = new L2TipsCache(archiverStore.blocks); + // Create L2 tips cache shared by archiver and synchronizer. The genesis block hash is dynamic — + // it depends on the injected initial header (genesisTimestamp + prefilled state). Hoisted to the + // caller so we can pass the same value to the archiver and expose it via `getGenesisBlockHash()`. + const l2TipsCache = new L2TipsCache(archiverStore.blocks, initialBlockHash); // Create the L1 synchronizer const synchronizer = new ArchiverL1Synchronizer( @@ -167,6 +175,8 @@ export async function createArchiver( l1Constants, synchronizer, events, + initialHeader, + initialBlockHash, l2TipsCache, ); diff --git a/yarn-project/archiver/src/modules/contract_data_source_adapter.ts b/yarn-project/archiver/src/modules/contract_data_source_adapter.ts index 5292dcb33a5a..9fc7a39bce20 100644 --- a/yarn-project/archiver/src/modules/contract_data_source_adapter.ts +++ b/yarn-project/archiver/src/modules/contract_data_source_adapter.ts @@ -35,12 +35,8 @@ export class ArchiverContractDataSourceAdapter implements ContractDataSource { let timestamp = maybeTimestamp; if (timestamp === undefined) { const latest = await this.stores.blocks.getLatestL2BlockNumber(); - if ((latest as BlockNumber) === 0) { - timestamp = 0n; - } else { - const [header] = await this.stores.blocks.getBlockHeaders(latest, 1); - timestamp = header ? header.globalVariables.timestamp : 0n; - } + const blockData = latest > 0 ? await this.stores.blocks.getBlockData({ number: latest }) : undefined; + timestamp = blockData ? blockData.header.globalVariables.timestamp : 0n; } return this.stores.contractInstances.getContractInstance(address, timestamp); } diff --git a/yarn-project/archiver/src/modules/data_source_base.ts b/yarn-project/archiver/src/modules/data_source_base.ts index 3078d115689c..ad051843475c 100644 --- a/yarn-project/archiver/src/modules/data_source_base.ts +++ b/yarn-project/archiver/src/modules/data_source_base.ts @@ -1,11 +1,25 @@ -import { range } from '@aztec/foundation/array'; -import { BlockNumber, CheckpointNumber, type EpochNumber, type SlotNumber } from '@aztec/foundation/branded-types'; +import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; +import { + BlockNumber, + CheckpointNumber, + type EpochNumber, + IndexWithinCheckpoint, + type SlotNumber, +} from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { EthAddress } from '@aztec/foundation/eth-address'; -import { isDefined } from '@aztec/foundation/types'; import type { FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { type BlockData, type BlockHash, CheckpointedL2Block, L2Block, type L2Tips } from '@aztec/stdlib/block'; +import { + type BlockData, + type BlockHash, + type BlockQuery, + type BlockTag, + type BlocksQuery, + Body, + L2Block, + type L2Tips, +} from '@aztec/stdlib/block'; import { Checkpoint, type CheckpointData, @@ -20,13 +34,22 @@ import type { L2LogsSource } from '@aztec/stdlib/interfaces/server'; import type { LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs'; import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging'; import type { CheckpointHeader } from '@aztec/stdlib/rollup'; +import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees'; import type { BlockHeader, IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx'; import type { UInt64 } from '@aztec/stdlib/types'; import type { ArchiverDataSource } from '../interfaces.js'; +import type { ResolvedBlockQuery, ResolvedBlocksQuery } from '../store/block_store.js'; import type { ArchiverDataStores } from '../store/data_stores.js'; import type { ValidateCheckpointResult } from './validation.js'; +/** + * Sentinel returned by {@link ArchiverDataSourceBase#resolveBlockQuery} when a query resolves + * to the genesis block. Forces single-block lookup methods to take the genesis branch + * explicitly rather than silently falling through to the BlockStore (which never has a block 0). + */ +type GenesisBlockQuery = { genesis: true }; + /** * Abstract base class implementing ArchiverDataSource using a bundle of archiver substores. * Provides implementations for all read-side methods and declares abstract methods for @@ -35,10 +58,69 @@ import type { ValidateCheckpointResult } from './validation.js'; export abstract class ArchiverDataSourceBase implements ArchiverDataSource, L2LogsSource, ContractDataSource, L1ToL2MessageSource { + /** The injected genesis block header. */ + protected readonly initialHeader: BlockHeader; + /** Precomputed hash of the initial header, exposed via {@link getGenesisBlockHash}. */ + protected readonly initialBlockHash: BlockHash; + /** Archive root after block 0 was appended — read from L1 (`Rollup.getGenesisArchiveTreeRoot`). */ + protected readonly genesisArchiveRoot: Fr; + + /** Memoized synthetic genesis block — callers rely on referential identity for caching. */ + private readonly genesisBlock: L2Block; + /** Memoized synthetic genesis block data — kept consistent with {@link genesisBlock}. */ + private readonly genesisBlockData: BlockData; + constructor( protected readonly stores: ArchiverDataStores, - protected readonly l1Constants?: L1RollupConstants, - ) {} + protected readonly l1Constants: L1RollupConstants | undefined, + initialHeader: BlockHeader, + initialBlockHash: BlockHash, + genesisArchiveRoot: Fr, + ) { + this.initialHeader = initialHeader; + this.initialBlockHash = initialBlockHash; + this.genesisArchiveRoot = genesisArchiveRoot; + + const genesisArchive = new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1); + this.genesisBlock = new L2Block( + genesisArchive, + initialHeader, + Body.empty(), + CheckpointNumber.ZERO, + IndexWithinCheckpoint(0), + ); + this.genesisBlockData = { + header: initialHeader, + archive: genesisArchive, + blockHash: initialBlockHash, + checkpointNumber: CheckpointNumber.ZERO, + indexWithinCheckpoint: IndexWithinCheckpoint(0), + }; + } + + /** Returns the precomputed hash of the genesis block header. */ + public getGenesisBlockHash(): BlockHash { + return this.initialBlockHash; + } + + /** Returns the synthetic genesis L2Block (memoized — same instance across calls). */ + private getGenesisBlock(): L2Block { + return this.genesisBlock; + } + + /** Returns genesis block data (memoized — same instance across calls). */ + private getGenesisBlockData(): BlockData { + return this.genesisBlockData; + } + + /** + * Type guard distinguishing the genesis sentinel from a {@link ResolvedBlockQuery}. + * `resolveBlockQuery` already rewrites every genesis-matching shape to the sentinel, + * so callers only need this single sync check. + */ + private isGenesisBlockQuery(query: ResolvedBlockQuery | GenesisBlockQuery): query is GenesisBlockQuery { + return 'genesis' in query; + } abstract getRollupAddress(): Promise; @@ -72,25 +154,24 @@ export abstract class ArchiverDataSourceBase return this.stores.blocks.getProvenCheckpointNumber(); } - public getBlockNumber(): Promise { - return this.stores.blocks.getLatestL2BlockNumber(); - } - - public getProvenBlockNumber(): Promise { - return this.stores.blocks.getProvenBlockNumber(); - } - - public async getBlockHeader(number: BlockNumber | 'latest'): Promise { - const blockNumber = number === 'latest' ? await this.stores.blocks.getLatestL2BlockNumber() : number; - if (blockNumber === 0) { + public getBlockNumber(): Promise; + public getBlockNumber(query: BlockQuery): Promise; + public async getBlockNumber(query?: BlockQuery): Promise { + if (!query) { + return this.stores.blocks.getLatestL2BlockNumber(); + } + const resolved = await this.resolveBlockQuery(query); + if (resolved === undefined) { return undefined; } - const headers = await this.stores.blocks.getBlockHeaders(blockNumber, 1); - return headers.length === 0 ? undefined : headers[0]; + if (this.isGenesisBlockQuery(resolved)) { + return BlockNumber.ZERO; + } + return this.stores.blocks.getBlockNumber(resolved); } - public getCheckpointedBlock(number: BlockNumber): Promise { - return this.stores.blocks.getCheckpointedBlock(number); + public getProvenBlockNumber(): Promise { + return this.stores.blocks.getProvenBlockNumber(); } public getCheckpointedL2BlockNumber(): Promise { @@ -123,10 +204,6 @@ export abstract class ArchiverDataSourceBase return BlockNumber(checkpointData.startBlock + checkpointData.blockCount - 1); } - public getCheckpointedBlocks(from: BlockNumber, limit: number): Promise { - return this.stores.blocks.getCheckpointedBlocks(from, limit); - } - public getCheckpointData(checkpointNumber: CheckpointNumber): Promise { return this.stores.blocks.getCheckpointData(checkpointNumber); } @@ -139,37 +216,6 @@ export abstract class ArchiverDataSourceBase return this.stores.blocks.getCheckpointNumberBySlot(slot); } - public getBlockDataWithCheckpointContext(blockNumber: BlockNumber) { - return this.stores.blocks.getBlockDataWithCheckpointContext(blockNumber); - } - - public getBlockHeaderByHash(blockHash: BlockHash): Promise { - return this.stores.blocks.getBlockHeaderByHash(blockHash); - } - - public getBlockHeaderByArchive(archive: Fr): Promise { - return this.stores.blocks.getBlockHeaderByArchive(archive); - } - - public getBlockData(number: BlockNumber): Promise { - return this.stores.blocks.getBlockData(number); - } - - public getBlockDataByArchive(archive: Fr): Promise { - return this.stores.blocks.getBlockDataByArchive(archive); - } - - public async getL2Block(number: BlockNumber): Promise { - // If the number provided is -ve, then return the latest block. - if (number < 0) { - number = await this.stores.blocks.getLatestL2BlockNumber(); - } - if (number === 0) { - return undefined; - } - return this.stores.blocks.getBlock(number); - } - public getTxEffect(txHash: TxHash): Promise { return this.stores.blocks.getTxEffect(txHash); } @@ -233,9 +279,8 @@ export abstract class ArchiverDataSourceBase ): Promise { let timestamp; if (maybeTimestamp === undefined) { - const latestBlockHeader = await this.getBlockHeader('latest'); - // If we get undefined block header, it means that the archiver has not yet synced any block so we default to 0. - timestamp = latestBlockHeader ? latestBlockHeader.globalVariables.timestamp : 0n; + const latestBlockData = await this.getBlockData({ tag: 'proposed' }); + timestamp = latestBlockData ? latestBlockData.header.globalVariables.timestamp : 0n; } else { timestamp = maybeTimestamp; } @@ -289,30 +334,6 @@ export abstract class ArchiverDataSourceBase return this.stores.blocks.getBlocksForSlot(slotNumber); } - public async getCheckpointedBlocksForEpoch(epochNumber: EpochNumber): Promise { - const checkpointsData = await this.getCheckpointsDataForEpoch(epochNumber); - const blocks = await Promise.all( - checkpointsData.flatMap(checkpoint => - range(checkpoint.blockCount, checkpoint.startBlock).map(blockNumber => - this.getCheckpointedBlock(BlockNumber(blockNumber)), - ), - ), - ); - return blocks.filter(isDefined); - } - - public async getCheckpointedBlockHeadersForEpoch(epochNumber: EpochNumber): Promise { - const checkpointsData = await this.getCheckpointsDataForEpoch(epochNumber); - const blocks = await Promise.all( - checkpointsData.flatMap(checkpoint => - range(checkpoint.blockCount, checkpoint.startBlock).map(blockNumber => - this.getBlockHeader(BlockNumber(blockNumber)), - ), - ), - ); - return blocks.filter(isDefined); - } - public async getCheckpointsForEpoch(epochNumber: EpochNumber): Promise { const checkpointsData = await this.getCheckpointsDataForEpoch(epochNumber); return Promise.all( @@ -330,36 +351,124 @@ export abstract class ArchiverDataSourceBase return this.stores.blocks.getCheckpointDataForSlotRange(start, end); } - public async getBlock(number: BlockNumber): Promise { - // If the number provided is -ve, then return the latest block. - if (number < 0) { - number = await this.stores.blocks.getLatestL2BlockNumber(); + /** Returns just the checkpoint numbers for all checkpoints whose slot falls within the given epoch. */ + public getCheckpointNumbersForEpoch(epochNumber: EpochNumber): Promise { + if (!this.l1Constants) { + throw new Error('L1 constants not set'); } - if (number === 0) { + + const [start, end] = getSlotRangeForEpoch(epochNumber, this.l1Constants); + return this.stores.blocks.getCheckpointNumbersForSlotRange(start, end); + } + + public async getBlock(query: BlockQuery): Promise { + const resolved = await this.resolveBlockQuery(query); + if (resolved === undefined) { return undefined; } - return this.stores.blocks.getBlock(number); + if (this.isGenesisBlockQuery(resolved)) { + return this.getGenesisBlock(); + } + return this.stores.blocks.getBlock(resolved); } - public getBlocks(from: BlockNumber, limit: number): Promise { - return this.stores.blocks.getBlocks(from, limit); + /** + * Range queries iterate physical blocks only; the genesis block is NOT prepended. + * `L2BlockStream` consumers (`world-state.handleL2Blocks`, etc.) emit `blocks-added` events for + * real blocks and would be surprised by a synthetic block 0. Use {@link getBlock} or + * {@link getBlockData} for genesis-aware single-block lookups. + */ + public async getBlocks(query: BlocksQuery): Promise { + const resolved = await this.resolveBlocksQuery(query); + return resolved ? this.stores.blocks.getBlocks(resolved) : []; } - public getCheckpointedBlockByHash(blockHash: BlockHash): Promise { - return this.stores.blocks.getCheckpointedBlockByHash(blockHash); + public async getBlockData(query: BlockQuery): Promise { + const resolved = await this.resolveBlockQuery(query); + if (resolved === undefined) { + return undefined; + } + if (this.isGenesisBlockQuery(resolved)) { + return this.getGenesisBlockData(); + } + return this.stores.blocks.getBlockData(resolved); } - public getCheckpointedBlockByArchive(archive: Fr): Promise { - return this.stores.blocks.getCheckpointedBlockByArchive(archive); + /** See {@link getBlocks} — range queries do not prepend the genesis block. */ + public async getBlocksData(query: BlocksQuery): Promise { + const resolved = await this.resolveBlocksQuery(query); + return resolved ? this.stores.blocks.getBlocksData(resolved) : []; } - public async getL2BlockByHash(blockHash: BlockHash): Promise { - const checkpointedBlock = await this.stores.blocks.getCheckpointedBlockByHash(blockHash); - return checkpointedBlock?.block; + /** + * Resolves a {@link BlockQuery} to either the genesis sentinel or a {@link ResolvedBlockQuery} + * understood by BlockStore. Detects every shape that points at block 0 — `{number:0}`, + * `{hash}` matching the initial header, `{archive}` matching the post-genesis archive root, + * and `{tag}` resolving to 0 — and rewrites them to the sentinel so callers branch once. + */ + private async resolveBlockQuery(query: BlockQuery): Promise { + if ('number' in query) { + return query.number === BlockNumber.ZERO ? { genesis: true } : query; + } + if ('hash' in query) { + return query.hash.equals(this.initialBlockHash) ? { genesis: true } : query; + } + if ('archive' in query) { + return query.archive.equals(this.genesisArchiveRoot) ? { genesis: true } : query; + } + const number = await this.resolveBlockTag(query.tag); + if (number === BlockNumber.ZERO) { + return { genesis: true }; + } + return { number }; + } + + /** Maps a {@link BlockTag} to the matching block number for the current chain state. */ + private resolveBlockTag(tag: BlockTag): Promise { + switch (tag) { + case 'latest': + case 'proposed': + return this.stores.blocks.getLatestL2BlockNumber(); + case 'checkpointed': + return this.stores.blocks.getCheckpointedL2BlockNumber(); + case 'proven': + return this.stores.blocks.getProvenBlockNumber(); + case 'finalized': + return this.stores.blocks.getFinalizedL2BlockNumber(); + } } - public async getL2BlockByArchive(archive: Fr): Promise { - const checkpointedBlock = await this.stores.blocks.getCheckpointedBlockByArchive(archive); - return checkpointedBlock?.block; + /** + * Converts an epoch-based BlocksQuery to a from/limit query using l1Constants. + * Returns undefined when the epoch has no checkpoints, so callers can return [] without + * entering BlockStore. Reads only the two endpoint checkpoints rather than the whole epoch. + */ + private async resolveBlocksQuery(query: BlocksQuery): Promise { + if (!('epoch' in query)) { + if (query.from < INITIAL_L2_BLOCK_NUM) { + throw new Error( + `getBlocks/getBlocksData: 'from' must be >= ${INITIAL_L2_BLOCK_NUM}, got ${query.from}. ` + + `Use getBlock({number:0})/getBlockData({number:0}) for genesis-aware single-block lookups.`, + ); + } + return query; + } + const checkpointNumbers = await this.getCheckpointNumbersForEpoch(query.epoch); + if (checkpointNumbers.length === 0) { + return undefined; + } + const firstNumber = checkpointNumbers[0]; + const lastNumber = checkpointNumbers[checkpointNumbers.length - 1]; + const first = await this.stores.blocks.getCheckpointData(firstNumber); + if (!first) { + return undefined; + } + const last = firstNumber === lastNumber ? first : await this.stores.blocks.getCheckpointData(lastNumber); + if (!last) { + return undefined; + } + const from = BlockNumber(first.startBlock); + const limit = last.startBlock + last.blockCount - first.startBlock; + return { from, limit, onlyCheckpointed: true }; } } diff --git a/yarn-project/archiver/src/modules/data_store_updater.test.ts b/yarn-project/archiver/src/modules/data_store_updater.test.ts index b0395494ce1a..3722d6eb8654 100644 --- a/yarn-project/archiver/src/modules/data_store_updater.test.ts +++ b/yarn-project/archiver/src/modules/data_store_updater.test.ts @@ -190,7 +190,7 @@ describe('ArchiverDataStoreUpdater', () => { await updater.addCheckpoints([makePublishedCheckpoint(makeCheckpoint([checkpointBlock]), 10)]); // Verify checkpoint block is stored - const storedBlock = await store.blocks.getBlock(BlockNumber(1)); + const storedBlock = await store.blocks.getBlock({ number: BlockNumber(1) }); expect(storedBlock?.archive.root.equals(checkpointBlock.archive.root)).toBe(true); const publicLogsAfter = await store.logs.getPublicLogs({}); expect(publicLogsAfter.logs.map(l => l.log)).toEqual(checkpointBlock.body.txEffects.flatMap(tx => tx.publicLogs)); diff --git a/yarn-project/archiver/src/modules/data_store_updater.ts b/yarn-project/archiver/src/modules/data_store_updater.ts index e02c51a214ee..e1b50131d589 100644 --- a/yarn-project/archiver/src/modules/data_store_updater.ts +++ b/yarn-project/archiver/src/modules/data_store_updater.ts @@ -177,10 +177,10 @@ export class ArchiverDataStoreUpdater { } // Get all uncheckpointed local blocks - const uncheckpointedLocalBlocks = await this.stores.blocks.getBlocks( - BlockNumber.add(lastCheckpointedBlockNumber, 1), - lastBlockNumber - lastCheckpointedBlockNumber, - ); + const uncheckpointedLocalBlocks = await this.stores.blocks.getBlocks({ + from: BlockNumber.add(lastCheckpointedBlockNumber, 1), + limit: lastBlockNumber - lastCheckpointedBlockNumber, + }); let lastAlreadyInsertedBlockNumber: BlockNumber | undefined; diff --git a/yarn-project/archiver/src/modules/l1_synchronizer.ts b/yarn-project/archiver/src/modules/l1_synchronizer.ts index 03fa5a373c64..f31f08cd0658 100644 --- a/yarn-project/archiver/src/modules/l1_synchronizer.ts +++ b/yarn-project/archiver/src/modules/l1_synchronizer.ts @@ -283,11 +283,10 @@ export class ArchiverL1Synchronizer implements Traceable { const firstUncheckpointedBlockNumber = BlockNumber(lastCheckpointedBlockNumber + 1); // What's the slot of the first uncheckpointed block? - const [firstUncheckpointedBlockHeader] = await this.stores.blocks.getBlockHeaders( - firstUncheckpointedBlockNumber, - 1, - ); - const firstUncheckpointedBlockSlot = firstUncheckpointedBlockHeader?.getSlot(); + const firstUncheckpointedBlockData = await this.stores.blocks.getBlockData({ + number: firstUncheckpointedBlockNumber, + }); + const firstUncheckpointedBlockSlot = firstUncheckpointedBlockData?.header.getSlot(); if (firstUncheckpointedBlockSlot === undefined || firstUncheckpointedBlockSlot >= slotAtNextL1Block) { return; @@ -297,7 +296,7 @@ export class ArchiverL1Synchronizer implements Traceable { // This also clears any proposed checkpoint whose blocks are being pruned. this.log.warn( `Pruning blocks after block ${lastCheckpointedBlockNumber} due to slot ${firstUncheckpointedBlockSlot} not being checkpointed`, - { firstUncheckpointedBlockHeader: firstUncheckpointedBlockHeader.toInspect(), slotAtNextL1Block }, + { firstUncheckpointedBlockHeader: firstUncheckpointedBlockData?.header.toInspect(), slotAtNextL1Block }, ); const prunedBlocks = await this.updater.removeUncheckpointedBlocksAfter(lastCheckpointedBlockNumber); @@ -1086,7 +1085,10 @@ export class ArchiverL1Synchronizer implements Traceable { { proposedHeader: proposed.header.toInspect(), proposedArchiveRoot: proposed.archive.root.toString() }, ); - const blocks = await this.stores.blocks.getBlocks(BlockNumber(proposed.startBlock), proposed.blockCount); + const blocks = await this.stores.blocks.getBlocks({ + from: BlockNumber(proposed.startBlock), + limit: proposed.blockCount, + }); if (blocks.length !== proposed.blockCount) { this.log.warn( `Local proposed checkpoint ${proposed.checkpointNumber} has wrong block count (expected ${proposed.blockCount} blocks starting at ${proposed.startBlock} but got ${blocks.length})`, diff --git a/yarn-project/archiver/src/store/block_store.test.ts b/yarn-project/archiver/src/store/block_store.test.ts index 66becf646ffb..08e72e029d08 100644 --- a/yarn-project/archiver/src/store/block_store.test.ts +++ b/yarn-project/archiver/src/store/block_store.test.ts @@ -11,9 +11,9 @@ import { sleep } from '@aztec/foundation/sleep'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { BlockHash, - CheckpointedL2Block, CommitteeAttestation, EthAddress, + GENESIS_BLOCK_HEADER_HASH, L2Block, type ValidateCheckpointResult, } from '@aztec/stdlib/block'; @@ -40,6 +40,7 @@ import { makeStateForBlock, } from '../test/mock_structs.js'; import { BlockStore } from './block_store.js'; +import { L2TipsCache } from './l2_tips_cache.js'; async function addProposedBlocks( blockStore: BlockStore, @@ -63,15 +64,16 @@ describe('BlockStore', () => { [5, () => publishedCheckpoints[4].checkpoint.blocks[0]], ]; - const expectCheckpointedBlockEquals = ( - actual: CheckpointedL2Block, + const expectCheckpointedBlockEquals = async ( + actual: L2Block, expectedBlock: L2Block, expectedCheckpoint: PublishedCheckpoint, ) => { - expect(actual.l1).toEqual(expectedCheckpoint.l1); - expect(actual.block.header.equals(expectedBlock.header)).toBe(true); + expect(actual.header.equals(expectedBlock.header)).toBe(true); expect(actual.checkpointNumber).toEqual(expectedCheckpoint.checkpoint.number); - expect(actual.attestations.every((a, i) => a.equals(expectedCheckpoint.attestations[i]))).toBe(true); + const checkpointData = await blockStore.getCheckpointData(actual.checkpointNumber); + expect(checkpointData?.l1).toEqual(expectedCheckpoint.l1); + expect(checkpointData?.attestations.every((a, i) => a.equals(expectedCheckpoint.attestations[i]))).toBe(true); }; beforeEach(async () => { @@ -158,7 +160,7 @@ describe('BlockStore', () => { const checkpoint = await Checkpoint.random(CheckpointNumber(2), { numBlocks: 1, startBlockNumber: 2 }); const block = makePublishedCheckpoint(checkpoint, 2); await expect(blockStore.addCheckpoints([block])).rejects.toThrow(InitialCheckpointNumberNotSequentialError); - await expect(blockStore.getCheckpointedBlock(BlockNumber(1))).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ number: BlockNumber(1) })).resolves.toBeUndefined(); }); it('throws an error if there is a gap in the blocks being added', async () => { @@ -166,7 +168,7 @@ describe('BlockStore', () => { const checkpoint3 = await Checkpoint.random(CheckpointNumber(3), { numBlocks: 1, startBlockNumber: 3 }); const checkpoints = [makePublishedCheckpoint(checkpoint1, 1), makePublishedCheckpoint(checkpoint3, 3)]; await expect(blockStore.addCheckpoints(checkpoints)).rejects.toThrow(CheckpointNumberNotSequentialError); - await expect(blockStore.getCheckpointedBlock(BlockNumber(1))).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ number: BlockNumber(1) })).resolves.toBeUndefined(); }); it('throws an error if blocks within a checkpoint are not sequential', async () => { @@ -183,7 +185,7 @@ describe('BlockStore', () => { const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10); await expect(blockStore.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockNumberNotSequentialError); - await expect(blockStore.getCheckpointedBlock(BlockNumber(1))).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ number: BlockNumber(1) })).resolves.toBeUndefined(); }); it('throws an error if blocks within a checkpoint do not have sequential indexes', async () => { @@ -206,7 +208,7 @@ describe('BlockStore', () => { const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10); await expect(blockStore.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockIndexNotSequentialError); - await expect(blockStore.getCheckpointedBlock(BlockNumber(1))).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ number: BlockNumber(1) })).resolves.toBeUndefined(); }); it('throws an error if blocks within a checkpoint do not start from index 0', async () => { @@ -229,7 +231,7 @@ describe('BlockStore', () => { const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10); await expect(blockStore.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockIndexNotSequentialError); - await expect(blockStore.getCheckpointedBlock(BlockNumber(1))).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ number: BlockNumber(1) })).resolves.toBeUndefined(); }); it('throws an error if block has invalid checkpoint index', async () => { @@ -248,7 +250,7 @@ describe('BlockStore', () => { const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10); await expect(blockStore.addCheckpoints([publishedCheckpoint])).rejects.toThrow(BlockIndexNotSequentialError); - await expect(blockStore.getCheckpointedBlock(BlockNumber(BlockNumber(1)))).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ number: BlockNumber(1) })).resolves.toBeUndefined(); }); it('throws an error if checkpoint has invalid initial number', async () => { @@ -355,10 +357,10 @@ describe('BlockStore', () => { await expect(blockStore.addCheckpoints(checkpoints)).resolves.toBe(true); // Verify blocks have correct checkpoint assignments - const block1 = await blockStore.getCheckpointedBlock(BlockNumber(1)); - const block2 = await blockStore.getCheckpointedBlock(BlockNumber(2)); - const block3 = await blockStore.getCheckpointedBlock(BlockNumber(3)); - const block4 = await blockStore.getCheckpointedBlock(BlockNumber(4)); + const block1 = await blockStore.getBlock({ number: BlockNumber(1) }); + const block2 = await blockStore.getBlock({ number: BlockNumber(2) }); + const block3 = await blockStore.getBlock({ number: BlockNumber(3) }); + const block4 = await blockStore.getBlock({ number: BlockNumber(4) }); expect(block1!.checkpointNumber).toBe(1); expect(block2!.checkpointNumber).toBe(1); @@ -375,15 +377,15 @@ describe('BlockStore', () => { const lastBlockNumber = lastCheckpoint.checkpoint.blocks[0].number; // Verify block exists before removing - const retrievedBlock = await blockStore.getCheckpointedBlock(BlockNumber(BlockNumber(lastBlockNumber))); + const retrievedBlock = await blockStore.getBlock({ number: BlockNumber(lastBlockNumber) }); expect(retrievedBlock).toBeDefined(); - expect(retrievedBlock!.block.header.equals(lastCheckpoint.checkpoint.blocks[0].header)).toBe(true); + expect(retrievedBlock!.header.equals(lastCheckpoint.checkpoint.blocks[0].header)).toBe(true); expect(retrievedBlock!.checkpointNumber).toEqual(checkpointNumber); await blockStore.removeCheckpointsAfter(CheckpointNumber(checkpointNumber - 1)); expect(await blockStore.getLatestCheckpointNumber()).toBe(checkpointNumber - 1); - await expect(blockStore.getCheckpointedBlock(BlockNumber(BlockNumber(lastBlockNumber)))).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ number: BlockNumber(lastBlockNumber) })).resolves.toBeUndefined(); }); it('can remove multiple checkpoints', async () => { @@ -417,19 +419,19 @@ describe('BlockStore', () => { const archive = lastBlock.archive.root; // Verify block and header exist before removing - const retrievedByHash = await blockStore.getCheckpointedBlockByHash(blockHash); + const retrievedByHash = await blockStore.getBlock({ hash: blockHash }); expect(retrievedByHash).toBeDefined(); - expect(retrievedByHash!.block.header.equals(lastBlock.header)).toBe(true); + expect(retrievedByHash!.header.equals(lastBlock.header)).toBe(true); - const retrievedByArchive = await blockStore.getCheckpointedBlockByArchive(archive); + const retrievedByArchive = await blockStore.getBlock({ archive: archive }); expect(retrievedByArchive).toBeDefined(); - expect(retrievedByArchive!.block.header.equals(lastBlock.header)).toBe(true); + expect(retrievedByArchive!.header.equals(lastBlock.header)).toBe(true); - const headerByHash = await blockStore.getBlockHeaderByHash(blockHash); + const headerByHash = (await blockStore.getBlockData({ hash: blockHash }))?.header; expect(headerByHash).toBeDefined(); expect(headerByHash!.equals(lastBlock.header)).toBe(true); - const headerByArchive = await blockStore.getBlockHeaderByArchive(archive); + const headerByArchive = (await blockStore.getBlockData({ archive: archive }))?.header; expect(headerByArchive).toBeDefined(); expect(headerByArchive!.equals(lastBlock.header)).toBe(true); @@ -437,10 +439,10 @@ describe('BlockStore', () => { await blockStore.removeCheckpointsAfter(CheckpointNumber(lastCheckpoint.checkpoint.number - 1)); // Verify neither block nor header can be retrieved after removal - expect(await blockStore.getCheckpointedBlockByHash(blockHash)).toBeUndefined(); - expect(await blockStore.getCheckpointedBlockByArchive(archive)).toBeUndefined(); - expect(await blockStore.getBlockHeaderByHash(blockHash)).toBeUndefined(); - expect(await blockStore.getBlockHeaderByArchive(archive)).toBeUndefined(); + expect(await blockStore.getBlock({ hash: blockHash })).toBeUndefined(); + expect(await blockStore.getBlock({ archive: archive })).toBeUndefined(); + expect(await blockStore.getBlockData({ hash: blockHash })).toBeUndefined(); + expect(await blockStore.getBlockData({ archive: archive })).toBeUndefined(); }); it('orphaned blocks are removed when removing checkpoints', async () => { @@ -472,7 +474,7 @@ describe('BlockStore', () => { expect(await blockStore.getLatestCheckpointNumber()).toBe(1); expect(await blockStore.getLatestL2BlockNumber()).toBe(2); expect(await blockStore.getCheckpointedL2BlockNumber()).toBe(1); - expect(await blockStore.getBlock(BlockNumber(2))).toBeDefined(); + expect(await blockStore.getBlock({ number: BlockNumber(2) })).toBeDefined(); // Remove checkpoint 1 (simulating L1 reorg) await blockStore.removeCheckpointsAfter(CheckpointNumber(0)); @@ -481,8 +483,8 @@ describe('BlockStore', () => { expect(await blockStore.getLatestCheckpointNumber()).toBe(0); expect(await blockStore.getLatestL2BlockNumber()).toBe(0); expect(await blockStore.getCheckpointedL2BlockNumber()).toBe(0); - expect(await blockStore.getBlock(BlockNumber(1))).toBeUndefined(); - expect(await blockStore.getBlock(BlockNumber(2))).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(1) })).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(2) })).toBeUndefined(); }); it('multiple orphaned blocks are removed when removing checkpoints', async () => { @@ -521,7 +523,7 @@ describe('BlockStore', () => { expect(await blockStore.getLatestCheckpointNumber()).toBe(0); expect(await blockStore.getLatestL2BlockNumber()).toBe(0); for (let i = 1; i <= 4; i++) { - expect(await blockStore.getBlock(BlockNumber(i))).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(i) })).toBeUndefined(); } }); }); @@ -644,12 +646,12 @@ describe('BlockStore', () => { // Verify blocks 1-5 still exist (from checkpoints 1 and 2) for (let blockNumber = 1; blockNumber <= 5; blockNumber++) { - expect(await blockStore.getCheckpointedBlock(BlockNumber(BlockNumber(blockNumber)))).toBeDefined(); + expect(await blockStore.getBlock({ number: BlockNumber(blockNumber) })).toBeDefined(); } // Verify blocks 6-10 are gone (from checkpoints 3 and 4) for (let blockNumber = 6; blockNumber <= 10; blockNumber++) { - expect(await blockStore.getCheckpointedBlock(BlockNumber(BlockNumber(blockNumber)))).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(blockNumber) })).toBeUndefined(); } // Remove remaining checkpoints 1 and 2 (which together have 5 blocks) @@ -660,7 +662,7 @@ describe('BlockStore', () => { // Verify all blocks are gone for (let blockNumber = 1; blockNumber <= 10; blockNumber++) { - expect(await blockStore.getCheckpointedBlock(BlockNumber(BlockNumber(blockNumber)))).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(blockNumber) })).toBeUndefined(); } }); @@ -684,25 +686,27 @@ describe('BlockStore', () => { // Check blocks from the first checkpoint (blocks 1, 2, 3) for (let i = 0; i < 3; i++) { const blockNumber = i + 1; - const retrievedBlock = await blockStore.getCheckpointedBlock(BlockNumber(BlockNumber(blockNumber))); + const retrievedBlock = await blockStore.getBlock({ number: BlockNumber(blockNumber) }); expect(retrievedBlock).toBeDefined(); expect(retrievedBlock!.checkpointNumber).toBe(1); - expect(retrievedBlock!.block.number).toBe(blockNumber); - expect(retrievedBlock!.l1).toEqual(checkpoint1.l1); - expect(retrievedBlock!.attestations.every((a, j) => a.equals(checkpoint1.attestations[j]))).toBe(true); + expect(retrievedBlock!.number).toBe(blockNumber); + const checkpointData1 = await blockStore.getCheckpointData(retrievedBlock!.checkpointNumber); + expect(checkpointData1?.l1).toEqual(checkpoint1.l1); + expect(checkpointData1?.attestations.every((a, j) => a.equals(checkpoint1.attestations[j]))).toBe(true); } // Check blocks from the second checkpoint (blocks 4, 5) for (let i = 0; i < 2; i++) { const blockNumber = i + 4; - const retrievedBlock = await blockStore.getCheckpointedBlock(BlockNumber(BlockNumber(blockNumber))); + const retrievedBlock = await blockStore.getBlock({ number: BlockNumber(blockNumber) }); expect(retrievedBlock).toBeDefined(); expect(retrievedBlock!.checkpointNumber).toBe(2); - expect(retrievedBlock!.block.number).toBe(blockNumber); - expect(retrievedBlock!.l1).toEqual(checkpoint2.l1); - expect(retrievedBlock!.attestations.every((a, j) => a.equals(checkpoint2.attestations[j]))).toBe(true); + expect(retrievedBlock!.number).toBe(blockNumber); + const checkpointData2 = await blockStore.getCheckpointData(retrievedBlock!.checkpointNumber); + expect(checkpointData2?.l1).toEqual(checkpoint2.l1); + expect(checkpointData2?.attestations.every((a, j) => a.equals(checkpoint2.attestations[j]))).toBe(true); } }); @@ -718,12 +722,13 @@ describe('BlockStore', () => { for (let i = 0; i < checkpoint.checkpoint.blocks.length; i++) { const block = checkpoint.checkpoint.blocks[i]; const blockHash = await block.header.hash(); - const retrievedBlock = await blockStore.getCheckpointedBlockByHash(blockHash); + const retrievedBlock = await blockStore.getBlock({ hash: blockHash }); expect(retrievedBlock).toBeDefined(); expect(retrievedBlock!.checkpointNumber).toBe(1); - expect(retrievedBlock!.block.number).toBe(i + 1); - expect(retrievedBlock!.l1).toEqual(checkpoint.l1); + expect(retrievedBlock!.number).toBe(i + 1); + const checkpointData = await blockStore.getCheckpointData(retrievedBlock!.checkpointNumber); + expect(checkpointData?.l1).toEqual(checkpoint.l1); } }); @@ -739,12 +744,13 @@ describe('BlockStore', () => { for (let i = 0; i < checkpoint.checkpoint.blocks.length; i++) { const block = checkpoint.checkpoint.blocks[i]; const archive = block.archive.root; - const retrievedBlock = await blockStore.getCheckpointedBlockByArchive(archive); + const retrievedBlock = await blockStore.getBlock({ archive: archive }); expect(retrievedBlock).toBeDefined(); expect(retrievedBlock!.checkpointNumber).toBe(1); - expect(retrievedBlock!.block.number).toBe(i + 1); - expect(retrievedBlock!.l1).toEqual(checkpoint.l1); + expect(retrievedBlock!.number).toBe(i + 1); + const checkpointData = await blockStore.getCheckpointData(retrievedBlock!.checkpointNumber); + expect(checkpointData?.l1).toEqual(checkpoint.l1); } }); @@ -758,7 +764,7 @@ describe('BlockStore', () => { // Verify all 3 blocks exist for (let blockNumber = 1; blockNumber <= 3; blockNumber++) { - expect(await blockStore.getCheckpointedBlock(BlockNumber(blockNumber))).toBeDefined(); + expect(await blockStore.getBlock({ number: BlockNumber(blockNumber) })).toBeDefined(); } // Remove the checkpoint @@ -766,7 +772,7 @@ describe('BlockStore', () => { // Verify all 3 blocks are removed for (let blockNumber = 1; blockNumber <= 3; blockNumber++) { - expect(await blockStore.getCheckpointedBlock(BlockNumber(blockNumber))).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(blockNumber) })).toBeUndefined(); } expect(await blockStore.getLatestCheckpointNumber()).toBe(0); @@ -836,11 +842,11 @@ describe('BlockStore', () => { await addProposedBlocks(blockStore, [block3, block4]); // getBlock should work for both checkpointed and uncheckpointed blocks - expect((await blockStore.getBlock(BlockNumber(1)))?.number).toBe(1); - expect((await blockStore.getBlock(BlockNumber(2)))?.number).toBe(2); - expect((await blockStore.getBlock(BlockNumber(3)))?.equals(block3)).toBe(true); - expect((await blockStore.getBlock(BlockNumber(4)))?.equals(block4)).toBe(true); - expect(await blockStore.getBlock(BlockNumber(5))).toBeUndefined(); + expect((await blockStore.getBlock({ number: BlockNumber(1) }))?.number).toBe(1); + expect((await blockStore.getBlock({ number: BlockNumber(2) }))?.number).toBe(2); + expect((await blockStore.getBlock({ number: BlockNumber(3) }))?.equals(block3)).toBe(true); + expect((await blockStore.getBlock({ number: BlockNumber(4) }))?.equals(block4)).toBe(true); + expect(await blockStore.getBlock({ number: BlockNumber(5) })).toBeUndefined(); const block5 = await L2Block.random(BlockNumber(5), { checkpointNumber: CheckpointNumber(2), @@ -850,13 +856,13 @@ describe('BlockStore', () => { await blockStore.addProposedBlock(block5); // Verify the uncheckpointed blocks have correct data - const retrieved3 = await blockStore.getBlock(BlockNumber(3)); + const retrieved3 = await blockStore.getBlock({ number: BlockNumber(3) }); expect(retrieved3!.number).toBe(3); expect(retrieved3!.equals(block3)).toBe(true); - const retrieved4 = await blockStore.getBlock(BlockNumber(4)); + const retrieved4 = await blockStore.getBlock({ number: BlockNumber(4) }); expect(retrieved4!.number).toBe(4); expect(retrieved4!.equals(block4)).toBe(true); - const retrieved5 = await blockStore.getBlock(BlockNumber(5)); + const retrieved5 = await blockStore.getBlock({ number: BlockNumber(5) }); expect(retrieved5!.number).toBe(5); expect(retrieved5!.equals(block5)).toBe(true); }); @@ -878,10 +884,10 @@ describe('BlockStore', () => { const hash1 = await block1.header.hash(); const hash2 = await block2.header.hash(); - const retrieved1 = await blockStore.getBlockByHash(hash1); + const retrieved1 = await blockStore.getBlock({ hash: hash1 }); expect(retrieved1!.equals(block1)).toBe(true); - const retrieved2 = await blockStore.getBlockByHash(hash2); + const retrieved2 = await blockStore.getBlock({ hash: hash2 }); expect(retrieved2!.equals(block2)).toBe(true); }); @@ -902,82 +908,13 @@ describe('BlockStore', () => { const archive1 = block1.archive.root; const archive2 = block2.archive.root; - const retrieved1 = await blockStore.getBlockByArchive(archive1); + const retrieved1 = await blockStore.getBlock({ archive: archive1 }); expect(retrieved1!.equals(block1)).toBe(true); - const retrieved2 = await blockStore.getBlockByArchive(archive2); + const retrieved2 = await blockStore.getBlock({ archive: archive2 }); expect(retrieved2!.equals(block2)).toBe(true); }); - it('getCheckpointedBlock returns undefined for uncheckpointed blocks', async () => { - // Add a checkpoint with blocks 1-2 - const checkpoint1 = makePublishedCheckpoint( - await Checkpoint.random(CheckpointNumber(1), { numBlocks: 2, startBlockNumber: 1 }), - 10, - ); - await blockStore.addCheckpoints([checkpoint1]); - - // Add uncheckpointed blocks 3-4 for upcoming checkpoint 2, chaining archive roots - const lastBlockArchive = checkpoint1.checkpoint.blocks.at(-1)!.archive; - const block3 = await L2Block.random(BlockNumber(3), { - checkpointNumber: CheckpointNumber(2), - indexWithinCheckpoint: IndexWithinCheckpoint(0), - lastArchive: lastBlockArchive, - }); - const block4 = await L2Block.random(BlockNumber(4), { - checkpointNumber: CheckpointNumber(2), - indexWithinCheckpoint: IndexWithinCheckpoint(1), - lastArchive: block3.archive, - }); - await addProposedBlocks(blockStore, [block3, block4]); - - // getCheckpointedBlock should work for checkpointed blocks - expect((await blockStore.getCheckpointedBlock(BlockNumber(1)))?.block.number).toBe(1); - expect((await blockStore.getCheckpointedBlock(BlockNumber(2)))?.block.number).toBe(2); - - // getCheckpointedBlock should return undefined for uncheckpointed blocks - expect(await blockStore.getCheckpointedBlock(BlockNumber(3))).toBeUndefined(); - expect(await blockStore.getCheckpointedBlock(BlockNumber(4))).toBeUndefined(); - - // But getBlock should work for all blocks - expect((await blockStore.getBlock(BlockNumber(3)))?.equals(block3)).toBe(true); - expect((await blockStore.getBlock(BlockNumber(4)))?.equals(block4)).toBe(true); - }); - - it('getCheckpointedBlockByHash returns undefined for uncheckpointed blocks', async () => { - // Add uncheckpointed blocks for initial checkpoint 1 - const block1 = await L2Block.random(BlockNumber(1), { - checkpointNumber: CheckpointNumber(1), - indexWithinCheckpoint: IndexWithinCheckpoint(0), - }); - await blockStore.addProposedBlock(block1); - - const hash = await block1.header.hash(); - - // getCheckpointedBlockByHash should return undefined - expect(await blockStore.getCheckpointedBlockByHash(hash)).toBeUndefined(); - - // But getBlockByHash should work - expect((await blockStore.getBlockByHash(hash))?.equals(block1)).toBe(true); - }); - - it('getCheckpointedBlockByArchive returns undefined for uncheckpointed blocks', async () => { - // Add uncheckpointed blocks for initial checkpoint 1 - const block1 = await L2Block.random(BlockNumber(1), { - checkpointNumber: CheckpointNumber(1), - indexWithinCheckpoint: IndexWithinCheckpoint(0), - }); - await blockStore.addProposedBlock(block1); - - const archive = block1.archive.root; - - // getCheckpointedBlockByArchive should return undefined - expect(await blockStore.getCheckpointedBlockByArchive(archive)).toBeUndefined(); - - // But getBlockByArchive should work - expect((await blockStore.getBlockByArchive(archive))?.equals(block1)).toBe(true); - }); - it('checkpoint adopts previously added uncheckpointed blocks', async () => { // Add blocks 1-3 without a checkpoint (for initial checkpoint 1), chaining archive roots const block1 = await L2Block.random(BlockNumber(1), { @@ -999,11 +936,6 @@ describe('BlockStore', () => { expect(await blockStore.getLatestCheckpointNumber()).toBe(0); expect(await blockStore.getLatestL2BlockNumber()).toBe(3); - // getCheckpointedBlock should return undefined for all - expect(await blockStore.getCheckpointedBlock(BlockNumber(1))).toBeUndefined(); - expect(await blockStore.getCheckpointedBlock(BlockNumber(2))).toBeUndefined(); - expect(await blockStore.getCheckpointedBlock(BlockNumber(3))).toBeUndefined(); - // Now add a checkpoint that covers blocks 1-3 const checkpoint1 = makePublishedCheckpoint( await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }), @@ -1014,17 +946,18 @@ describe('BlockStore', () => { expect(await blockStore.getLatestCheckpointNumber()).toBe(1); expect(await blockStore.getLatestL2BlockNumber()).toBe(3); - // Now getCheckpointedBlock should work for all blocks - const checkpointed1 = await blockStore.getCheckpointedBlock(BlockNumber(1)); + // Now getBlock should return all blocks + const checkpointed1 = await blockStore.getBlock({ number: BlockNumber(1) }); expect(checkpointed1).toBeDefined(); expect(checkpointed1!.checkpointNumber).toBe(1); - expect(checkpointed1!.l1).toEqual(checkpoint1.l1); + const checkpointData1 = await blockStore.getCheckpointData(checkpointed1!.checkpointNumber); + expect(checkpointData1?.l1).toEqual(checkpoint1.l1); - const checkpointed2 = await blockStore.getCheckpointedBlock(BlockNumber(2)); + const checkpointed2 = await blockStore.getBlock({ number: BlockNumber(2) }); expect(checkpointed2).toBeDefined(); expect(checkpointed2!.checkpointNumber).toBe(1); - const checkpointed3 = await blockStore.getCheckpointedBlock(BlockNumber(3)); + const checkpointed3 = await blockStore.getBlock({ number: BlockNumber(3) }); expect(checkpointed3).toBeDefined(); expect(checkpointed3!.checkpointNumber).toBe(1); }); @@ -1059,11 +992,6 @@ describe('BlockStore', () => { expect(await blockStore.getLatestCheckpointNumber()).toBe(1); expect(await blockStore.getLatestL2BlockNumber()).toBe(5); - // Blocks 3-5 are not checkpointed yet - expect(await blockStore.getCheckpointedBlock(BlockNumber(3))).toBeUndefined(); - expect(await blockStore.getCheckpointedBlock(BlockNumber(4))).toBeUndefined(); - expect(await blockStore.getCheckpointedBlock(BlockNumber(5))).toBeUndefined(); - // Add checkpoint 2 covering blocks 3-5, chaining from checkpoint1 const checkpoint2 = makePublishedCheckpoint( await Checkpoint.random(CheckpointNumber(2), { @@ -1079,16 +1007,17 @@ describe('BlockStore', () => { expect(await blockStore.getLatestL2BlockNumber()).toBe(5); // Now blocks 3-5 should be checkpointed with checkpoint 2's info - const checkpointed3 = await blockStore.getCheckpointedBlock(BlockNumber(3)); + const checkpointed3 = await blockStore.getBlock({ number: BlockNumber(3) }); expect(checkpointed3).toBeDefined(); expect(checkpointed3!.checkpointNumber).toBe(2); - expect(checkpointed3!.l1).toEqual(checkpoint2.l1); + const checkpointData2 = await blockStore.getCheckpointData(checkpointed3!.checkpointNumber); + expect(checkpointData2?.l1).toEqual(checkpoint2.l1); - const checkpointed4 = await blockStore.getCheckpointedBlock(BlockNumber(4)); + const checkpointed4 = await blockStore.getBlock({ number: BlockNumber(4) }); expect(checkpointed4).toBeDefined(); expect(checkpointed4!.checkpointNumber).toBe(2); - const checkpointed5 = await blockStore.getCheckpointedBlock(BlockNumber(5)); + const checkpointed5 = await blockStore.getBlock({ number: BlockNumber(5) }); expect(checkpointed5).toBeDefined(); expect(checkpointed5!.checkpointNumber).toBe(2); }); @@ -1116,7 +1045,7 @@ describe('BlockStore', () => { await addProposedBlocks(blockStore, [block3, block4]); // getBlocks should retrieve all blocks - const allBlocks = await blockStore.getBlocks(BlockNumber(1), 10); + const allBlocks = await blockStore.getBlocks({ from: BlockNumber(1), limit: 10 }); expect(allBlocks.length).toBe(4); expect(allBlocks.map(b => b.number)).toEqual([1, 2, 3, 4]); }); @@ -1164,8 +1093,8 @@ describe('BlockStore', () => { await expect(addProposedBlocks(blockStore, [block3, block4])).resolves.toBe(true); // Verify blocks were added - expect((await blockStore.getBlock(BlockNumber(3)))?.equals(block3)).toBe(true); - expect((await blockStore.getBlock(BlockNumber(4)))?.equals(block4)).toBe(true); + expect((await blockStore.getBlock({ number: BlockNumber(3) }))?.equals(block3)).toBe(true); + expect((await blockStore.getBlock({ number: BlockNumber(4) }))?.equals(block4)).toBe(true); }); it('allows blocks for the initial checkpoint when store is empty', async () => { @@ -1183,8 +1112,8 @@ describe('BlockStore', () => { await expect(addProposedBlocks(blockStore, [block1, block2])).resolves.toBe(true); // Verify blocks were added - expect((await blockStore.getBlock(BlockNumber(1)))?.equals(block1)).toBe(true); - expect((await blockStore.getBlock(BlockNumber(2)))?.equals(block2)).toBe(true); + expect((await blockStore.getBlock({ number: BlockNumber(1) }))?.equals(block1)).toBe(true); + expect((await blockStore.getBlock({ number: BlockNumber(2) }))?.equals(block2)).toBe(true); expect(await blockStore.getLatestL2BlockNumber()).toBe(2); }); @@ -1688,26 +1617,95 @@ describe('BlockStore', () => { }); }); + describe('getCheckpointNumbersForSlotRange', () => { + it('returns empty array when no checkpoints exist', async () => { + const numbers = await blockStore.getCheckpointNumbersForSlotRange(SlotNumber(0), SlotNumber(100)); + expect(numbers).toEqual([]); + }); + + it('returns checkpoint numbers for checkpoints whose slot is within the range (inclusive)', async () => { + const cp1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1, slotNumber: SlotNumber(5) }), + 10, + ); + const previousArchive1 = cp1.checkpoint.blocks.at(-1)!.archive; + const cp2 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(2), { + numBlocks: 1, + startBlockNumber: 2, + previousArchive: previousArchive1, + slotNumber: SlotNumber(8), + }), + 11, + ); + const previousArchive2 = cp2.checkpoint.blocks.at(-1)!.archive; + const cp3 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(3), { + numBlocks: 1, + startBlockNumber: 3, + previousArchive: previousArchive2, + slotNumber: SlotNumber(12), + }), + 12, + ); + await blockStore.addCheckpoints([cp1, cp2, cp3]); + + // Inclusive range covering all three slots + expect(await blockStore.getCheckpointNumbersForSlotRange(SlotNumber(0), SlotNumber(20))).toEqual([1, 2, 3]); + + // Range that excludes the first checkpoint + expect(await blockStore.getCheckpointNumbersForSlotRange(SlotNumber(6), SlotNumber(20))).toEqual([2, 3]); + + // Range that includes only the middle checkpoint (endpoints are inclusive) + expect(await blockStore.getCheckpointNumbersForSlotRange(SlotNumber(8), SlotNumber(8))).toEqual([2]); + + // Range with no matching checkpoints + expect(await blockStore.getCheckpointNumbersForSlotRange(SlotNumber(9), SlotNumber(11))).toEqual([]); + }); + + it('reflects unwound checkpoints', async () => { + const cp1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1, slotNumber: SlotNumber(1) }), + 10, + ); + const previousArchive1 = cp1.checkpoint.blocks.at(-1)!.archive; + const cp2 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(2), { + numBlocks: 1, + startBlockNumber: 2, + previousArchive: previousArchive1, + slotNumber: SlotNumber(2), + }), + 11, + ); + await blockStore.addCheckpoints([cp1, cp2]); + + await blockStore.removeCheckpointsAfter(CheckpointNumber(1)); + + expect(await blockStore.getCheckpointNumbersForSlotRange(SlotNumber(0), SlotNumber(10))).toEqual([1]); + }); + }); + describe('getCheckpointedBlock', () => { beforeEach(async () => { await blockStore.addCheckpoints(publishedCheckpoints); }); it.each(blockNumberTests)('retrieves previously stored block %i', async (blockNumber, getExpectedBlock) => { - const retrievedBlock = await blockStore.getCheckpointedBlock(BlockNumber(blockNumber)); + const retrievedBlock = await blockStore.getBlock({ number: BlockNumber(blockNumber) }); const expectedBlock = getExpectedBlock(); const expectedCheckpoint = publishedCheckpoints[blockNumber - 1]; expect(retrievedBlock).toBeDefined(); - expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint); + await expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint); }); it('returns undefined if block is not found', async () => { - await expect(blockStore.getCheckpointedBlock(BlockNumber(12))).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ number: BlockNumber(12) })).resolves.toBeUndefined(); }); it('returns undefined for block number 0', async () => { - await expect(blockStore.getCheckpointedBlock(BlockNumber(0))).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ number: BlockNumber(0) })).resolves.toBeUndefined(); }); }); @@ -1720,15 +1718,15 @@ describe('BlockStore', () => { const expectedCheckpoint = publishedCheckpoints[5]; const expectedBlock = expectedCheckpoint.checkpoint.blocks[0]; const blockHash = await expectedBlock.header.hash(); - const retrievedBlock = await blockStore.getCheckpointedBlockByHash(blockHash); + const retrievedBlock = await blockStore.getBlock({ hash: blockHash }); expect(retrievedBlock).toBeDefined(); - expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint); + await expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint); }); it('returns undefined for non-existent block hash', async () => { const nonExistentHash = BlockHash.random(); - await expect(blockStore.getCheckpointedBlockByHash(nonExistentHash)).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ hash: nonExistentHash })).resolves.toBeUndefined(); }); }); @@ -1741,27 +1739,27 @@ describe('BlockStore', () => { const expectedCheckpoint = publishedCheckpoints[3]; const expectedBlock = expectedCheckpoint.checkpoint.blocks[0]; const archive = expectedBlock.archive.root; - const retrievedBlock = await blockStore.getCheckpointedBlockByArchive(archive); + const retrievedBlock = await blockStore.getBlock({ archive: archive }); expect(retrievedBlock).toBeDefined(); - expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint); + await expectCheckpointedBlockEquals(retrievedBlock!, expectedBlock, expectedCheckpoint); }); it('returns undefined for non-existent archive root', async () => { const nonExistentArchive = Fr.random(); - await expect(blockStore.getCheckpointedBlockByArchive(nonExistentArchive)).resolves.toBeUndefined(); + await expect(blockStore.getBlock({ archive: nonExistentArchive })).resolves.toBeUndefined(); }); }); - describe('getBlockHeaderByHash', () => { + describe('getBlockData by hash', () => { beforeEach(async () => { await blockStore.addCheckpoints(publishedCheckpoints); }); - it('retrieves a block header by its hash', async () => { + it('retrieves block header by hash', async () => { const expectedBlock = publishedCheckpoints[7].checkpoint.blocks[0]; const blockHash = await expectedBlock.header.hash(); - const retrievedHeader = await blockStore.getBlockHeaderByHash(blockHash); + const retrievedHeader = (await blockStore.getBlockData({ hash: blockHash }))?.header; expect(retrievedHeader).toBeDefined(); expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true); @@ -1769,19 +1767,19 @@ describe('BlockStore', () => { it('returns undefined for non-existent block hash', async () => { const nonExistentHash = BlockHash.random(); - await expect(blockStore.getBlockHeaderByHash(nonExistentHash)).resolves.toBeUndefined(); + await expect(blockStore.getBlockData({ hash: nonExistentHash })).resolves.toBeUndefined(); }); }); - describe('getBlockHeaderByArchive', () => { + describe('getBlockData by archive', () => { beforeEach(async () => { await blockStore.addCheckpoints(publishedCheckpoints); }); - it('retrieves a block header by its archive root', async () => { + it('retrieves block header by archive root', async () => { const expectedBlock = publishedCheckpoints[2].checkpoint.blocks[0]; const archive = expectedBlock.archive.root; - const retrievedHeader = await blockStore.getBlockHeaderByArchive(archive); + const retrievedHeader = (await blockStore.getBlockData({ archive: archive }))?.header; expect(retrievedHeader).toBeDefined(); expect(retrievedHeader!.equals(expectedBlock.header)).toBe(true); @@ -1789,7 +1787,36 @@ describe('BlockStore', () => { it('returns undefined for non-existent archive root', async () => { const nonExistentArchive = Fr.random(); - await expect(blockStore.getBlockHeaderByArchive(nonExistentArchive)).resolves.toBeUndefined(); + await expect(blockStore.getBlockData({ archive: nonExistentArchive })).resolves.toBeUndefined(); + }); + }); + + describe('getBlockNumber', () => { + beforeEach(async () => { + await blockStore.addCheckpoints(publishedCheckpoints); + }); + + it('resolves a number query', async () => { + await expect(blockStore.getBlockNumber({ number: BlockNumber(3) })).resolves.toBe(3); + }); + + it('resolves a hash query', async () => { + const block = publishedCheckpoints[4].checkpoint.blocks[0]; + const hash = await block.header.hash(); + await expect(blockStore.getBlockNumber({ hash })).resolves.toBe(block.number); + }); + + it('resolves an archive query', async () => { + const block = publishedCheckpoints[6].checkpoint.blocks[0]; + await expect(blockStore.getBlockNumber({ archive: block.archive.root })).resolves.toBe(block.number); + }); + + it('returns undefined for unknown hash', async () => { + await expect(blockStore.getBlockNumber({ hash: BlockHash.random() })).resolves.toBeUndefined(); + }); + + it('returns undefined for unknown archive', async () => { + await expect(blockStore.getBlockNumber({ archive: Fr.random() })).resolves.toBeUndefined(); }); }); @@ -1989,7 +2016,7 @@ describe('BlockStore', () => { await expect(blockStore.addCheckpoints([publishedCheckpoint2])).resolves.toBe(true); // Verify block exists and is consistent - const storedBlock = await blockStore.getBlock(BlockNumber(2)); + const storedBlock = await blockStore.getBlock({ number: BlockNumber(2) }); expect(storedBlock?.archive.root.equals(provisionalBlock.archive.root)).toBe(true); }); @@ -2079,6 +2106,511 @@ describe('BlockStore', () => { }); }); + describe('proposedCheckpointNumber', () => { + /** Adds proposed blocks to the store so addProposedCheckpoint can validate them. + * Uses force: true to skip addProposedBlock's own chaining checks (we only want to test addProposedCheckpoint). */ + async function addBlocksForProposedCheckpoint( + startBlock: number, + blockCount: number, + checkpointNumber: number, + previousArchive?: AppendOnlyTreeSnapshot, + ): Promise { + for (let i = 0; i < blockCount; i++) { + const opts: Parameters[1] = { + checkpointNumber: CheckpointNumber(checkpointNumber), + indexWithinCheckpoint: IndexWithinCheckpoint(i), + }; + if (i === 0 && previousArchive) { + (opts as any).lastArchive = previousArchive; + } + const block = await L2Block.random(BlockNumber(startBlock + i), opts); + await blockStore.addProposedBlock(block, { force: true }); + } + } + + it('returns initial value when no proposed checkpoint is set', async () => { + const pending = await blockStore.getProposedCheckpointNumber(); + expect(pending).toBe(INITIAL_CHECKPOINT_NUMBER - 1); + }); + + it('stores and retrieves proposed checkpoint number', async () => { + await addBlocksForProposedCheckpoint(1, 1, 1); + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(1), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(1), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + const pending = await blockStore.getProposedCheckpointNumber(); + expect(pending).toBe(1); + }); + + it('stores and retrieves proposed checkpoint data with fee fields', async () => { + await addBlocksForProposedCheckpoint(1, 1, 1); + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(1), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(1), + blockCount: 1, + totalManaUsed: 12345n, + feeAssetPriceModifier: -75n, + }); + const pending = await blockStore.getLastProposedCheckpoint(); + expect(pending).toBeDefined(); + expect(pending!.checkpointNumber).toBe(1); + expect(pending!.totalManaUsed).toBe(12345n); + expect(pending!.feeAssetPriceModifier).toBe(-75n); + }); + + it('clears proposed checkpoint when confirmed checkpoints are added', async () => { + // Add checkpoint 1 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Add blocks for proposed checkpoint 2, chaining from checkpoint 1's last block + await addBlocksForProposedCheckpoint(2, 1, 2, checkpoint1.checkpoint.blocks[0].archive); + + // Set proposed checkpoint to 2 (attested but not yet on L1) + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(2), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(2), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + expect(await blockStore.getProposedCheckpointNumber()).toBe(2); + + // Confirm checkpoint 2 on L1 + const checkpoint2 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(2), { + numBlocks: 1, + startBlockNumber: 2, + previousArchive: checkpoint1.checkpoint.blocks[0].archive, + }), + 20, + ); + await blockStore.addCheckpoints([checkpoint2]); + + // Proposed checkpoint should be cleared + expect(await blockStore.hasProposedCheckpoint()).toBe(false); + }); + + it('throws on proposed checkpoint that is more than 1 ahead of confirmed', async () => { + // Add checkpoint 1 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Try to set proposed checkpoint to 3 (confirmed=1, expected=2) + await expect( + blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(3), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(1), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }), + ).rejects.toThrow('not sequential'); + + // Proposed checkpoint should remain unset (3 !== 1 + 1) + expect(await blockStore.hasProposedCheckpoint()).toBe(false); + }); + + it('throws on proposed checkpoint that equals the confirmed checkpoint', async () => { + // Add checkpoint 1 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Try to set proposed checkpoint to 1 (confirmed=1, expected=2). + // With fallback behavior, getProposedCheckpointNumber returns 1 (confirmed), so this triggers the stale check. + await expect( + blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(1), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(1), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }), + ).rejects.toThrow('not sequential'); + + // Proposed checkpoint should remain unset + expect(await blockStore.hasProposedCheckpoint()).toBe(false); + }); + + it('clears proposed checkpoint when checkpoints are removed past it', async () => { + // Add checkpoints 1 and 2 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + const checkpoint2 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(2), { + numBlocks: 1, + startBlockNumber: 2, + previousArchive: checkpoint1.checkpoint.blocks[0].archive, + }), + 20, + ); + await blockStore.addCheckpoints([checkpoint1, checkpoint2]); + + // Add blocks for proposed checkpoint 3, chaining from checkpoint 2's last block + await addBlocksForProposedCheckpoint(3, 1, 3, checkpoint2.checkpoint.blocks[0].archive); + + // Set proposed checkpoint to 3 + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(3), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(3), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + + // Remove checkpoints after 1 (removes checkpoint 2, and pending 3 should be cleared) + await blockStore.removeCheckpointsAfter(CheckpointNumber(1)); + + expect(await blockStore.hasProposedCheckpoint()).toBe(false); + }); + + it('does not clear proposed checkpoint when removing checkpoints before it', async () => { + // Add checkpoints 1, 2 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + const checkpoint2 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(2), { + numBlocks: 1, + startBlockNumber: 2, + previousArchive: checkpoint1.checkpoint.blocks[0].archive, + }), + 20, + ); + await blockStore.addCheckpoints([checkpoint1, checkpoint2]); + + // Add blocks for proposed checkpoint 3, chaining from checkpoint 2's last block + await addBlocksForProposedCheckpoint(3, 1, 3, checkpoint2.checkpoint.blocks[0].archive); + + // Set pending to 3 (confirmed=2, 3===2+1 ✓) + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(3), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(3), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + + // Remove checkpoints after 2 (nothing removed since latest is 2, pending=3 stays) + await blockStore.removeCheckpointsAfter(CheckpointNumber(2)); + + expect(await blockStore.getProposedCheckpointNumber()).toBe(3); + }); + + it('allows addProposedBlocks when proposed checkpoint matches expected', async () => { + // Add checkpoint 1 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Add blocks for proposed checkpoint 2, chaining from checkpoint 1's last block + await addBlocksForProposedCheckpoint(2, 1, 2, checkpoint1.checkpoint.blocks[0].archive); + + // Set proposed checkpoint to 2 (attested but not on L1 yet) + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(2), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(2), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + + // Add a block for checkpoint 3 — this should succeed because + // proposed checkpoint (2) matches expectedCheckpointNumber (3 - 1 = 2) + const pendingBlock = await blockStore.getBlock({ number: BlockNumber(2) }); + const block3 = await L2Block.random(BlockNumber(3), { + checkpointNumber: CheckpointNumber(3), + indexWithinCheckpoint: IndexWithinCheckpoint(0), + lastArchive: pendingBlock!.archive, + }); + + await expect(blockStore.addProposedBlock(block3)).resolves.toBe(true); + }); + + it('throws with proposed checkpoint value when neither confirmed nor pending matches', async () => { + // Add checkpoint 1 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Add blocks for proposed checkpoint 2, chaining from checkpoint 1's last block + await addBlocksForProposedCheckpoint(2, 1, 2, checkpoint1.checkpoint.blocks[0].archive); + + // Set proposed checkpoint to 2 + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(2), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(2), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + + // Try to add a block for checkpoint 4 (expected = 3, confirmed = 1, pending = 2 — neither matches) + const pendingBlock = await blockStore.getBlock({ number: BlockNumber(2) }); + const block3 = await L2Block.random(BlockNumber(3), { + checkpointNumber: CheckpointNumber(4), + indexWithinCheckpoint: IndexWithinCheckpoint(0), + lastArchive: pendingBlock!.archive, + }); + + await expect(blockStore.addProposedBlock(block3)).rejects.toThrow( + // Error should report the proposed checkpoint number (2), not the confirmed one (1) + 'Cannot insert new block 3 for checkpoint 4 given previous checkpoint number is 2', + ); + }); + + it('throws with confirmed checkpoint value when pending is not set', async () => { + // Add checkpoint 1 (no pending set, so pending defaults to 0) + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Try to add a block for checkpoint 4 (expected = 3, confirmed = 1, pending = 0) + // Error should report confirmed (1) since it's higher than the default pending (0) + const lastBlockArchive = checkpoint1.checkpoint.blocks[0].archive; + const block2 = await L2Block.random(BlockNumber(2), { + checkpointNumber: CheckpointNumber(4), + indexWithinCheckpoint: IndexWithinCheckpoint(0), + lastArchive: lastBlockArchive, + }); + + await expect(blockStore.addProposedBlock(block2)).rejects.toThrow( + 'Cannot insert new block 2 for checkpoint 4 given previous checkpoint number is 1', + ); + }); + + it('getProposedCheckpointL2BlockNumber defaults to checkpointed block number', async () => { + // Add checkpoint 1 with blocks 1-3 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // No proposed checkpoint set — should fall back to the checkpointed block number + const pendingBlockNumber = await blockStore.getProposedCheckpointL2BlockNumber(); + const checkpointedBlockNumber = await blockStore.getCheckpointedL2BlockNumber(); + expect(pendingBlockNumber).toBe(checkpointedBlockNumber); + expect(pendingBlockNumber).toBe(3); + }); + + it('getProposedCheckpointL2BlockNumber returns pending block number when set', async () => { + // Add checkpoint 1 with block 1 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Add proposed block for proposed checkpoint 2 + await addBlocksForProposedCheckpoint(2, 1, 2, checkpoint1.checkpoint.blocks[0].archive); + + // Set proposed checkpoint + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(2), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(2), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + + // Should return last block of proposed checkpoint (startBlock + blockCount - 1) + const pendingBlockNumber = await blockStore.getProposedCheckpointL2BlockNumber(); + expect(pendingBlockNumber).toBe(2); + // And it should be greater than the checkpointed block number + expect(pendingBlockNumber).toBeGreaterThan(await blockStore.getCheckpointedL2BlockNumber()); + }); + + it('getProposedCheckpointL2BlockNumber falls back to checkpointed after pending is cleared', async () => { + // Add checkpoint 1 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Add blocks and set proposed checkpoint 2 + await addBlocksForProposedCheckpoint(2, 1, 2, checkpoint1.checkpoint.blocks[0].archive); + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(2), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(2), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + expect(await blockStore.getProposedCheckpointL2BlockNumber()).toBe(2); + + // Confirm checkpoint 2 on L1 (clears pending) + const checkpoint2 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(2), { + numBlocks: 1, + startBlockNumber: 2, + previousArchive: checkpoint1.checkpoint.blocks[0].archive, + }), + 20, + ); + await blockStore.addCheckpoints([checkpoint2]); + + // Pending cleared — should fall back to the new checkpointed block number + const pendingBlockNumber = await blockStore.getProposedCheckpointL2BlockNumber(); + const checkpointedBlockNumber = await blockStore.getCheckpointedL2BlockNumber(); + expect(pendingBlockNumber).toBe(checkpointedBlockNumber); + expect(pendingBlockNumber).toBe(2); + }); + }); + + describe('promoteProposedToCheckpointed', () => { + async function setupProposedCheckpoint() { + // Add confirmed checkpoint 1 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Add proposed blocks for checkpoint 2 + const block2 = await L2Block.random(BlockNumber(2), { + checkpointNumber: CheckpointNumber(2), + indexWithinCheckpoint: IndexWithinCheckpoint(0), + lastArchive: checkpoint1.checkpoint.blocks[0].archive, + }); + await blockStore.addProposedBlock(block2, { force: true }); + + // Set proposed checkpoint 2 + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(2), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(2), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + + const proposed = await blockStore.getLastProposedCheckpoint(); + return { checkpoint1, proposed: proposed! }; + } + + it('promotes proposed checkpoint to confirmed', async () => { + const { proposed } = await setupProposedCheckpoint(); + const l1 = makeL1PublishedData(20); + const attestations = [CommitteeAttestation.random()]; + + await blockStore.promoteProposedToCheckpointed( + proposed.checkpointNumber, + l1, + attestations, + proposed.archive.root, + ); + + expect(await blockStore.hasProposedCheckpoint()).toBe(false); + expect(await blockStore.getLatestCheckpointNumber()).toBe(2); + }); + + it('throws when no proposed checkpoint exists', async () => { + await expect( + blockStore.promoteProposedToCheckpointed(CheckpointNumber(1), makeL1PublishedData(20), [], Fr.random()), + ).rejects.toThrow('no proposed checkpoint exists'); + }); + + it('throws on archive root mismatch', async () => { + const { proposed } = await setupProposedCheckpoint(); + + await expect( + blockStore.promoteProposedToCheckpointed(proposed.checkpointNumber, makeL1PublishedData(20), [], Fr.random()), + ).rejects.toThrow('archive root mismatch'); + + // Proposed checkpoint should still exist (transaction rolled back) + expect(await blockStore.hasProposedCheckpoint()).toBe(true); + }); + }); + + describe('L2TipsCache proposedCheckpoint', () => { + it('returns proposedCheckpoint equal to checkpointed when no pending exists', async () => { + // Add checkpoint 1 with blocks 1-3 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 3, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + const l2TipsCache = new L2TipsCache(blockStore, GENESIS_BLOCK_HEADER_HASH); + const tips = await l2TipsCache.getL2Tips(); + + // proposedCheckpoint should always be defined + expect(tips.proposedCheckpoint).toBeDefined(); + // With no proposed checkpoint, it should equal the checkpointed tip + expect(tips.proposedCheckpoint!.block.number).toBe(tips.checkpointed.block.number); + expect(tips.proposedCheckpoint!.checkpoint.number).toBe(tips.checkpointed.checkpoint.number); + }); + + it('returns proposedCheckpoint ahead of checkpointed when pending is set', async () => { + // Add checkpoint 1 + const checkpoint1 = makePublishedCheckpoint( + await Checkpoint.random(CheckpointNumber(1), { numBlocks: 1, startBlockNumber: 1 }), + 10, + ); + await blockStore.addCheckpoints([checkpoint1]); + + // Add a proposed block for proposed checkpoint 2, chaining from checkpoint 1 + const block2 = await L2Block.random(BlockNumber(2), { + checkpointNumber: CheckpointNumber(2), + indexWithinCheckpoint: IndexWithinCheckpoint(0), + lastArchive: checkpoint1.checkpoint.blocks[0].archive, + }); + await blockStore.addProposedBlock(block2, { force: true }); + + // Set proposed checkpoint + await blockStore.addProposedCheckpoint({ + checkpointNumber: CheckpointNumber(2), + header: CheckpointHeader.empty(), + startBlock: BlockNumber(2), + blockCount: 1, + totalManaUsed: 100n, + feeAssetPriceModifier: 50n, + }); + + const l2TipsCache = new L2TipsCache(blockStore, GENESIS_BLOCK_HEADER_HASH); + const tips = await l2TipsCache.getL2Tips(); + + expect(tips.proposedCheckpoint).toBeDefined(); + expect(tips.proposedCheckpoint!.block.number).toBeGreaterThan(tips.checkpointed.block.number); + expect(tips.proposedCheckpoint!.checkpoint.number).toBeGreaterThan(tips.checkpointed.checkpoint.number); + }); + }); + describe('removeBlocksAfterBlock', () => { it('removes blocks with number > given blockNumber', async () => { // Create blocks for initial checkpoint @@ -2109,10 +2641,10 @@ describe('BlockStore', () => { await blockStore.removeBlocksAfter(BlockNumber(2)); expect(await blockStore.getLatestL2BlockNumber()).toBe(2); - expect(await blockStore.getBlock(BlockNumber(1))).toBeDefined(); - expect(await blockStore.getBlock(BlockNumber(2))).toBeDefined(); - expect(await blockStore.getBlock(BlockNumber(3))).toBeUndefined(); - expect(await blockStore.getBlock(BlockNumber(4))).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(1) })).toBeDefined(); + expect(await blockStore.getBlock({ number: BlockNumber(2) })).toBeDefined(); + expect(await blockStore.getBlock({ number: BlockNumber(3) })).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(4) })).toBeUndefined(); }); it('returns the removed blocks', async () => { @@ -2186,8 +2718,8 @@ describe('BlockStore', () => { const block2Hash = await block2.header.hash(); const block2Archive = block2.archive.root; - expect(await blockStore.getBlockByHash(block2Hash)).toBeDefined(); - expect(await blockStore.getBlockByArchive(block2Archive)).toBeDefined(); + expect(await blockStore.getBlock({ hash: block2Hash })).toBeDefined(); + expect(await blockStore.getBlock({ archive: block2Archive })).toBeDefined(); // Verify tx effects for block2 are retrievable before removal for (const txEffect of block2.body.txEffects) { @@ -2199,8 +2731,8 @@ describe('BlockStore', () => { await blockStore.removeBlocksAfter(BlockNumber(1)); // Verify block2 is no longer retrievable by hash or archive - expect(await blockStore.getBlockByHash(block2Hash)).toBeUndefined(); - expect(await blockStore.getBlockByArchive(block2Archive)).toBeUndefined(); + expect(await blockStore.getBlock({ hash: block2Hash })).toBeUndefined(); + expect(await blockStore.getBlock({ archive: block2Archive })).toBeUndefined(); // Verify tx effects for block2 are no longer retrievable for (const txEffect of block2.body.txEffects) { @@ -2212,8 +2744,8 @@ describe('BlockStore', () => { const block1Hash = await block1.header.hash(); const block1Archive = block1.archive.root; - expect(await blockStore.getBlockByHash(block1Hash)).toBeDefined(); - expect(await blockStore.getBlockByArchive(block1Archive)).toBeDefined(); + expect(await blockStore.getBlock({ hash: block1Hash })).toBeDefined(); + expect(await blockStore.getBlock({ archive: block1Archive })).toBeDefined(); for (const txEffect of block1.body.txEffects) { const retrieved = await blockStore.getTxEffect(txEffect.txHash); @@ -2238,8 +2770,8 @@ describe('BlockStore', () => { expect(removedBlocks.length).toBe(2); expect(await blockStore.getLatestL2BlockNumber()).toBe(0); - expect(await blockStore.getBlock(BlockNumber(1))).toBeUndefined(); - expect(await blockStore.getBlock(BlockNumber(2))).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(1) })).toBeUndefined(); + expect(await blockStore.getBlock({ number: BlockNumber(2) })).toBeUndefined(); }); }); }); diff --git a/yarn-project/archiver/src/store/block_store.ts b/yarn-project/archiver/src/store/block_store.ts index 5770adad285a..6ec9d07f19b2 100644 --- a/yarn-project/archiver/src/store/block_store.ts +++ b/yarn-project/archiver/src/store/block_store.ts @@ -10,10 +10,8 @@ import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } fro import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import { type BlockData, - type BlockDataWithCheckpointContext, BlockHash, Body, - CheckpointedL2Block, CommitteeAttestation, L2Block, type ValidateCheckpointResult, @@ -96,6 +94,19 @@ type ProposedCheckpointStorage = CommonCheckpointStorage & { export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined }; +/** + * Single-block lookup with the chain-tip `tag` variant of {@link BlockQuery} already resolved + * to a concrete block number. The `tag` branch is unrepresentable here so storage code does + * not need to handle it at runtime. + */ +export type ResolvedBlockQuery = { number: BlockNumber } | { hash: BlockHash } | { archive: Fr }; + +/** + * Range lookup with the `epoch` variant of {@link BlocksQuery} already resolved to a + * `{ from, limit }` pair. Storage code never needs to map epoch numbers to block ranges. + */ +export type ResolvedBlocksQuery = { from: BlockNumber; limit: number; onlyCheckpointed?: boolean }; + /** * LMDB-based block storage for the archiver. */ @@ -196,7 +207,7 @@ export class BlockStore { const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber(); if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) { // Check if the proposed block matches the already-checkpointed one - const existingBlock = await this.getBlock(BlockNumber(blockNumber)); + const existingBlock = await this.getBlockData({ number: BlockNumber(blockNumber) }); if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) { throw new BlockAlreadyCheckpointedError(blockNumber); } @@ -215,11 +226,11 @@ export class BlockStore { if (!opts.force && latestCheckpointNumber !== expectedCheckpointNumber && !hasPendingAtExpected) { const [latestPendingKey] = await toArray(this.#proposedCheckpoints.keysAsync({ reverse: true, limit: 1 })); const previous = CheckpointNumber(Math.max(latestCheckpointNumber, latestPendingKey ?? 0)); - throw new BlockCheckpointNumberNotSequentialError(blockNumber, blockCheckpointNumber, previous); + throw new BlockCheckpointNumberNotSequentialError(BlockNumber(blockNumber), blockCheckpointNumber, previous); } // Extract the previous block if there is one and see if it is for the same checkpoint or not - const previousBlockResult = await this.getBlock(previousBlockNumber); + const previousBlockResult = await this.getBlockData({ number: previousBlockNumber }); let expectedBlockIndex = 0; let previousBlockIndex: number | undefined = undefined; @@ -232,7 +243,7 @@ export class BlockStore { if (!previousBlockResult.archive.root.equals(blockLastArchive)) { throw new BlockArchiveNotConsistentError( blockNumber, - previousBlockResult.number, + previousBlockResult.header.globalVariables.blockNumber, blockLastArchive, previousBlockResult.archive.root, ); @@ -398,7 +409,7 @@ export class BlockStore { } const previousBlockNumber = BlockNumber(predecessor.startBlock + predecessor.blockCount - 1); - const previousBlock = await this.getBlock(previousBlockNumber); + const previousBlock = await this.getBlock({ number: previousBlockNumber }); if (previousBlock === undefined) { throw new BlockNotFoundError(previousBlockNumber); } @@ -577,6 +588,22 @@ export class BlockStore { return result; } + /** + * Returns the checkpoint numbers for all checkpoints whose slot falls within the given range (inclusive). + * Lighter than {@link getCheckpointDataForSlotRange} when callers only need to identify which + * checkpoints fall in the range and will fetch full data for at most a few of them. + */ + async getCheckpointNumbersForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise { + const result: CheckpointNumber[] = []; + for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({ + start: startSlot, + end: endSlot + 1, + })) { + result.push(CheckpointNumber(checkpointNumber)); + } + return result; + } + private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData { return { header: CheckpointHeader.fromBuffer(checkpointStorage.header), @@ -648,7 +675,7 @@ export class BlockStore { // Iterate from blockNumber + 1 to latestBlockNumber for (let bn = blockNumber + 1; bn <= latestBlockNumber; bn++) { - const block = await this.getBlock(BlockNumber(bn)); + const block = await this.getBlock({ number: BlockNumber(bn) }); if (block === undefined) { this.#log.warn(`Cannot remove block ${bn} from the store since we don't have it`); @@ -770,15 +797,6 @@ export class BlockStore { return stored ? this.convertToProposedCheckpointData(stored) : undefined; } - /** Returns all pending checkpoints in ascending checkpoint-number order. */ - async getProposedCheckpoints(): Promise { - const results: ProposedCheckpointData[] = []; - for await (const [, stored] of this.#proposedCheckpoints.entriesAsync()) { - results.push(this.convertToProposedCheckpointData(stored)); - } - return results; - } - /** * Evicts all pending checkpoints with checkpoint number >= fromNumber. * Used for divergent-mined-checkpoint cleanup: when L1 mines checkpoint N with a different archive, @@ -846,195 +864,33 @@ export class BlockStore { return BlockNumber(proposed.startBlock + proposed.blockCount - 1); } - async getCheckpointedBlock(number: BlockNumber): Promise { - const blockStorage = await this.#blocks.getAsync(number); - if (!blockStorage) { - return undefined; - } - const checkpoint = await this.#checkpoints.getAsync(blockStorage.checkpointNumber); - if (!checkpoint) { - return undefined; - } - const block = await this.getBlockFromBlockStorage(number, blockStorage); - if (!block) { - return undefined; - } - return new CheckpointedL2Block( - CheckpointNumber(checkpoint.checkpointNumber), - block, - L1PublishedData.fromBuffer(checkpoint.l1), - checkpoint.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)), - ); - } - - /** - * Gets up to `limit` amount of Checkpointed L2 blocks starting from `from`. - * @param start - Number of the first block to return (inclusive). - * @param limit - The number of blocks to return. - * @returns The requested L2 blocks - */ - getCheckpointedBlocks(start: BlockNumber, limit: number): Promise { - return toArray(this.iterateCheckpointedBlocks(start, limit)); - } - - /** Async iterator variant of {@link getCheckpointedBlocks}. */ - async *iterateCheckpointedBlocks(start: BlockNumber, limit: number): AsyncIterableIterator { - const checkpointCache = new Map(); - for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) { - const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage); - if (block) { - const checkpoint = - checkpointCache.get(CheckpointNumber(blockStorage.checkpointNumber)) ?? - (await this.#checkpoints.getAsync(blockStorage.checkpointNumber)); - if (checkpoint) { - checkpointCache.set(CheckpointNumber(blockStorage.checkpointNumber), checkpoint); - const checkpointedBlock = new CheckpointedL2Block( - CheckpointNumber(checkpoint.checkpointNumber), - block, - L1PublishedData.fromBuffer(checkpoint.l1), - checkpoint.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)), - ); - yield checkpointedBlock; - } - } - } - } - - async getCheckpointedBlockByHash(blockHash: BlockHash): Promise { - const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString()); - if (blockNumber === undefined) { - return undefined; - } - return this.getCheckpointedBlock(BlockNumber(blockNumber)); - } - - async getCheckpointedBlockByArchive(archive: Fr): Promise { - const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString()); - if (blockNumber === undefined) { - return undefined; - } - return this.getCheckpointedBlock(BlockNumber(blockNumber)); - } - - /** - * Gets up to `limit` amount of L2 blocks starting from `from`. - * @param start - Number of the first block to return (inclusive). - * @param limit - The number of blocks to return. - * @returns The requested L2 blocks - */ - getBlocks(start: BlockNumber, limit: number): Promise { - return toArray(this.iterateBlocks(start, limit)); - } - - /** Async iterator variant of {@link getBlocks}. */ - async *iterateBlocks(start: BlockNumber, limit: number): AsyncIterableIterator { - for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) { - const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage); - if (block) { - yield block; - } - } - } - - /** - * Gets block metadata (without tx data) by block number. - * @param blockNumber - The number of the block to return. - * @returns The requested block data. - */ - async getBlockData(blockNumber: BlockNumber): Promise { - const blockStorage = await this.#blocks.getAsync(blockNumber); - if (!blockStorage || !blockStorage.header) { - return undefined; - } - return this.getBlockDataFromBlockStorage(blockStorage); - } - - /** - * Gets block metadata plus checkpoint-derived context (L1 publish info, attestations) without - * deserializing tx bodies. When the block's containing checkpoint has not yet been L1-confirmed, - * `checkpoint` and `l1` are `undefined` and `attestations` is empty. - */ - async getBlockDataWithCheckpointContext( - blockNumber: BlockNumber, - ): Promise { - const blockStorage = await this.#blocks.getAsync(blockNumber); - if (!blockStorage || !blockStorage.header) { - return undefined; - } - const data = this.getBlockDataFromBlockStorage(blockStorage); - const checkpointStorage = await this.#checkpoints.getAsync(blockStorage.checkpointNumber); - if (!checkpointStorage) { - return { data, checkpoint: undefined, l1: undefined, attestations: [] }; - } - const checkpoint = this.checkpointDataFromCheckpointStorage(checkpointStorage); - return { data, checkpoint, l1: checkpoint.l1, attestations: checkpoint.attestations }; - } - /** Returns the checkpoint number that contains the given slot (or undefined if not found). */ async getCheckpointNumberBySlot(slot: SlotNumber): Promise { const checkpointNumber = await this.#slotToCheckpoint.getAsync(slot); return checkpointNumber === undefined ? undefined : CheckpointNumber(checkpointNumber); } - /** - * Gets block metadata (without tx data) by archive root. - * @param archive - The archive root of the block to return. - * @returns The requested block data. - */ - async getBlockDataByArchive(archive: Fr): Promise { - const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString()); + /** Gets a single L2 block matching the given resolved query. */ + async getBlock(query: ResolvedBlockQuery): Promise { + const blockNumber = await this.getBlockNumber(query); if (blockNumber === undefined) { return undefined; } - return this.getBlockData(BlockNumber(blockNumber)); - } - - /** - * Gets an L2 block. - * @param blockNumber - The number of the block to return. - * @returns The requested L2 block. - */ - async getBlock(blockNumber: BlockNumber): Promise { const blockStorage = await this.#blocks.getAsync(blockNumber); - if (!blockStorage || !blockStorage.header) { - return Promise.resolve(undefined); + if (!blockStorage) { + return undefined; } return this.getBlockFromBlockStorage(blockNumber, blockStorage); } - /** - * Gets an L2 block by its hash. - * @param blockHash - The hash of the block to return. - * @returns The requested L2 block. - */ - async getBlockByHash(blockHash: BlockHash): Promise { - const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString()); - if (blockNumber === undefined) { - return undefined; - } - return this.getBlock(BlockNumber(blockNumber)); + /** Gets a collection of L2 blocks for a resolved range. */ + getBlocks(query: ResolvedBlocksQuery): Promise { + return toArray(this.iterateBlocks(query)); } - /** - * Gets an L2 block by its archive root. - * @param archive - The archive root of the block to return. - * @returns The requested L2 block. - */ - async getBlockByArchive(archive: Fr): Promise { - const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString()); - if (blockNumber === undefined) { - return undefined; - } - return this.getBlock(BlockNumber(blockNumber)); - } - - /** - * Gets a block header by its hash. - * @param blockHash - The hash of the block to return. - * @returns The requested block header. - */ - async getBlockHeaderByHash(blockHash: BlockHash): Promise { - const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString()); + /** Gets single block metadata matching the given resolved query. */ + async getBlockData(query: ResolvedBlockQuery): Promise { + const blockNumber = await this.getBlockNumber(query); if (blockNumber === undefined) { return undefined; } @@ -1042,46 +898,36 @@ export class BlockStore { if (!blockStorage || !blockStorage.header) { return undefined; } - return BlockHeader.fromBuffer(blockStorage.header); + return this.getBlockDataFromBlockStorage(blockStorage); } - /** - * Gets a block header by its archive root. - * @param archive - The archive root of the block to return. - * @returns The requested block header. - */ - async getBlockHeaderByArchive(archive: Fr): Promise { - const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString()); - if (blockNumber === undefined) { - return undefined; - } - const blockStorage = await this.#blocks.getAsync(blockNumber); - if (!blockStorage || !blockStorage.header) { - return undefined; - } - return BlockHeader.fromBuffer(blockStorage.header); + /** Gets a collection of block metadata entries for a resolved range. */ + getBlocksData(query: ResolvedBlocksQuery): Promise { + return toArray(this.iterateBlocksData(query)); } - /** - * Gets the headers for a sequence of L2 blocks. - * @param start - Number of the first block to return (inclusive). - * @param limit - The number of blocks to return. - * @returns The requested L2 block headers - */ - getBlockHeaders(start: BlockNumber, limit: number): Promise { - return toArray(this.iterateBlockHeaders(start, limit)); + /** Async iterator over L2 blocks for a resolved range. */ + private async *iterateBlocks(query: ResolvedBlocksQuery): AsyncIterableIterator { + const cap = query.onlyCheckpointed ? await this.getCheckpointedL2BlockNumber() : undefined; + for await (const [blockNumber, blockStorage] of this.getBlockStorages(query.from, query.limit)) { + if (cap !== undefined && blockNumber > cap) { + break; + } + const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage); + if (block) { + yield block; + } + } } - /** Async iterator variant of {@link getBlockHeaders}. */ - async *iterateBlockHeaders(start: BlockNumber, limit: number): AsyncIterableIterator { - for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) { - const header = BlockHeader.fromBuffer(blockStorage.header); - if (header.getBlockNumber() !== blockNumber) { - throw new Error( - `Block number mismatch when retrieving block header from archive (expected ${blockNumber} but got ${header.getBlockNumber()})`, - ); + /** Async iterator over block metadata for a resolved range. */ + private async *iterateBlocksData(query: ResolvedBlocksQuery): AsyncIterableIterator { + const cap = query.onlyCheckpointed ? await this.getCheckpointedL2BlockNumber() : undefined; + for await (const [blockNumber, blockStorage] of this.getBlockStorages(query.from, query.limit)) { + if (cap !== undefined && blockNumber > cap) { + break; } - yield header; + yield this.getBlockDataFromBlockStorage(blockStorage); } } @@ -1098,6 +944,24 @@ export class BlockStore { } } + /** Resolves a ResolvedBlockQuery discriminant to a block number, or undefined if not found. */ + async getBlockNumber(query: ResolvedBlockQuery): Promise { + let blockNumber: BlockNumber | undefined; + if ('number' in query) { + blockNumber = query.number; + } else if ('hash' in query) { + const n = await this.#blockHashIndex.getAsync(query.hash.toString()); + blockNumber = n !== undefined ? BlockNumber(n) : undefined; + } else { + const n = await this.#blockArchiveIndex.getAsync(query.archive.toString()); + blockNumber = n !== undefined ? BlockNumber(n) : undefined; + } + if (blockNumber === undefined) { + return undefined; + } + return blockNumber; + } + private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData { return { header: BlockHeader.fromBuffer(blockStorage.header), @@ -1180,7 +1044,7 @@ export class BlockStore { this.getProvenBlockNumber(), this.getCheckpointedL2BlockNumber(), this.getFinalizedL2BlockNumber(), - this.getBlockData(blockNumber), + this.getBlockData({ number: blockNumber }), ]); let status: TxStatus; @@ -1284,7 +1148,7 @@ export class BlockStore { const previousBlock = await this.getPreviousCheckpointBlock(proposed.checkpointNumber); const blocks: L2Block[] = []; for (let i = 0; i < proposed.blockCount; i++) { - const block = await this.getBlock(BlockNumber(proposed.startBlock + i)); + const block = await this.getBlock({ number: BlockNumber(proposed.startBlock + i) }); if (!block) { throw new BlockNotFoundError(proposed.startBlock + i); } diff --git a/yarn-project/archiver/src/store/l2_tips_cache.ts b/yarn-project/archiver/src/store/l2_tips_cache.ts index bb2b26d522b6..21c4b08f47d0 100644 --- a/yarn-project/archiver/src/store/l2_tips_cache.ts +++ b/yarn-project/archiver/src/store/l2_tips_cache.ts @@ -2,8 +2,8 @@ import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import { type BlockData, + type BlockHash, type CheckpointId, - GENESIS_BLOCK_HEADER_HASH, GENESIS_CHECKPOINT_HEADER_HASH, type L2Tips, } from '@aztec/stdlib/block'; @@ -18,7 +18,16 @@ import type { BlockStore } from './block_store.js'; export class L2TipsCache { #tipsPromise: Promise | undefined; - constructor(private blockStore: BlockStore) {} + /** + * Asymmetric by design: the genesis block hash is dynamic — derived from the injected initial header, + * which depends on `genesisTimestamp` and any prefilled state. The genesis checkpoint hash is static — + * checkpoint 0 is fully synthetic (no real checkpoint header exists at 0), so it stays at the protocol + * constant `GENESIS_CHECKPOINT_HEADER_HASH`. + */ + constructor( + private blockStore: BlockStore, + private readonly initialBlockHash: BlockHash, + ) {} /** Returns the cached L2 tips. Loads from the block store on first call. */ public getL2Tips(): Promise { @@ -47,13 +56,15 @@ export class L2TipsCache { ]); const genesisBlockHeader = { - blockHash: GENESIS_BLOCK_HEADER_HASH, + blockHash: this.initialBlockHash, checkpointNumber: CheckpointNumber.ZERO, } as const; const beforeInitialBlockNumber = BlockNumber(INITIAL_L2_BLOCK_NUM - 1); const getBlockData = (blockNumber: BlockNumber) => - blockNumber > beforeInitialBlockNumber ? this.blockStore.getBlockData(blockNumber) : genesisBlockHeader; + blockNumber > beforeInitialBlockNumber + ? this.blockStore.getBlockData({ number: blockNumber }) + : genesisBlockHeader; const [latestBlockData, provenBlockData, proposedCheckpointBlockData, checkpointedBlockData, finalizedBlockData] = await Promise.all( diff --git a/yarn-project/archiver/src/test/mock_l2_block_source.ts b/yarn-project/archiver/src/test/mock_l2_block_source.ts index 484c804fe778..0250dd7159ad 100644 --- a/yarn-project/archiver/src/test/mock_l2_block_source.ts +++ b/yarn-project/archiver/src/test/mock_l2_block_source.ts @@ -1,6 +1,12 @@ import { GENESIS_ARCHIVE_ROOT } from '@aztec/constants'; import { DefaultL1ContractsConfig } from '@aztec/ethereum/config'; -import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { + BlockNumber, + CheckpointNumber, + EpochNumber, + IndexWithinCheckpoint, + SlotNumber, +} from '@aztec/foundation/branded-types'; import { Buffer32 } from '@aztec/foundation/buffer'; import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -9,8 +15,13 @@ import type { FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import { type BlockData, - BlockHash, - CheckpointedL2Block, + type BlockHash, + type BlockQuery, + type BlockTag, + type BlocksQuery, + Body, + GENESIS_BLOCK_HEADER_HASH, + GENESIS_CHECKPOINT_HEADER_HASH, L2Block, type L2BlockSource, type L2Tips, @@ -32,7 +43,8 @@ import { } from '@aztec/stdlib/epoch-helpers'; import { computeCheckpointOutHash } from '@aztec/stdlib/messaging'; import { CheckpointHeader } from '@aztec/stdlib/rollup'; -import { type BlockHeader, TxExecutionResult, TxHash, TxReceipt, TxStatus } from '@aztec/stdlib/tx'; +import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees'; +import { BlockHeader, TxExecutionResult, TxHash, TxReceipt, TxStatus } from '@aztec/stdlib/tx'; import type { UInt64 } from '@aztec/stdlib/types'; /** @@ -47,8 +59,66 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { private checkpointedBlockNumber: number = 0; private proposedCheckpointBlockNumber: number = 0; + private initialHeader: BlockHeader = BlockHeader.empty(); + private initialHeaderHash: BlockHash = GENESIS_BLOCK_HEADER_HASH; + private genesisArchiveRoot?: Fr; + private genesisBlock?: L2Block; + private log = createLogger('archiver:mock_l2_block_source'); + /** Returns the initial header used to synthesize block 0. */ + public getInitialHeader(): BlockHeader { + return this.initialHeader; + } + + /** + * Sets the initial header used to synthesize block 0. Tests that wire up a real + * world-state should call this with `worldState.getInitialHeader()` so the L2BlockStream + * agrees on the genesis hash on both sides. Precomputes and caches the header hash so + * `getGenesisBlockHash()` can return synchronously. + */ + public async setInitialHeader(header: BlockHeader): Promise { + this.initialHeader = header; + this.initialHeaderHash = await header.hash(); + this.genesisBlock = undefined; + } + + /** + * Returns the precomputed hash of the genesis block header. Defaults to the static + * {@link GENESIS_BLOCK_HEADER_HASH} unless {@link setInitialHeader} has been called with a + * custom header. + */ + public getGenesisBlockHash(): BlockHash { + return this.initialHeaderHash; + } + + /** + * Sets the post-genesis archive root used to synthesize block 0. Mirrors the real archiver, + * whose synthetic block 0 carries `new AppendOnlyTreeSnapshot(genesisArchiveRoot, 1)` rather + * than `AppendOnlyTreeSnapshot.empty()`. Tests wiring up a real world-state should set this so + * archive-based block lookups against the mock match production semantics. + */ + public setGenesisArchiveRoot(root: Fr): void { + this.genesisArchiveRoot = root; + this.genesisBlock = undefined; + } + + private getGenesisBlock(): L2Block { + if (this.genesisBlock) { + return this.genesisBlock; + } + const archive = this.genesisArchiveRoot + ? new AppendOnlyTreeSnapshot(this.genesisArchiveRoot, 1) + : AppendOnlyTreeSnapshot.empty(); + return (this.genesisBlock = new L2Block( + archive, + this.initialHeader, + Body.empty(), + CheckpointNumber.ZERO, + IndexWithinCheckpoint(0), + )); + } + /** Creates blocks grouped into single-block checkpoints. */ public async createBlocks(numBlocks: number) { await this.createCheckpoints(numBlocks, 1); @@ -171,8 +241,20 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { * Gets the number of the latest L2 block processed by the block source implementation. * @returns In this mock instance, returns the number of L2 blocks that we've mocked. */ - public getBlockNumber() { - return Promise.resolve(BlockNumber(this.l2Blocks.length)); + public getBlockNumber(): Promise; + public getBlockNumber(query: BlockQuery): Promise; + public async getBlockNumber(query?: BlockQuery): Promise { + if (!query) { + return BlockNumber(this.l2Blocks.length); + } + if ('number' in query) { + return query.number; + } + if ('tag' in query) { + return BlockNumber(this.resolveBlockTag(query.tag)); + } + const block = await this.getBlock(query); + return block ? block.header.globalVariables.blockNumber : undefined; } public getProvenBlockNumber() { @@ -191,62 +273,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { return Promise.resolve(BlockNumber(this.proposedCheckpointBlockNumber)); } - public getCheckpointedBlock(number: BlockNumber): Promise { - if (number > this.checkpointedBlockNumber) { - return Promise.resolve(undefined); - } - const block = this.l2Blocks[number - 1]; - if (!block) { - return Promise.resolve(undefined); - } - return Promise.resolve(this.toCheckpointedBlock(block)); - } - - public async getCheckpointedBlocks(from: BlockNumber, limit: number): Promise { - const result: CheckpointedL2Block[] = []; - for (let i = 0; i < limit; i++) { - const blockNum = from + i; - if (blockNum > this.checkpointedBlockNumber) { - break; - } - const block = await this.getCheckpointedBlock(BlockNumber(blockNum)); - if (block) { - result.push(block); - } - } - return result; - } - - /** - * Gets an l2 block. - * @param number - The block number to return (inclusive). - * @returns The requested L2 block. - */ - public getBlock(number: number): Promise { - const block = this.l2Blocks[number - 1]; - return Promise.resolve(block); - } - - /** - * Gets an L2 block (new format). - * @param number - The block number to return. - * @returns The requested L2 block. - */ - public getL2Block(number: BlockNumber): Promise { - const block = this.l2Blocks[number - 1]; - return Promise.resolve(block); - } - - /** - * Gets up to `limit` amount of L2 blocks starting from `from`. - * @param from - Number of the first block to return (inclusive). - * @param limit - The maximum number of blocks to return. - * @returns The requested mocked L2 blocks. - */ - public getBlocks(from: number, limit: number): Promise { - return Promise.resolve(this.l2Blocks.slice(from - 1, from - 1 + limit)); - } - public getCheckpoints(from: CheckpointNumber, limit: number) { const checkpoints = this.checkpointList.slice(from - 1, from - 1 + limit); return Promise.resolve( @@ -259,68 +285,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { return Promise.resolve(checkpoint); } - public async getCheckpointedBlockByHash(blockHash: BlockHash): Promise { - for (const block of this.l2Blocks) { - const hash = await block.hash(); - if (hash.equals(blockHash)) { - return this.toCheckpointedBlock(block); - } - } - return undefined; - } - - public getCheckpointedBlockByArchive(archive: Fr): Promise { - const block = this.l2Blocks.find(b => b.archive.root.equals(archive)); - if (!block) { - return Promise.resolve(undefined); - } - return Promise.resolve(this.toCheckpointedBlock(block)); - } - - public async getL2BlockByHash(blockHash: BlockHash): Promise { - for (const block of this.l2Blocks) { - const hash = await block.hash(); - if (hash.equals(blockHash)) { - return block; - } - } - return undefined; - } - - public getL2BlockByArchive(archive: Fr): Promise { - const block = this.l2Blocks.find(b => b.archive.root.equals(archive)); - return Promise.resolve(block); - } - - public async getBlockHeaderByHash(blockHash: BlockHash): Promise { - for (const block of this.l2Blocks) { - const hash = await block.hash(); - if (hash.equals(blockHash)) { - return block.header; - } - } - return undefined; - } - - public getBlockHeaderByArchive(archive: Fr): Promise { - const block = this.l2Blocks.find(b => b.archive.root.equals(archive)); - return Promise.resolve(block?.header); - } - - public async getBlockData(number: BlockNumber): Promise { - const block = this.l2Blocks[number - 1]; - if (!block) { - return undefined; - } - return { - header: block.header, - archive: block.archive, - blockHash: await block.hash(), - checkpointNumber: block.checkpointNumber, - indexWithinCheckpoint: block.indexWithinCheckpoint, - }; - } - public getCheckpointData(_n: CheckpointNumber): Promise { return Promise.resolve(undefined); } @@ -333,32 +297,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { return Promise.resolve(undefined); } - public async getBlockDataWithCheckpointContext(number: BlockNumber) { - const data = await this.getBlockData(number); - if (!data) { - return undefined; - } - return { data, checkpoint: undefined, l1: undefined, attestations: [] }; - } - - public async getBlockDataByArchive(archive: Fr): Promise { - const block = this.l2Blocks.find(b => b.archive.root.equals(archive)); - if (!block) { - return undefined; - } - return { - header: block.header, - archive: block.archive, - blockHash: await block.hash(), - checkpointNumber: block.checkpointNumber, - indexWithinCheckpoint: block.indexWithinCheckpoint, - }; - } - - getBlockHeader(number: number | 'latest'): Promise { - return Promise.resolve(this.l2Blocks.at(typeof number === 'number' ? number - 1 : -1)?.header); - } - getCheckpointsForEpoch(epochNumber: EpochNumber): Promise { return Promise.resolve(this.getCheckpointsInEpoch(epochNumber)); } @@ -384,23 +322,11 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { ); } - getCheckpointedBlocksForEpoch(epochNumber: EpochNumber): Promise { - const checkpoints = this.getCheckpointsInEpoch(epochNumber); - return Promise.resolve( - checkpoints.flatMap(checkpoint => checkpoint.blocks.map(block => this.toCheckpointedBlock(block))), - ); - } - getBlocksForSlot(slotNumber: SlotNumber): Promise { const blocks = this.l2Blocks.filter(b => b.header.globalVariables.slotNumber === slotNumber); return Promise.resolve(blocks); } - async getCheckpointedBlockHeadersForEpoch(epochNumber: EpochNumber): Promise { - const checkpointedBlocks = await this.getCheckpointedBlocksForEpoch(epochNumber); - return checkpointedBlocks.map(b => b.block.header); - } - /** * Gets a tx effect. * @param txHash - The hash of the tx corresponding to the tx effect. @@ -463,34 +389,48 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { const checkpointedBlock = this.l2Blocks[checkpointed - 1]; const proposedCheckpointBlock = this.l2Blocks[proposedCheckpoint - 1]; + // For genesis tips (block number 0) report the dynamic initial header hash so consumers + // running L2BlockStream against this mock agree at block 0 with their local tip store. + const genesisHash = (await this.initialHeader.hash()).toString(); + const tipHash = async (block: L2Block | undefined, number: number): Promise => { + if (block) { + return (await block.hash()).toString(); + } + return number === 0 ? genesisHash : ''; + }; + const latestBlockId = { number: BlockNumber(latest), - hash: (await latestBlock?.hash())?.toString(), + hash: await tipHash(latestBlock, latest), }; const provenBlockId = { number: BlockNumber(proven), - hash: (await provenBlock?.hash())?.toString(), + hash: await tipHash(provenBlock, proven), }; const finalizedBlockId = { number: BlockNumber(finalized), - hash: (await finalizedBlock?.hash())?.toString(), + hash: await tipHash(finalizedBlock, finalized), }; const checkpointedBlockId = { number: BlockNumber(checkpointed), - hash: (await checkpointedBlock?.hash())?.toString(), + hash: await tipHash(checkpointedBlock, checkpointed), }; const proposedCheckpointBlockId = { number: BlockNumber(proposedCheckpoint), - hash: (await proposedCheckpointBlock?.hash())?.toString(), + hash: await tipHash(proposedCheckpointBlock, proposedCheckpoint), }; - const makeTipId = (blockId: typeof latestBlockId) => ({ - block: blockId, - checkpoint: { - number: this.findCheckpointNumberForBlock(blockId.number) ?? CheckpointNumber(0), - hash: blockId.hash, - }, - }); + const makeTipId = (blockId: typeof latestBlockId) => { + const checkpointNumber = this.findCheckpointNumberForBlock(blockId.number) ?? CheckpointNumber(0); + // Match production semantics: checkpoint 0 is fully synthetic (no real checkpoint header + // exists at 0), so its hash stays at the protocol constant `GENESIS_CHECKPOINT_HEADER_HASH` + // even though the block-0 hash is dynamic. See L2TipsCache for the production path. + const hash = checkpointNumber === 0 ? GENESIS_CHECKPOINT_HEADER_HASH.toString() : blockId.hash; + return { + block: blockId, + checkpoint: { number: checkpointNumber, hash }, + }; + }; return { proposed: latestBlockId, @@ -518,7 +458,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { } getGenesisValues(): Promise<{ genesisArchiveRoot: Fr }> { - return Promise.resolve({ genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT) }); + return Promise.resolve({ genesisArchiveRoot: this.genesisArchiveRoot ?? new Fr(GENESIS_ARCHIVE_ROOT) }); } getL1Timestamp(): Promise { @@ -571,6 +511,95 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { return Promise.resolve(); } + async getBlock(query: BlockQuery): Promise { + if ('number' in query) { + if (query.number === 0) { + return this.getGenesisBlock(); + } + return this.l2Blocks[query.number - 1]; + } + if ('hash' in query) { + const genesis = this.getGenesisBlock(); + if ((await genesis.hash()).equals(query.hash)) { + return genesis; + } + for (const b of this.l2Blocks) { + const hash = await b.hash(); + if (hash.equals(query.hash)) { + return b; + } + } + return undefined; + } + if ('archive' in query) { + const genesis = this.getGenesisBlock(); + if (genesis.archive.root.equals(query.archive)) { + return genesis; + } + return this.l2Blocks.find(b => b.archive.root.equals(query.archive)); + } + const number = this.resolveBlockTag(query.tag); + if (number === 0) { + return this.getGenesisBlock(); + } + return this.l2Blocks[number - 1]; + } + + private resolveBlockTag(tag: BlockTag): number { + switch (tag) { + case 'latest': + case 'proposed': + return this.l2Blocks.length; + case 'checkpointed': + return this.checkpointedBlockNumber; + case 'proven': + return this.provenBlockNumber; + case 'finalized': + return this.finalizedBlockNumber; + } + } + + getBlocks(query: BlocksQuery): Promise { + let blocks: L2Block[]; + if ('from' in query) { + blocks = this.l2Blocks.slice(query.from - 1, query.from - 1 + query.limit); + } else { + const epochCheckpoints = this.getCheckpointsInEpoch(query.epoch); + blocks = epochCheckpoints.flatMap(c => c.blocks); + } + if (query.onlyCheckpointed) { + blocks = blocks.filter(b => b.header.globalVariables.blockNumber <= this.checkpointedBlockNumber); + } + return Promise.resolve(blocks); + } + + async getBlockData(query: BlockQuery): Promise { + const block = await this.getBlock(query); + if (!block) { + return undefined; + } + return { + header: block.header, + archive: block.archive, + blockHash: await block.hash(), + checkpointNumber: block.checkpointNumber, + indexWithinCheckpoint: block.indexWithinCheckpoint, + }; + } + + async getBlocksData(query: BlocksQuery): Promise { + const blocks = await this.getBlocks(query); + return Promise.all( + blocks.map(async block => ({ + header: block.header, + archive: block.archive, + blockHash: await block.hash(), + checkpointNumber: block.checkpointNumber, + indexWithinCheckpoint: block.indexWithinCheckpoint, + })), + ); + } + isPendingChainInvalid(): Promise { return Promise.resolve(false); } @@ -599,22 +628,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource { return new L1PublishedData(BigInt(checkpoint.number), BigInt(checkpoint.number), Buffer32.random().toString()); } - /** Creates a CheckpointedL2Block from a block using stored checkpoint info. */ - private toCheckpointedBlock(block: L2Block): CheckpointedL2Block { - const checkpoint = this.checkpointList.find(c => c.blocks.some(b => b.number === block.number)); - const checkpointNumber = checkpoint?.number ?? block.checkpointNumber; - return new CheckpointedL2Block( - checkpointNumber, - block, - new L1PublishedData( - BigInt(block.number), - BigInt(block.number), - `0x${block.number.toString(16).padStart(64, '0')}`, - ), - [], - ); - } - /** Finds the checkpoint number for a block, or undefined if the block is not in any checkpoint. */ private findCheckpointNumberForBlock(blockNumber: BlockNumber): CheckpointNumber | undefined { const checkpoint = this.checkpointList.find(c => c.blocks.some(b => b.number === blockNumber)); diff --git a/yarn-project/archiver/src/test/noop_l1_archiver.ts b/yarn-project/archiver/src/test/noop_l1_archiver.ts index 95ad0831f878..e4601b30fdfe 100644 --- a/yarn-project/archiver/src/test/noop_l1_archiver.ts +++ b/yarn-project/archiver/src/test/noop_l1_archiver.ts @@ -6,8 +6,9 @@ import { Buffer32 } from '@aztec/foundation/buffer'; import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; import type { FunctionsOf } from '@aztec/foundation/types'; -import type { ArchiverEmitter } from '@aztec/stdlib/block'; +import type { ArchiverEmitter, BlockHash } from '@aztec/stdlib/block'; import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; +import type { BlockHeader } from '@aztec/stdlib/tx'; import { type TelemetryClient, type Tracer, getTelemetryClient } from '@aztec/telemetry-client'; import { mock } from 'jest-mock-extended'; @@ -17,6 +18,7 @@ import { Archiver } from '../archiver.js'; import { ArchiverInstrumentation } from '../modules/instrumentation.js'; import type { ArchiverL1Synchronizer } from '../modules/l1_synchronizer.js'; import type { ArchiverDataStores } from '../store/data_stores.js'; +import { L2TipsCache } from '../store/l2_tips_cache.js'; /** Noop L1 synchronizer for testing without L1 connectivity. */ class NoopL1Synchronizer implements FunctionsOf { @@ -51,6 +53,9 @@ export class NoopL1Archiver extends Archiver { dataStores: ArchiverDataStores, l1Constants: L1RollupConstants & { genesisArchiveRoot: Fr }, instrumentation: ArchiverInstrumentation, + initialHeader: BlockHeader, + initialBlockHash: BlockHash, + l2TipsCache: L2TipsCache, ) { // Create mocks for L1 clients const publicClient = mock(); @@ -90,6 +95,9 @@ export class NoopL1Archiver extends Archiver { { ...l1Constants, l1StartBlockHash: Buffer32.random() }, synchronizer as ArchiverL1Synchronizer, events, + initialHeader, + initialBlockHash, + l2TipsCache, ); } @@ -111,7 +119,14 @@ export async function createNoopL1Archiver( dataStores: ArchiverDataStores, l1Constants: L1RollupConstants & { genesisArchiveRoot: Fr }, telemetry: TelemetryClient = getTelemetryClient(), + initialHeader: BlockHeader, ): Promise { const instrumentation = await ArchiverInstrumentation.new(telemetry, () => dataStores.db.estimateSize()); - return new NoopL1Archiver(dataStores, l1Constants, instrumentation); + // Mirror the production factory: precompute the dynamic genesis block hash from the injected + // initial header so `L2TipsCache` reports the correct tip hash at block 0. Without this, the + // cache falls back to the static `GENESIS_BLOCK_HEADER_HASH`, which only matches deployments + // with default empty genesis. + const initialBlockHash = await initialHeader.hash(); + const l2TipsCache = new L2TipsCache(dataStores.blocks, initialBlockHash); + return new NoopL1Archiver(dataStores, l1Constants, instrumentation, initialHeader, initialBlockHash, l2TipsCache); } diff --git a/yarn-project/aztec-node/src/aztec-node/block_response_helpers.ts b/yarn-project/aztec-node/src/aztec-node/block_response_helpers.ts index 518027789433..672c031329a0 100644 --- a/yarn-project/aztec-node/src/aztec-node/block_response_helpers.ts +++ b/yarn-project/aztec-node/src/aztec-node/block_response_helpers.ts @@ -1,12 +1,6 @@ import { BlockNumber } from '@aztec/foundation/branded-types'; -import { - type BlockData, - type BlockDataWithCheckpointContext, - type CheckpointedL2Block, - type CommitteeAttestation, - L2Block, -} from '@aztec/stdlib/block'; -import type { CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; +import { type BlockData, type CommitteeAttestation, L2Block } from '@aztec/stdlib/block'; +import type { CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; import { type BlockIncludeOptions, type BlockResponse, @@ -19,7 +13,7 @@ import { export async function blockResponseFromL2Block( block: L2Block, options: BlockIncludeOptions, - context?: { l1?: BlockDataWithCheckpointContext['l1']; attestations?: CommitteeAttestation[] }, + context?: { l1?: L1PublishedData; attestations?: CommitteeAttestation[] }, ): Promise { const response: BlockResponse = { header: block.header, @@ -44,9 +38,8 @@ export async function blockResponseFromL2Block( /** Projects metadata-only {@link BlockData} into a {@link BlockResponse}. */ export function blockResponseFromBlockData( data: BlockData, - blockNumber: BlockNumber, options: BlockIncludeOptions, - context?: { l1?: BlockDataWithCheckpointContext['l1']; attestations?: CommitteeAttestation[] }, + context?: { l1?: L1PublishedData; attestations?: CommitteeAttestation[] }, ): BlockResponse { const response: BlockResponse = { header: data.header, @@ -54,7 +47,7 @@ export function blockResponseFromBlockData( hash: data.blockHash, checkpointNumber: data.checkpointNumber, indexWithinCheckpoint: data.indexWithinCheckpoint, - number: blockNumber, + number: data.header.getBlockNumber(), }; if (options.includeL1PublishInfo) { (response as BlockResponse).l1 = l1PublishInfoFromL1PublishedData(context?.l1); @@ -65,14 +58,6 @@ export function blockResponseFromBlockData( return response; } -/** Projects a {@link CheckpointedL2Block} into a {@link BlockResponse}. */ -export function blockResponseFromCheckpointedL2Block( - cp: CheckpointedL2Block, - options: BlockIncludeOptions, -): Promise { - return blockResponseFromL2Block(cp.block, options, { l1: cp.l1, attestations: cp.attestations }); -} - /** Projects a {@link PublishedCheckpoint} into a {@link CheckpointResponse}. */ export async function checkpointResponseFromPublishedCheckpoint( pc: PublishedCheckpoint, diff --git a/yarn-project/aztec-node/src/aztec-node/server.test.ts b/yarn-project/aztec-node/src/aztec-node/server.test.ts index 9df49ffbe54f..21b5e558fa91 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.test.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.test.ts @@ -1,7 +1,13 @@ import { TestCircuitVerifier } from '@aztec/bb-prover'; import { EpochCache } from '@aztec/epoch-cache'; import type { RollupContract } from '@aztec/ethereum/contracts'; -import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { + BlockNumber, + CheckpointNumber, + EpochNumber, + IndexWithinCheckpoint, + SlotNumber, +} from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; import { BadRequestError } from '@aztec/foundation/json-rpc'; @@ -16,8 +22,14 @@ import { computeFeePayerBalanceLeafSlot } from '@aztec/protocol-contracts/fee-ju import type { GlobalVariableBuilder, Sequencer, SequencerClient } from '@aztec/sequencer-client'; import type { SlasherClientInterface } from '@aztec/slasher'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { BlockHash, type BlockParameter, CheckpointedL2Block, L2Block, type L2BlockSource } from '@aztec/stdlib/block'; -import { L1PublishedData } from '@aztec/stdlib/checkpoint'; +import { + type BlockData, + BlockHash, + type BlockParameter, + type BlockQuery, + L2Block, + type L2BlockSource, +} from '@aztec/stdlib/block'; import type { ContractDataSource } from '@aztec/stdlib/contract'; import { EmptyL1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import { GasFees } from '@aztec/stdlib/gas'; @@ -50,7 +62,6 @@ import { dirname, join, resolve } from 'path'; import { fileURLToPath } from 'url'; import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; -import { blockResponseFromL2Block } from './block_response_helpers.js'; import { type AztecNodeConfig, getConfigEnvVars } from './config.js'; import { AztecNodeService } from './server.js'; @@ -149,8 +160,17 @@ describe('aztec node', () => { worldState.syncImmediate.mockImplementation(() => Promise.resolve(lastBlockNumber)); l2BlockSource = mock(); - l2BlockSource.getBlockNumber.mockImplementation(() => Promise.resolve(lastBlockNumber)); + l2BlockSource.getBlockNumber.mockImplementation(((query?: BlockQuery) => { + if (!query) { + return Promise.resolve(lastBlockNumber); + } + if ('number' in query) { + return Promise.resolve(query.number); + } + return Promise.resolve(undefined); + }) as L2BlockSource['getBlockNumber']); l2BlockSource.getL1Constants.mockResolvedValue(EmptyL1RollupConstants); + l2BlockSource.getGenesisBlockHash.mockReturnValue(BlockHash.random()); const l2LogsSource = mock(); @@ -356,68 +376,78 @@ describe('aztec node', () => { }); header2 = BlockHeader.empty({ globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(2) }) }); - merkleTreeOps.getInitialHeader.mockReturnValue(initialHeader); + // Archiver returns the genesis block data for block 0 queries (including {tag:'proposed'} at genesis). + l2BlockSource.getBlockData.mockResolvedValue({ header: initialHeader } as any); l2BlockSource.getBlockNumber.mockResolvedValue(BlockNumber(2)); }); it('returns requested block number', async () => { - l2BlockSource.getBlockHeader.mockResolvedValue(header1); + l2BlockSource.getBlockData.mockResolvedValue({ header: header1 } as any); expect(await node.getBlockHeader(BlockNumber(1))).toEqual(header1); }); it('returns latest', async () => { - l2BlockSource.getBlockHeader.mockResolvedValue(header2); + l2BlockSource.getBlockData.mockResolvedValue({ header: header2 } as any); expect(await node.getBlockHeader('latest')).toEqual(header2); }); it('returns initial header on zero', async () => { + // Archiver returns synthetic genesis block data when queried with block 0. expect(await node.getBlockHeader(BlockNumber.ZERO)).toEqual(initialHeader); }); it('returns initial header if no blocks mined', async () => { + // When no blocks have been mined, {tag:'proposed'} resolves to the genesis block. l2BlockSource.getBlockNumber.mockResolvedValue(BlockNumber.ZERO); expect(await node.getBlockHeader('latest')).toEqual(initialHeader); }); it('returns undefined for non-existent block', async () => { - l2BlockSource.getBlockHeader.mockResolvedValue(undefined); + l2BlockSource.getBlockData.mockResolvedValue(undefined); expect(await node.getBlockHeader(BlockNumber(3))).toEqual(undefined); }); }); describe('getBlock', () => { - let block1: L2Block; - let block2: L2Block; + let blockData1: BlockData; + let blockData2: BlockData; beforeEach(() => { - block1 = L2Block.empty( - BlockHeader.empty({ globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(1) }) }), - ); - block2 = L2Block.empty( - BlockHeader.empty({ globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(2) }) }), - ); + blockData1 = { + header: BlockHeader.empty({ globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(1) }) }), + archive: L2Block.empty().archive, + blockHash: BlockHash.random(), + checkpointNumber: CheckpointNumber(1), + indexWithinCheckpoint: IndexWithinCheckpoint(0), + }; + blockData2 = { + header: BlockHeader.empty({ globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(2) }) }), + archive: L2Block.empty().archive, + blockHash: BlockHash.random(), + checkpointNumber: CheckpointNumber(1), + indexWithinCheckpoint: IndexWithinCheckpoint(1), + }; l2BlockSource.getBlockNumber.mockResolvedValue(BlockNumber(2)); }); - it('returns requested block number with transactions', async () => { - l2BlockSource.getL2Block.mockResolvedValue(block1); - const expected = await blockResponseFromL2Block(block1, { includeTransactions: true }); - expect(await node.getBlock(BlockNumber(1), { includeTransactions: true })).toEqual(expected); - expect(l2BlockSource.getL2Block).toHaveBeenCalledWith(BlockNumber(1)); + it('returns requested block number', async () => { + l2BlockSource.getBlockData.mockResolvedValue(blockData1); + const result = await node.getBlock(BlockNumber(1)); + expect(result?.header).toEqual(blockData1.header); + expect(result?.number).toEqual(BlockNumber(1)); }); - it('returns latest block with transactions', async () => { - l2BlockSource.getL2Block.mockResolvedValue(block2); - const expected = await blockResponseFromL2Block(block2, { includeTransactions: true }); - expect(await node.getBlock('latest', { includeTransactions: true })).toEqual(expected); - expect(l2BlockSource.getL2Block).toHaveBeenCalledWith(2); + it('returns latest block', async () => { + l2BlockSource.getBlockData.mockResolvedValue(blockData2); + const result = await node.getBlock('latest'); + expect(result?.header).toEqual(blockData2.header); + expect(result?.number).toEqual(BlockNumber(2)); }); it('returns undefined for non-existent block', async () => { - l2BlockSource.getL2Block.mockResolvedValue(undefined); - expect(await node.getBlock(BlockNumber(3), { includeTransactions: true })).toEqual(undefined); - expect(l2BlockSource.getL2Block).toHaveBeenCalledWith(BlockNumber(3)); + l2BlockSource.getBlockData.mockResolvedValue(undefined); + expect(await node.getBlock(BlockNumber(3))).toEqual(undefined); }); }); @@ -576,11 +606,24 @@ describe('aztec node', () => { let snapshotMerkleTreeOps: MockProxy; let initialHeader: BlockHeader; - beforeEach(() => { + beforeEach(async () => { lastBlockNumber = BlockNumber(5); initialHeader = BlockHeader.empty({ globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber.ZERO }), }); + // Archiver resolves the initial block hash to block number 0 directly. + const initialBlockHash = await initialHeader.hash(); + l2BlockSource.getBlockNumber.mockImplementation(((query?: BlockQuery) => + Promise.resolve( + !query + ? lastBlockNumber + : 'number' in query + ? query.number + : 'hash' in query && query.hash.equals(initialBlockHash) + ? BlockNumber.ZERO + : undefined, + )) as L2BlockSource['getBlockNumber']); + // #getInitialHeaderHash still sources from worldStateSynchronizer (used in error messages). merkleTreeOps.getInitialHeader.mockReturnValue(initialHeader); snapshotMerkleTreeOps = mock(); worldState.getSnapshot.mockReturnValue(snapshotMerkleTreeOps); @@ -604,20 +647,20 @@ describe('aztec node', () => { it('throws for a block hash whose block number is beyond sync range', async () => { const blockHash = BlockHash.random(); - const header = BlockHeader.empty({ - globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(10) }), - }); - l2BlockSource.getBlockHeaderByHash.mockResolvedValue(header); + l2BlockSource.getBlockNumber.mockImplementation(((query?: BlockQuery) => + Promise.resolve( + query && 'hash' in query ? BlockNumber(10) : lastBlockNumber, + )) as L2BlockSource['getBlockNumber']); await expect(node.getWorldState(blockHash)).rejects.toThrow(/not yet synced/); }); it('resolves block hash to block number via archiver and returns snapshot', async () => { const blockHash = BlockHash.random(); - const header = BlockHeader.empty({ - globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(3) }), - }); - l2BlockSource.getBlockHeaderByHash.mockResolvedValue(header); + l2BlockSource.getBlockNumber.mockImplementation(((query?: BlockQuery) => + Promise.resolve( + query && 'hash' in query ? BlockNumber(3) : lastBlockNumber, + )) as L2BlockSource['getBlockNumber']); snapshotMerkleTreeOps.getLeafValue.mockResolvedValue(blockHash); const result = await node.getWorldState(blockHash); @@ -627,7 +670,6 @@ describe('aztec node', () => { it('throws when block hash is not found in archiver', async () => { const blockHash = BlockHash.random(); - l2BlockSource.getBlockHeaderByHash.mockResolvedValue(undefined); await expect(node.getWorldState(blockHash)).rejects.toThrow(/not found when querying world state/); }); @@ -635,10 +677,10 @@ describe('aztec node', () => { it('throws when world-state block hash does not match requested hash (reorg)', async () => { const blockHash = BlockHash.random(); const differentHash = BlockHash.random(); - const header = BlockHeader.empty({ - globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(3) }), - }); - l2BlockSource.getBlockHeaderByHash.mockResolvedValue(header); + l2BlockSource.getBlockNumber.mockImplementation(((query?: BlockQuery) => + Promise.resolve( + query && 'hash' in query ? BlockNumber(3) : lastBlockNumber, + )) as L2BlockSource['getBlockNumber']); // World state returns a different hash for the same block number snapshotMerkleTreeOps.getLeafValue.mockResolvedValue(differentHash); @@ -660,23 +702,16 @@ describe('aztec node', () => { }); describe('getBlockHashMembershipWitness', () => { - let initialHeader: BlockHeader; - - beforeEach(() => { - lastBlockNumber = BlockNumber(5); - initialHeader = BlockHeader.empty({ + it('returns undefined when reference block is the initial block hash', async () => { + // Block 0 has an empty archive — no block hashes exist in it yet. + // getBlockHashMembershipWitness short-circuits at block 0 and returns undefined. + const initialHeader = BlockHeader.empty({ globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber.ZERO }), }); - merkleTreeOps.getInitialHeader.mockReturnValue(initialHeader); - }); - - it('returns undefined when reference block is the initial block hash', async () => { - // The initial block (block 0) has an empty archive — no block hashes exist in it. - // getBlockHashMembershipWitness computes referenceBlockNumber - 1, which would be 0 - 1 = -1. - // This should return undefined (empty archive has no witnesses) rather than crashing. const initialBlockHash = await initialHeader.hash(); - const someBlockHash = BlockHash.random(); + l2BlockSource.getBlockNumber.mockResolvedValue(BlockNumber.ZERO); + const someBlockHash = BlockHash.random(); const result = await node.getBlockHashMembershipWitness(initialBlockHash, someBlockHash); expect(result).toBeUndefined(); }); @@ -1042,7 +1077,7 @@ describe('aztec node', () => { }); describe('getL2ToL1Messages', () => { - const makeCheckpointedBlock = (slotNumber: number, l2ToL1MsgsByTx: Fr[][]): CheckpointedL2Block => { + const makeBlock = (slotNumber: number, l2ToL1MsgsByTx: Fr[][]): L2Block => { const block = L2Block.empty( BlockHeader.empty({ globalVariables: GlobalVariables.empty({ slotNumber: SlotNumber(slotNumber) }), @@ -1050,7 +1085,7 @@ describe('aztec node', () => { ); // Override the body's txEffects with our custom l2ToL1Msgs unfreeze(block.body).txEffects = l2ToL1MsgsByTx.map(msgs => ({ l2ToL1Msgs: msgs }) as TxEffect); - return new CheckpointedL2Block(CheckpointNumber(0), block, new L1PublishedData(0n, 0n, '0x0'), []); + return block; }; it('groups blocks by slot number into checkpoints', async () => { @@ -1059,13 +1094,9 @@ describe('aztec node', () => { const msg3 = Fr.random(); // Two blocks in slot 1, one block in slot 2 - const blocks = [ - makeCheckpointedBlock(1, [[msg1]]), - makeCheckpointedBlock(1, [[msg2]]), - makeCheckpointedBlock(2, [[msg3]]), - ]; + const blocks = [makeBlock(1, [[msg1]]), makeBlock(1, [[msg2]]), makeBlock(2, [[msg3]])]; - l2BlockSource.getCheckpointedBlocksForEpoch.mockResolvedValue(blocks); + l2BlockSource.getBlocks.mockResolvedValue(blocks); const result = await node.getL2ToL1Messages(EpochNumber(0)); @@ -1079,9 +1110,9 @@ describe('aztec node', () => { const msg2 = Fr.random(); // Block in slot 0, block in slot 1 - const blocks = [makeCheckpointedBlock(0, [[msg1]]), makeCheckpointedBlock(1, [[msg2]])]; + const blocks = [makeBlock(0, [[msg1]]), makeBlock(1, [[msg2]])]; - l2BlockSource.getCheckpointedBlocksForEpoch.mockResolvedValue(blocks); + l2BlockSource.getBlocks.mockResolvedValue(blocks); const result = await node.getL2ToL1Messages(EpochNumber(0)); diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index a48a5dcd9bbf..975f9bfcce78 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -52,11 +52,14 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { BlockHash, type BlockParameter, + BlockTag, + type CommitteeAttestation, type DataInBlock, - L2Block, type L2BlockSource, + type NormalizedBlockParameter, inspectBlockParameter, } from '@aztec/stdlib/block'; +import { L1PublishedData } from '@aztec/stdlib/checkpoint'; import type { ContractClassPublic, ContractDataSource, @@ -129,7 +132,7 @@ import { createValidatorClient, } from '@aztec/validator-client'; import type { SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types'; -import { createWorldStateSynchronizer } from '@aztec/world-state'; +import { createWorldState, createWorldStateSynchronizer } from '@aztec/world-state'; import { createPublicClient } from 'viem'; @@ -149,8 +152,6 @@ import { NodeMetrics } from './node_metrics.js'; */ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDebug, Traceable { private metrics: NodeMetrics; - private initialHeaderHashPromise: Promise | undefined = undefined; - // Prevent two snapshot operations to happen simultaneously private isUploadingSnapshot = false; @@ -219,15 +220,24 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb } public async getBlockHeader(number: BlockNumber | 'latest'): Promise { - const resolvedNumber = number === 'latest' ? await this.blockSource.getBlockNumber() : number; - if (resolvedNumber === BlockNumber.ZERO) { - return this.worldStateSynchronizer.getCommitted().getInitialHeader(); - } - return this.blockSource.getBlockHeader(resolvedNumber); - } - - public async getCheckpointedBlocks(from: BlockNumber, limit: number) { - return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? []; + if (number === 'latest') { + return (await this.blockSource.getBlockData({ tag: 'proposed' }))?.header; + } + return (await this.blockSource.getBlockData({ number }))?.header; + } + + public async getCheckpointedBlocks(from: BlockNumber, limit: number): Promise { + const blocks = await this.blockSource.getBlocks({ from, limit, onlyCheckpointed: true }); + const ctxByCheckpoint = await this.#getCheckpointContextsForBlocks(blocks); + return Promise.all( + blocks.map(block => + blockResponseFromL2Block( + block, + { includeTransactions: true, includeL1PublishInfo: true, includeAttestations: true }, + ctxByCheckpoint.get(block.checkpointNumber), + ), + ), + ); } public getCheckpointsDataForEpoch(epoch: EpochNumber) { @@ -266,20 +276,23 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb return value === 'proposed' || value === 'checkpointed' || value === 'proven' || value === 'finalized'; } - private async resolveBlockParameter( - param: BlockParameter, - ): Promise<{ number?: BlockNumber; hash?: BlockHash; archive?: Fr }> { + /** + * Normalizes a {@link BlockParameter} (which may be a bare value) into a + * {@link NormalizedBlockParameter} object form. Performs no chain-tip resolution — tag + * lookups are deferred to the underlying block source. + */ + private normalizeBlockParameter(param: BlockParameter): NormalizedBlockParameter { if (BlockHash.isBlockHash(param)) { return { hash: param }; } if (typeof param === 'number') { return { number: param as BlockNumber }; } - if (param === 'latest') { - return { number: await this.blockSource.getBlockNumber() }; - } - if (this.isChainTip(param)) { - return { number: await this.getBlockNumber(param) }; + if (typeof param === 'string') { + if (this.isBlockTag(param)) { + return { tag: param === 'latest' ? 'proposed' : param }; + } + throw new BadRequestError(`Invalid BlockParameter tag: ${param}`); } if (typeof param === 'object' && param !== null) { if ('number' in param) { @@ -291,10 +304,20 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb if ('archive' in param) { return { archive: param.archive }; } + if ('tag' in param) { + if (this.isBlockTag(param.tag)) { + return { tag: param.tag }; + } + throw new BadRequestError(`Invalid BlockParameter tag: ${param.tag}`); + } } throw new BadRequestError(`Invalid BlockParameter: ${JSON.stringify(param)}`); } + private isBlockTag(value: string): value is BlockTag { + return BlockTag.includes(value as BlockTag); + } + private async resolveCheckpointParameter( param: CheckpointParameter, ): Promise<{ number?: CheckpointNumber; slot?: SlotNumber }> { @@ -318,78 +341,39 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb throw new BadRequestError(`Invalid CheckpointParameter: ${JSON.stringify(param)}`); } + /** Fetches checkpoint-level L1 and attestation data for use as block response context. */ + async #getCheckpointContext( + checkpointNumber: CheckpointNumber, + ): Promise<{ l1?: L1PublishedData; attestations?: CommitteeAttestation[] } | undefined> { + const checkpoint = await this.blockSource.getCheckpointData(checkpointNumber); + if (!checkpoint) { + return undefined; + } + return { l1: checkpoint.l1, attestations: checkpoint.attestations }; + } + public async getBlock( param: BlockParameter, options: Opts = {} as Opts, ): Promise | undefined> { - const resolved = await this.resolveBlockParameter(param); + const query = this.normalizeBlockParameter(param); const wantTxs = !!options.includeTransactions; const wantContext = !!options.includeL1PublishInfo || !!options.includeAttestations; - if (resolved.hash !== undefined) { - const initial = await this.#getInitialHeaderHash(); - if (resolved.hash.equals(initial)) { - return (await this.buildGenesisBlockResponse(options)) as BlockResponse; - } - if (wantTxs) { - const block = await this.blockSource.getL2BlockByHash(resolved.hash); - if (!block) { - return undefined; - } - const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(block.number) : undefined; - return (await blockResponseFromL2Block(block, options, ctx)) as BlockResponse; - } - const data = await this.blockSource.getBlockHeaderByHash(resolved.hash); - if (!data) { - return undefined; - } - const blockNumber = data.globalVariables.blockNumber; - const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(blockNumber) : undefined; - if (ctx) { - return blockResponseFromBlockData(ctx.data, blockNumber, options, ctx) as BlockResponse; - } - const blockData = await this.blockSource.getBlockData(blockNumber); - if (!blockData) { - return undefined; - } - return blockResponseFromBlockData(blockData, blockNumber, options) as BlockResponse; - } - - if (resolved.archive !== undefined) { - if (wantTxs) { - const block = await this.blockSource.getL2BlockByArchive(resolved.archive); - if (!block) { - return undefined; - } - const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(block.number) : undefined; - return (await blockResponseFromL2Block(block, options, ctx)) as BlockResponse; - } - const data = await this.blockSource.getBlockDataByArchive(resolved.archive); - if (!data) { - return undefined; - } - const blockNumber = data.header.globalVariables.blockNumber; - const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(blockNumber) : undefined; - return blockResponseFromBlockData(data, blockNumber, options, ctx) as BlockResponse; - } - - const blockNumber = resolved.number!; - if (blockNumber === BlockNumber.ZERO) { - return (await this.buildGenesisBlockResponse(options)) as BlockResponse; - } if (wantTxs) { - const block = await this.blockSource.getL2Block(blockNumber); + const block = await this.blockSource.getBlock(query); if (!block) { return undefined; } - const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(blockNumber) : undefined; + const ctx = wantContext ? await this.#getCheckpointContext(block.checkpointNumber) : undefined; return (await blockResponseFromL2Block(block, options, ctx)) as BlockResponse; } - const ctx = await this.blockSource.getBlockDataWithCheckpointContext(blockNumber); - if (!ctx) { + const data = await this.blockSource.getBlockData(query); + if (!data) { return undefined; } - return blockResponseFromBlockData(ctx.data, blockNumber, options, ctx) as BlockResponse; + const ctx = wantContext ? await this.#getCheckpointContext(data.checkpointNumber) : undefined; + return blockResponseFromBlockData(data, options, ctx) as BlockResponse; } public async getBlocks( @@ -400,24 +384,28 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb const wantTxs = !!options.includeTransactions; const wantContext = !!options.includeL1PublishInfo || !!options.includeAttestations; if (wantTxs) { - const blocks = await this.blockSource.getBlocks(from, limit); + const blocks = await this.blockSource.getBlocks({ from, limit }); + const ctxByCheckpoint = await this.#getCheckpointContextsForBlocks(wantContext ? blocks : []); return (await Promise.all( - blocks.map(async block => { - const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(block.number) : undefined; - return blockResponseFromL2Block(block, options, ctx); - }), + blocks.map(block => blockResponseFromL2Block(block, options, ctxByCheckpoint.get(block.checkpointNumber))), )) as BlockResponse[]; } - const results: BlockResponse[] = []; - for (let i = 0; i < limit; i++) { - const blockNumber = BlockNumber(from + i); - const ctx = await this.blockSource.getBlockDataWithCheckpointContext(blockNumber); - if (!ctx) { - break; - } - results.push(blockResponseFromBlockData(ctx.data, blockNumber, options, ctx) as BlockResponse); - } - return results; + const dataItems = await this.blockSource.getBlocksData({ from, limit }); + const ctxByCheckpoint = await this.#getCheckpointContextsForBlocks(wantContext ? dataItems : []); + return (await Promise.all( + dataItems.map(data => blockResponseFromBlockData(data, options, ctxByCheckpoint.get(data.checkpointNumber))), + )) as BlockResponse[]; + } + + /** Fetches checkpoint context for a set of blocks, deduplicating shared checkpoints. */ + async #getCheckpointContextsForBlocks( + blocks: { checkpointNumber: CheckpointNumber }[], + // TODO(palla): CheckpointNumber should be accepted by this lint rule + // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections + ): Promise> { + const unique = Array.from(new Set(blocks.map(b => b.checkpointNumber))); + const entries = await Promise.all(unique.map(async n => [n, await this.#getCheckpointContext(n)] as const)); + return new Map(entries); } public async getCheckpoint( @@ -461,29 +449,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb return datas.map(d => checkpointResponseFromCheckpointData(d, options)) as CheckpointResponse[]; } - private async buildGenesisBlockResponse(options: BlockIncludeOptions): Promise { - const initial = this.worldStateSynchronizer.getCommitted().getInitialHeader(); - const empty = L2Block.empty(initial); - const response: BlockResponse = { - header: empty.header, - archive: empty.archive, - hash: await this.#getInitialHeaderHash(), - checkpointNumber: empty.checkpointNumber, - indexWithinCheckpoint: empty.indexWithinCheckpoint, - number: empty.number, - }; - if (options.includeTransactions) { - (response as BlockResponse).body = empty.body; - } - if (options.includeL1PublishInfo) { - (response as BlockResponse).l1 = { published: false }; - } - if (options.includeAttestations) { - (response as BlockResponse).attestations = []; - } - return response; - } - /** * initializes the Aztec Node, wait for component to sync. * @param config - The configuration to be used by the aztec node. @@ -600,15 +565,21 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb // Track started resources so we can clean up on partial failure during node creation. const started: { stop?(): Promise | void }[] = []; try { + // Create world-state first so we can retrieve the initial header before constructing the archiver. + const nativeWs = await createWorldState(config, options.genesis); + const initialHeader = nativeWs.getInitialHeader(); + const initialBlockHash = await initialHeader.hash(); const archiver = await createArchiver( config, { blobClient, epochCache, telemetry, dateProvider }, { blockUntilSync: !config.skipArchiverInitialSync }, + initialHeader, + initialBlockHash, ); started.push(archiver); - // now create the merkle trees and the world state synchronizer - const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, options.genesis, telemetry); + // The synchronizer takes ownership of the native world-state from here + const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, nativeWs, telemetry); started.push(worldStateSynchronizer); const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification; let peerProofVerifier: ClientProtocolCircuitVerifier; @@ -663,6 +634,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb dateProvider, telemetry, deps.p2pClientDeps, + initialBlockHash, ); started.push(p2pClient); @@ -1075,18 +1047,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb ): Promise { let upToBlockNumber: BlockNumber | undefined; if (referenceBlock) { - const initialBlockHash = await this.#getInitialHeaderHash(); - if (referenceBlock.equals(initialBlockHash)) { - upToBlockNumber = BlockNumber(0); - } else { - const header = await this.blockSource.getBlockHeaderByHash(referenceBlock); - if (!header) { - throw new Error( - `Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`, - ); - } - upToBlockNumber = header.globalVariables.blockNumber; + const data = await this.blockSource.getBlockData({ hash: referenceBlock }); + if (!data) { + throw new Error( + `Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`, + ); } + upToBlockNumber = data.header.globalVariables.blockNumber; } return this.logsSource.getPrivateLogsByTags(tags, page, upToBlockNumber); } @@ -1099,18 +1066,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb ): Promise { let upToBlockNumber: BlockNumber | undefined; if (referenceBlock) { - const initialBlockHash = await this.#getInitialHeaderHash(); - if (referenceBlock.equals(initialBlockHash)) { - upToBlockNumber = BlockNumber(0); - } else { - const header = await this.blockSource.getBlockHeaderByHash(referenceBlock); - if (!header) { - throw new Error( - `Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`, - ); - } - upToBlockNumber = header.globalVariables.blockNumber; + const data = await this.blockSource.getBlockData({ hash: referenceBlock }); + if (!data) { + throw new Error( + `Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`, + ); } + upToBlockNumber = data.header.globalVariables.blockNumber; } return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber); } @@ -1398,13 +1360,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb * @returns The L2 to L1 messages (empty array if the epoch is not found). */ public async getL2ToL1Messages(epoch: EpochNumber): Promise { - // Assumes `getCheckpointedBlocksForEpoch` returns blocks in ascending order of block number. - const checkpointedBlocks = await this.blockSource.getCheckpointedBlocksForEpoch(epoch); - const blocksInCheckpoints = chunkBy(checkpointedBlocks, cb => cb.block.header.globalVariables.slotNumber).map( - group => group.map(cb => cb.block), - ); - return blocksInCheckpoints.map(blocks => - blocks.map(block => block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs)), + const blocks = await this.blockSource.getBlocks({ epoch, onlyCheckpointed: true }); + const blocksInCheckpoints = chunkBy(blocks, block => block.header.globalVariables.slotNumber); + return blocksInCheckpoints.map(slotBlocks => + slotBlocks.map(block => block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs)), ); } @@ -1887,13 +1846,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb } } - #getInitialHeaderHash(): Promise { - if (!this.initialHeaderHashPromise) { - this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash(); - } - return this.initialHeaderHashPromise; - } - /** * Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched * @param block - The block parameter (block number, block hash, or 'latest') at which to get the data. @@ -1908,32 +1860,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb this.log.error(`Error getting world state: ${err}`); } - if (block === 'latest') { - this.log.debug(`Using committed db for block 'latest', world state synced upto ${blockSyncedTo}`); + const query = this.normalizeBlockParameter(block); + if ('tag' in query && query.tag === 'proposed') { + this.log.debug(`Using committed db for latest block, world state synced upto ${blockSyncedTo}`); return this.worldStateSynchronizer.getCommitted(); } - // Get the block number, either directly from the parameter or by quering the archiver with the block hash - let blockNumber: BlockNumber; - if (BlockHash.isBlockHash(block)) { - const initialBlockHash = await this.#getInitialHeaderHash(); - if (block.equals(initialBlockHash)) { - // Block 0 is a first-class historical block: its state lives in the trees' persisted - // block-0 payload. Resolving the genesis hash to block number 0 lets the snapshot path - // pin reads to genesis state even after the node has advanced past it. - blockNumber = BlockNumber.ZERO; - } else { - const header = await this.blockSource.getBlockHeaderByHash(block); - if (!header) { - throw new Error( - `Block hash ${block.toString()} not found when querying world state. If the node API has been queried with anchor block hash possibly a reorg has occurred.`, - ); - } - blockNumber = header.getBlockNumber(); - } - } else { - blockNumber = block as BlockNumber; - } + const blockNumber = await this.resolveBlockNumber(block); // Check it's within world state sync range if (blockNumber > blockSyncedTo) { @@ -1945,13 +1878,17 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb const snapshot = this.worldStateSynchronizer.getSnapshot(blockNumber); - // Double-check world-state synced to the same block hash as was requested - if (BlockHash.isBlockHash(block)) { + // Double-check world-state synced to the same block hash as was requested. + // Block 0 is skipped: the snapshot returned by `getSnapshot(0)` is the *pre*-genesis archive + // (size 0), so leaf 0 is not yet inserted from that snapshot's view even though block 0's hash + // does live at archive index 0 in the committed tree. The genesis hash is already validated by + // the archiver when it resolves the hash query to block number 0. + const requestedHash = 'hash' in query ? query.hash : undefined; + if (requestedHash !== undefined && blockNumber !== BlockNumber.ZERO) { const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber)); - if (!blockHash || !block.equals(blockHash)) { - const initialBlockHash = await this.#getInitialHeaderHash(); + if (!blockHash || !requestedHash.equals(blockHash)) { throw new Error( - `Block hash ${block.toString()} not found in world state at block number ${blockNumber} (world state has ${blockHash?.toString() ?? 'no hash'} at that index, genesis header hash is ${initialBlockHash.toString()}). If the node API has been queried with anchor block hash possibly a reorg has occurred.`, + `Block hash ${requestedHash.toString()} not found in world state at block number ${blockNumber} (world state has ${blockHash?.toString() ?? 'no hash'} at that index, genesis header hash is ${this.blockSource.getGenesisBlockHash().toString()}). If the node API has been queried with anchor block hash possibly a reorg has occurred.`, ); } } @@ -1961,29 +1898,20 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb /** Resolves any {@link BlockParameter} variant to a concrete block number. */ protected async resolveBlockNumber(block: BlockParameter): Promise { - const resolved = await this.resolveBlockParameter(block); - if (resolved.number !== undefined) { - return resolved.number; - } - if (resolved.hash !== undefined) { - const initialBlockHash = await this.#getInitialHeaderHash(); - if (resolved.hash.equals(initialBlockHash)) { - return BlockNumber.ZERO; - } - const header = await this.blockSource.getBlockHeaderByHash(resolved.hash); - if (!header) { - throw new Error(`Block hash ${resolved.hash.toString()} not found.`); + const query = this.normalizeBlockParameter(block); + const blockNumber = await this.blockSource.getBlockNumber(query); + if (blockNumber === undefined) { + if ('hash' in query) { + throw new Error( + `Block hash ${query.hash.toString()} not found when querying world state. If the node API has been queried with anchor block hash possibly a reorg has occurred.`, + ); } - return header.getBlockNumber(); - } - if (resolved.archive !== undefined) { - const header = await this.blockSource.getBlockHeaderByArchive(resolved.archive); - if (!header) { - throw new Error(`Block with archive ${resolved.archive.toString()} not found.`); + if ('archive' in query) { + throw new Error(`Block with archive ${query.archive.toString()} not found.`); } - return header.getBlockNumber(); + throw new Error(`Block not found for ${inspectBlockParameter(block)}.`); } - throw new BadRequestError(`Invalid BlockParameter: ${JSON.stringify(block)}`); + return blockNumber; } /** diff --git a/yarn-project/aztec-node/src/sentinel/sentinel.test.ts b/yarn-project/aztec-node/src/sentinel/sentinel.test.ts index 7e54ee16cd79..1d745c9d4582 100644 --- a/yarn-project/aztec-node/src/sentinel/sentinel.test.ts +++ b/yarn-project/aztec-node/src/sentinel/sentinel.test.ts @@ -8,6 +8,7 @@ import type { P2PClient } from '@aztec/p2p'; import { OffenseType, WANT_TO_SLASH_EVENT, type WantToSlashArgs } from '@aztec/slasher'; import { CommitteeAttestation, + GENESIS_BLOCK_HEADER_HASH, L2Block, type L2BlockSource, type L2BlockStream, @@ -61,6 +62,7 @@ describe('sentinel', () => { beforeEach(async () => { epochCache = mock(); archiver = mock(); + archiver.getGenesisBlockHash.mockReturnValue(GENESIS_BLOCK_HEADER_HASH); p2p = mock(); blockStream = mock(); @@ -607,7 +609,7 @@ describe('sentinel', () => { epochCache.getTargetSlot.mockReturnValue(slot); epochCache.getEpochNow.mockReturnValue(epochNumber); epochCache.getTargetEpoch.mockReturnValue(epochNumber); - archiver.getBlockHeader.calledWith(blockNumber).mockResolvedValue(mockBlock.header); + archiver.getBlockData.mockResolvedValue({ header: mockBlock.header } as any); archiver.getL1Constants.mockResolvedValue(l1Constants); epochCache.getL1Constants.mockReturnValue(l1Constants); @@ -729,7 +731,7 @@ describe('sentinel', () => { const epochNumber = getEpochAtSlot(blockSlot, l1Constants); const validator1 = EthAddress.random(); - archiver.getBlockHeader.calledWith(blockNumber).mockResolvedValue(mockBlock.header); + archiver.getBlockData.mockResolvedValue({ header: mockBlock.header } as any); epochCache.getCommittee.mockResolvedValue({ committee: [validator1], diff --git a/yarn-project/aztec-node/src/sentinel/sentinel.ts b/yarn-project/aztec-node/src/sentinel/sentinel.ts index a3b2ce0d4a62..7c1a67f245fe 100644 --- a/yarn-project/aztec-node/src/sentinel/sentinel.ts +++ b/yarn-project/aztec-node/src/sentinel/sentinel.ts @@ -77,7 +77,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme protected logger = createLogger('node:sentinel'), ) { super(); - this.l2TipsStore = new L2TipsMemoryStore(); + this.l2TipsStore = new L2TipsMemoryStore(archiver.getGenesisBlockHash()); const interval = (epochCache.getL1Constants().ethereumSlotDuration * 1000) / 4; this.runningPromise = new RunningPromise(this.work.bind(this), logger, interval); } @@ -151,7 +151,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme return; } const blockNumber = event.block.number; - const header = await this.archiver.getBlockHeader(blockNumber); + const header = (await this.archiver.getBlockData({ number: blockNumber }))?.header; if (!header) { this.logger.error(`Failed to get block header ${blockNumber}`); return; diff --git a/yarn-project/aztec/src/cli/aztec_start_action.ts b/yarn-project/aztec/src/cli/aztec_start_action.ts index bc46c50af937..65bb2b2d7aa2 100644 --- a/yarn-project/aztec/src/cli/aztec_start_action.ts +++ b/yarn-project/aztec/src/cli/aztec_start_action.ts @@ -67,9 +67,6 @@ export async function aztecStart(options: any, userLog: LogFn, debugLogger: Logg } else if (options.bot) { const { startBot } = await import('./cmds/start_bot.js'); await startBot(options, signalHandlers, services, userLog); - } else if (options.archiver) { - const { startArchiver } = await import('./cmds/start_archiver.js'); - ({ config } = await startArchiver(options, signalHandlers, services)); } else if (options.p2pBootstrap) { const { startP2PBootstrap } = await import('./cmds/start_p2p_bootstrap.js'); ({ config } = await startP2PBootstrap(options, signalHandlers, services, userLog)); diff --git a/yarn-project/aztec/src/cli/aztec_start_options.ts b/yarn-project/aztec/src/cli/aztec_start_options.ts index 6b5a605da08f..dec92e996fd7 100644 --- a/yarn-project/aztec/src/cli/aztec_start_options.ts +++ b/yarn-project/aztec/src/cli/aztec_start_options.ts @@ -222,12 +222,6 @@ export const aztecStartOptions: { [key: string]: AztecStartOption[] } = { }, ], ARCHIVER: [ - { - flag: '--archiver', - description: 'Starts Aztec Archiver with options', - defaultValue: undefined, - env: undefined, - }, ...getOptions( 'archiver', omitConfigMappings(archiverConfigMappings, Object.keys(l1ContractsConfigMappings) as (keyof ArchiverConfig)[]), diff --git a/yarn-project/aztec/src/cli/cmds/start_archiver.ts b/yarn-project/aztec/src/cli/cmds/start_archiver.ts deleted file mode 100644 index e4bb2bd72db9..000000000000 --- a/yarn-project/aztec/src/cli/cmds/start_archiver.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { type ArchiverConfig, archiverConfigMappings, createArchiver, getArchiverConfigFromEnv } from '@aztec/archiver'; -import { createLogger } from '@aztec/aztec.js/log'; -import { type BlobClientConfig, blobClientConfigMapping, createBlobClient } from '@aztec/blob-client/client'; -import { getL1Config } from '@aztec/cli/config'; -import type { NamespacedApiHandlers } from '@aztec/foundation/json-rpc/server'; -import { ArchiverApiSchema } from '@aztec/stdlib/interfaces/server'; -import { type DataStoreConfig, dataConfigMappings } from '@aztec/stdlib/kv-store'; -import { getConfigEnvVars as getTelemetryClientConfig, initTelemetryClient } from '@aztec/telemetry-client'; - -import { extractRelevantOptions } from '../util.js'; - -export type { ArchiverConfig, DataStoreConfig }; - -/** Starts a standalone archiver. */ -export async function startArchiver( - options: any, - signalHandlers: (() => Promise)[], - services: NamespacedApiHandlers, -): Promise<{ config: ArchiverConfig & DataStoreConfig }> { - const envConfig = getArchiverConfigFromEnv(); - const cliOptions = extractRelevantOptions( - options, - { - // dataConfigMappings must come first: its l1Contracts only maps rollupAddress, - // while archiverConfigMappings (spread later) maps all L1 contract addresses. - ...dataConfigMappings, - ...archiverConfigMappings, - ...blobClientConfigMapping, - }, - 'archiver', - ); - - let archiverConfig = { ...envConfig, ...cliOptions }; - archiverConfig.dataStoreMapSizeKb = archiverConfig.archiverStoreMapSizeKb ?? archiverConfig.dataStoreMapSizeKb; - - if (!archiverConfig.l1Contracts.registryAddress || archiverConfig.l1Contracts.registryAddress.isZero()) { - throw new Error('L1 registry address is required to start an Archiver'); - } - - const { addresses, config: l1Config } = await getL1Config( - archiverConfig.l1Contracts.registryAddress, - archiverConfig.l1RpcUrls, - archiverConfig.l1ChainId, - ); - - archiverConfig.l1Contracts = addresses; - archiverConfig = { ...archiverConfig, ...l1Config }; - - const telemetry = await initTelemetryClient(getTelemetryClientConfig()); - const blobClient = createBlobClient(archiverConfig, { logger: createLogger('archiver:blob-client:client') }); - const archiver = await createArchiver(archiverConfig, { telemetry, blobClient }, { blockUntilSync: true }); - services.archiver = [archiver, ArchiverApiSchema]; - signalHandlers.push(archiver.stop); - - return { config: archiverConfig }; -} diff --git a/yarn-project/aztec/src/testing/epoch_test_settler.ts b/yarn-project/aztec/src/testing/epoch_test_settler.ts index c2fa06dcf024..37697b74ed29 100644 --- a/yarn-project/aztec/src/testing/epoch_test_settler.ts +++ b/yarn-project/aztec/src/testing/epoch_test_settler.ts @@ -31,8 +31,7 @@ export class EpochTestSettler { } async handleEpochReadyToProve(epoch: EpochNumber): Promise { - const checkpointedBlocks = await this.l2BlockSource.getCheckpointedBlocksForEpoch(epoch); - const blocks = checkpointedBlocks.map(b => b.block); + const blocks = await this.l2BlockSource.getBlocks({ epoch, onlyCheckpointed: true }); this.log.info( `Settling epoch ${epoch} with blocks ${blocks[0]?.header.getBlockNumber()} to ${blocks.at(-1)?.header.getBlockNumber()}`, { blocks: blocks.map(b => b.toBlockInfo()) }, @@ -58,7 +57,7 @@ export class EpochTestSettler { this.log.info(`No L2 to L1 messages in epoch ${epoch}`); } - const lastCheckpoint = checkpointedBlocks.at(-1)?.checkpointNumber; + const lastCheckpoint = blocks.at(-1)?.checkpointNumber; if (lastCheckpoint !== undefined) { await this.rollupCheatCodes.markAsProven(lastCheckpoint); } else { diff --git a/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts b/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts index b78b7f79bd32..71df12761fdb 100644 --- a/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts +++ b/yarn-project/end-to-end/src/composed/ha/e2e_ha_full.test.ts @@ -350,8 +350,8 @@ describe('HA Full Setup', () => { } // Verify txs were included in the block (tests full signing path) - expect(block.block.body.txEffects.length).toBeGreaterThan(0); - logger.info(`Block contains ${block.block.body.txEffects.length} transaction(s)`); + expect(block.body!.txEffects.length).toBeGreaterThan(0); + logger.info(`Block contains ${block.body!.txEffects.length} transaction(s)`); // get attestations from checkpoint const [checkpoint] = await aztecNode.getCheckpoints(block.checkpointNumber, 1, { includeAttestations: true }); @@ -372,7 +372,7 @@ describe('HA Full Setup', () => { logger.info(`Verified ${attestations.length} signatures from Web3Signer`); // Query database to verify HA coordination - const slotNumber = BigInt(block.block.header.globalVariables.slotNumber); + const slotNumber = BigInt(block.header.globalVariables.slotNumber); logger.info(`Querying duties for slot ${slotNumber} (block ${receipt.blockNumber})`); const allDuties = await getValidatorDuties(mainPool, slotNumber); expect(allDuties.length).toBeGreaterThan(0); @@ -461,7 +461,7 @@ describe('HA Full Setup', () => { if (!block) { throw new Error(`Block ${receipt.blockNumber} not found`); } - const blockSlot = block.block.header.globalVariables.slotNumber; + const blockSlot = block.header.globalVariables.slotNumber; logger.info(`Block was built in slot ${blockSlot}`); // Compute round for governance voting from the block slot @@ -668,7 +668,7 @@ describe('HA Full Setup', () => { if (!block) { throw new Error(`Block ${receipt.blockNumber} not found`); } - const slotNumber = BigInt(block.block.header.globalVariables.slotNumber); + const slotNumber = BigInt(block.header.globalVariables.slotNumber); const duties = await getValidatorDuties(mainPool, slotNumber); const blockProposalDuty = duties.find(d => d.dutyType === 'BLOCK_PROPOSAL'); @@ -731,7 +731,7 @@ describe('HA Full Setup', () => { if (!block) { throw new Error(`Block ${receipt.blockNumber} not found`); } - const slotNumber = BigInt(block.block.header.globalVariables.slotNumber); + const slotNumber = BigInt(block.header.globalVariables.slotNumber); // PRIMARY CHECK: Database records show all attestation duties attempted/completed const duties = await getValidatorDuties(mainPool, slotNumber); diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_ha_sync.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_ha_sync.test.ts index 58db53710795..938426711a38 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_ha_sync.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_ha_sync.test.ts @@ -191,8 +191,8 @@ describe('e2e_epochs/epochs_ha_sync', () => { expect(minProposed).toBeGreaterThan(initialCheckpointedBlock); logger.warn(`Verifying block hashes at proposed block ${minProposed}.`, { proposedNumbers }); - const headers = await Promise.all(allArchivers.map(a => a.getBlockHeader(minProposed))); - const hashes = await Promise.all(headers.map(h => h!.hash())); + const blockDatas = await Promise.all(allArchivers.map(a => a.getBlockData({ number: minProposed }))); + const hashes = await Promise.all(blockDatas.map(d => d!.header.hash())); for (let i = 1; i < hashes.length; i++) { expect(hashes[i].toString()).toBe(hashes[0].toString()); } diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_high_tps_block_building.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_high_tps_block_building.test.ts index d70747193f1d..303436897d52 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_high_tps_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_high_tps_block_building.test.ts @@ -177,16 +177,19 @@ describe('e2e_epochs/epochs_high_tps_block_building', () => { let checkedFullCheckpoints = 0; for (const checkpointBlocks of checkpoints) { const first = checkpointBlocks[0]; - const slotStartTimestamp = getTimestampForSlot(first.block.slot, test.constants); - const l1OffsetInSlot = Number(first.l1.timestamp - slotStartTimestamp) / ethereumSlotDuration; + const firstSlot = first.header.globalVariables.slotNumber; + const slotStartTimestamp = getTimestampForSlot(firstSlot, test.constants); + const l1OffsetInSlot = first.l1?.published + ? Number(first.l1.timestamp - slotStartTimestamp) / ethereumSlotDuration + : undefined; logger.warn( - `Checkpoint ${first.checkpointNumber} (target slot ${first.block.slot}) mined at L1 block ${first.l1.blockNumber} ` + + `Checkpoint ${first.checkpointNumber} (target slot ${firstSlot}) mined at L1 block ${first.l1?.published ? first.l1.blockNumber : 'pending'} ` + `(offset ${l1OffsetInSlot} into L2 slot) with ${checkpointBlocks.length} blocks`, { - blocks: checkpointBlocks.map(b => ({ number: b.block.number, txs: b.block.body.txEffects.length })), + blocks: checkpointBlocks.map(b => ({ number: b.number, txs: b.body?.txEffects.length })), }, ); - if (first.block.slot < targetSlot || checkedFullCheckpoints >= CHECKPOINTS_TO_CHECK) { + if (firstSlot < targetSlot || checkedFullCheckpoints >= CHECKPOINTS_TO_CHECK) { continue; } @@ -195,7 +198,7 @@ describe('e2e_epochs/epochs_high_tps_block_building', () => { for (const block of checkpointBlocks) { // We don't test for exactly TXS_PER_BLOCK since CI delays make this flakey - const txCount = block.block.body.txEffects.length; + const txCount = block.body!.txEffects.length; expect(txCount).toBeGreaterThanOrEqual(1); expect(txCount).toBeLessThanOrEqual(TXS_PER_BLOCK); } @@ -206,7 +209,7 @@ describe('e2e_epochs/epochs_high_tps_block_building', () => { // Check that we've gone through all checkpoints, and at least one checkpoint reached // expected number of blocks, and at least one block reached the expected number of txs. expect(checkedFullCheckpoints).toBe(CHECKPOINTS_TO_CHECK); - expect(Math.max(...blocks.map(b => b.block.body.txEffects.length))).toEqual(TXS_PER_BLOCK); + expect(Math.max(...blocks.map(b => b.body!.txEffects.length))).toEqual(TXS_PER_BLOCK); expect(Math.max(...checkpoints.map(c => c.length))).toEqual(BLOCKS_PER_CHECKPOINT); // Expect no failures from sequencers during block building. Filter out the self-proposal 'Rollup contract diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts index b0ba59e5624c..ec13d41a8ed4 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_mbps.parallel.test.ts @@ -442,15 +442,15 @@ describe('e2e_epochs/epochs_mbps', () => { if (tips.proposed.number <= tips.checkpointed.block.number) { return false; } - const header = await nonValidatorArchiver.getBlockHeader(tips.proposed.number); - if (!header) { + const blockData = await nonValidatorArchiver.getBlockData({ number: tips.proposed.number }); + if (!blockData) { return false; } - const blocksInSlot = await nonValidatorArchiver.getBlocksForSlot(header.globalVariables.slotNumber); + const blocksInSlot = await nonValidatorArchiver.getBlocksForSlot(blockData.header.globalVariables.slotNumber); if (blocksInSlot.length < 2) { return false; } - multiBlockSlotNumber = header.globalVariables.slotNumber; + multiBlockSlotNumber = blockData.header.globalVariables.slotNumber; checkpointedBlockNumber = tips.checkpointed.block.number; return true; }, diff --git a/yarn-project/end-to-end/src/e2e_l1_publisher/e2e_l1_publisher.test.ts b/yarn-project/end-to-end/src/e2e_l1_publisher/e2e_l1_publisher.test.ts index e77df10cb097..eb25a0beed48 100644 --- a/yarn-project/end-to-end/src/e2e_l1_publisher/e2e_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/e2e_l1_publisher/e2e_l1_publisher.test.ts @@ -25,7 +25,13 @@ import { EthCheatCodesWithState, RollupCheatCodes, startAnvil } from '@aztec/eth import type { Anvil } from '@aztec/ethereum/test'; import type { ExtendedViemWalletClient } from '@aztec/ethereum/types'; import { range } from '@aztec/foundation/array'; -import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { + BlockNumber, + CheckpointNumber, + EpochNumber, + IndexWithinCheckpoint, + SlotNumber, +} from '@aztec/foundation/branded-types'; import { Buffer32 } from '@aztec/foundation/buffer'; import { times, timesParallel } from '@aztec/foundation/collection'; import { SecretValue } from '@aztec/foundation/config'; @@ -42,10 +48,12 @@ import { ProtocolContractsList, protocolContractsHash } from '@aztec/protocol-co import { LightweightCheckpointBuilder } from '@aztec/prover-client/light'; import { SequencerPublisher, SequencerPublisherMetrics } from '@aztec/sequencer-client'; import { - CheckpointedL2Block, + type BlockData, + type BlockQuery, + type BlocksQuery, + Body, type CommitteeAttestation, CommitteeAttestationsAndSigners, - GENESIS_BLOCK_HEADER_HASH, L2Block, type L2Tips, Signature, @@ -63,13 +71,9 @@ import { } from '@aztec/stdlib/p2p'; import { CheckpointHeader } from '@aztec/stdlib/rollup'; import { fr, mockProcessedTx } from '@aztec/stdlib/testing'; +import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees'; import type { BlockHeader, CheckpointGlobalVariables, ProcessedTx } from '@aztec/stdlib/tx'; -import { - type MerkleTreeAdminDatabase, - NativeWorldStateService, - ServerWorldStateSynchronizer, - type WorldStateConfig, -} from '@aztec/world-state'; +import { NativeWorldStateService, ServerWorldStateSynchronizer, type WorldStateConfig } from '@aztec/world-state'; import { beforeEach, describe, expect, it, jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -110,7 +114,7 @@ describe('L1Publisher integration', () => { let publisher: SequencerPublisher; - let builderDb: MerkleTreeAdminDatabase; + let builderDb: NativeWorldStateService; // The header of the last block let prevHeader: BlockHeader; @@ -203,25 +207,47 @@ describe('L1Publisher integration', () => { builderDb = await NativeWorldStateService.tmp(EthAddress.fromString(rollupAddress)); blocks = []; + // World-state derives block 0's hash from its initial header (which depends on prefilled state and + // genesisTimestamp), not from the static GENESIS_BLOCK_HEADER_HASH constant. The mock must report the + // same hash so L2BlockStream's reorg-search at genesis sees matching local/source hashes. + const initialHeader = builderDb.getInitialHeader(); + const initialHeaderHash = (await initialHeader.hash()).toString(); + const genesisArchiveSnapshot = new AppendOnlyTreeSnapshot( + deployL1ContractsArgs.genesisArchiveRoot ?? new Fr(GENESIS_ARCHIVE_ROOT), + 1, + ); + const genesisBlock = new L2Block( + genesisArchiveSnapshot, + initialHeader, + Body.empty(), + CheckpointNumber.ZERO, + IndexWithinCheckpoint(0), + ); + const genesisBlockData: BlockData = { + header: initialHeader, + archive: genesisArchiveSnapshot, + blockHash: await initialHeader.hash(), + checkpointNumber: CheckpointNumber.ZERO, + indexWithinCheckpoint: IndexWithinCheckpoint(0), + }; blockSource = mock({ - getBlocks(from, limit) { - return Promise.resolve(blocks.slice(from - 1, from - 1 + limit)); + getBlocks(query: BlocksQuery) { + if (!('from' in query)) { + return Promise.resolve([]); + } + return Promise.resolve(blocks.slice(query.from - 1, query.from - 1 + query.limit)); }, - // Methods needed by L2BlockStream for world state sync - getCheckpointedBlocks(from, limit) { - const slicedBlocks = blocks.slice(from - 1, from - 1 + limit); - return Promise.all( - slicedBlocks.map( - async block => - new CheckpointedL2Block( - // Test uses 1-block-per-checkpoint, so checkpoint number equals block number - CheckpointNumber.fromBlockNumber(block.number), - block, - new L1PublishedData(BigInt(block.number), BigInt(block.number), (await block.hash()).toString()), - [], - ), - ), - ); + getBlock(query: BlockQuery) { + if ('number' in query && Number(query.number) === 0) { + return Promise.resolve(genesisBlock); + } + return Promise.resolve(undefined); + }, + getBlockData(query: BlockQuery) { + if ('number' in query && Number(query.number) === 0) { + return Promise.resolve(genesisBlockData); + } + return Promise.resolve(undefined); }, async getCheckpoints(checkpointNumber, _limit) { // Test uses 1-block-per-checkpoint, so we find block by checkpoint number @@ -247,7 +273,7 @@ describe('L1Publisher integration', () => { const latestBlock = blocks.at(-1); const blockId = latestBlock ? { number: latestBlock.number, hash: (await latestBlock.hash()).toString() } - : { number: BlockNumber.ZERO, hash: GENESIS_BLOCK_HEADER_HASH.toString() }; + : { number: BlockNumber.ZERO, hash: initialHeaderHash }; // Test uses 1-block-per-checkpoint, so checkpoint number equals block number const tipId = { block: blockId, diff --git a/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts b/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts index 2e1be2bb0450..3d0481027068 100644 --- a/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts +++ b/yarn-project/end-to-end/src/e2e_multi_validator/e2e_multi_validator_node.test.ts @@ -12,7 +12,7 @@ import { createExtendedL1Client } from '@aztec/ethereum/client'; import { getL1ContractsConfigEnvVars } from '@aztec/ethereum/config'; import { RollupContract } from '@aztec/ethereum/contracts'; import type { DeployAztecL1ContractsReturnType } from '@aztec/ethereum/deploy-aztec-l1-contracts'; -import { EpochNumber } from '@aztec/foundation/branded-types'; +import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types'; import { SecretValue } from '@aztec/foundation/config'; import { Signature } from '@aztec/foundation/eth-signature'; import { retryUntil } from '@aztec/foundation/retry'; @@ -123,8 +123,8 @@ describe('e2e_multi_validator_node', () => { expect(tx.blockNumber).toBeDefined(); const dataStore = (aztecNode as AztecNodeService).getBlockSource() as Archiver; - const checkpointedBlock = await dataStore.getCheckpointedBlock(tx.blockNumber!); - const [publishedCheckpoint] = await dataStore.getCheckpoints(checkpointedBlock!.checkpointNumber, 1); + const blockData = await dataStore.getBlockData({ number: BlockNumber(tx.blockNumber!) }); + const [publishedCheckpoint] = await dataStore.getCheckpoints(blockData!.checkpointNumber, 1); const signatureContext = { chainId: config.l1ChainId, rollupAddress: deployL1ContractsValues.l1ContractAddresses.rollupAddress, @@ -185,8 +185,8 @@ describe('e2e_multi_validator_node', () => { expect(tx.blockNumber).toBeDefined(); const dataStore = (aztecNode as AztecNodeService).getBlockSource() as Archiver; - const checkpointedBlock = await dataStore.getCheckpointedBlock(tx.blockNumber!); - const [publishedCheckpoint] = await dataStore.getCheckpoints(checkpointedBlock!.checkpointNumber, 1); + const blockData = await dataStore.getBlockData({ number: BlockNumber(tx.blockNumber!) }); + const [publishedCheckpoint] = await dataStore.getCheckpoints(blockData!.checkpointNumber, 1); const signatureContext = { chainId: config.l1ChainId, rollupAddress: deployL1ContractsValues.l1ContractAddresses.rollupAddress, diff --git a/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts b/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts index e499925375e8..7cedb449aa54 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts @@ -2,6 +2,7 @@ import type { Archiver } from '@aztec/archiver'; import type { AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node'; import { waitForTx } from '@aztec/aztec.js/node'; import { TxHash } from '@aztec/aztec.js/tx'; +import { BlockNumber } from '@aztec/foundation/branded-types'; import { Signature } from '@aztec/foundation/eth-signature'; import { retryUntil } from '@aztec/foundation/retry'; import type { SequencerClient } from '@aztec/sequencer-client'; @@ -186,8 +187,8 @@ describe('e2e_p2p_network', () => { const receipt = await nodes[0].getTxReceipt(txsSentViaDifferentNodes[0][0]); const blockNumber = receipt.blockNumber!; const dataStore = (nodes[0] as AztecNodeService).getBlockSource() as Archiver; - const checkpointedBlock = await dataStore.getCheckpointedBlock(blockNumber); - const [publishedCheckpoint] = await dataStore.getCheckpoints(checkpointedBlock!.checkpointNumber, 1); + const blockData = await dataStore.getBlockData({ number: BlockNumber(blockNumber) }); + const [publishedCheckpoint] = await dataStore.getCheckpoints(blockData!.checkpointNumber, 1); const signatureContext = { chainId: t.ctx.aztecNodeConfig.l1ChainId, rollupAddress: t.ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress, diff --git a/yarn-project/end-to-end/src/e2e_p2p/gossip_network_no_cheat.test.ts b/yarn-project/end-to-end/src/e2e_p2p/gossip_network_no_cheat.test.ts index cc99e6283744..5dc5504822ca 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/gossip_network_no_cheat.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/gossip_network_no_cheat.test.ts @@ -6,7 +6,7 @@ import { waitForTx } from '@aztec/aztec.js/node'; import { TxHash } from '@aztec/aztec.js/tx'; import { addL1Validator } from '@aztec/cli/l1/validators'; import { RollupContract } from '@aztec/ethereum/contracts'; -import { EpochNumber } from '@aztec/foundation/branded-types'; +import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types'; import { Signature } from '@aztec/foundation/eth-signature'; import { retryUntil } from '@aztec/foundation/retry'; import { sleep } from '@aztec/foundation/sleep'; @@ -246,8 +246,8 @@ describe('e2e_p2p_network', () => { // Gather signers from attestations downloaded from L1 const blockNumber = await nodes[0].getTxReceipt(txsSentViaDifferentNodes[0][0]).then(r => r.blockNumber!); const dataStore = (nodes[0] as AztecNodeService).getBlockSource() as Archiver; - const checkpointedBlock = await dataStore.getCheckpointedBlock(blockNumber); - const [publishedCheckpoint] = await dataStore.getCheckpoints(checkpointedBlock!.checkpointNumber, 1); + const blockData = await dataStore.getBlockData({ number: BlockNumber(blockNumber) }); + const [publishedCheckpoint] = await dataStore.getCheckpoints(blockData!.checkpointNumber, 1); const signatureContext = { chainId: t.ctx.aztecNodeConfig.l1ChainId, rollupAddress: t.ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress, diff --git a/yarn-project/end-to-end/src/e2e_p2p/preferred_gossip_network.test.ts b/yarn-project/end-to-end/src/e2e_p2p/preferred_gossip_network.test.ts index 25c96a232ea2..7e13b565cf65 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/preferred_gossip_network.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/preferred_gossip_network.test.ts @@ -2,6 +2,7 @@ import type { Archiver } from '@aztec/archiver'; import type { AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node'; import { waitForTx } from '@aztec/aztec.js/node'; import { TxHash } from '@aztec/aztec.js/tx'; +import { BlockNumber } from '@aztec/foundation/branded-types'; import { Signature } from '@aztec/foundation/eth-signature'; import { retryUntil } from '@aztec/foundation/retry'; import { ENR, type P2PClient, type P2PService, type PeerId } from '@aztec/p2p'; @@ -355,8 +356,8 @@ describe('e2e_p2p_preferred_network', () => { // Gather signers from attestations downloaded from L1 const blockNumber = receipts[0].blockNumber!; const dataStore = (nodes[0] as AztecNodeService).getBlockSource() as Archiver; - const checkpointedBlock = await dataStore.getCheckpointedBlock(blockNumber); - const [publishedCheckpoint] = await dataStore.getCheckpoints(checkpointedBlock!.checkpointNumber, 1); + const blockData = await dataStore.getBlockData({ number: BlockNumber(blockNumber) }); + const [publishedCheckpoint] = await dataStore.getCheckpoints(blockData!.checkpointNumber, 1); const signatureContext = { chainId: t.ctx.aztecNodeConfig.l1ChainId, rollupAddress: t.ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress, diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index fb140b65439a..0ac2cfc8e6de 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -60,7 +60,7 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { CommitteeAttestationsAndSigners, L2Block } from '@aztec/stdlib/block'; import { Checkpoint } from '@aztec/stdlib/checkpoint'; import { tryStop } from '@aztec/stdlib/interfaces/server'; -import { createWorldStateSynchronizer } from '@aztec/world-state'; +import { createWorldState, createWorldStateSynchronizer } from '@aztec/world-state'; import * as fs from 'fs'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -595,14 +595,19 @@ describe('e2e_synching', () => { opts.config!, createLogger('test:blob-client:client'), ); + const nativeWs = await createWorldState(opts.config!, opts.genesis); + const initialHeader = nativeWs.getInitialHeader(); + const initialBlockHash = await initialHeader.hash(); const archiver = await createArchiver( opts.config!, { blobClient, dateProvider: opts.dateProvider! }, { blockUntilSync: true }, + initialHeader, + initialBlockHash, ); const pendingCheckpointNumber = CheckpointNumber.fromBigInt(await rollup.read.getPendingCheckpointNumber()); - const worldState = await createWorldStateSynchronizer(opts.config!, archiver, opts.genesis); + const worldState = await createWorldStateSynchronizer(opts.config!, archiver, nativeWs); await worldState.start(); // We prune the last token and schnorr contract @@ -616,7 +621,7 @@ describe('e2e_synching', () => { await opts.cheatCodes!.eth.warp(Number(timeJumpTo), { resetBlockInterval: true }); expect(await archiver.getCheckpointNumber()).toBeGreaterThan(provenThrough); - const blockTip = (await archiver.getBlock(await archiver.getBlockNumber()))!; + const blockTip = (await archiver.getBlock({ number: await archiver.getBlockNumber() }))!; const txHash = blockTip.body.txEffects[0].txHash; const contractClassIds = await archiver.getContractClassIds(); diff --git a/yarn-project/foundation/src/branded-types/block_number.test.ts b/yarn-project/foundation/src/branded-types/block_number.test.ts index 8002aac31223..4e78aa2bb8e3 100644 --- a/yarn-project/foundation/src/branded-types/block_number.test.ts +++ b/yarn-project/foundation/src/branded-types/block_number.test.ts @@ -91,6 +91,25 @@ describe('BlockNumber', () => { it('rejects non-integer values', () => { expect(() => BlockNumberSchema.parse(1.5)).toThrow(); }); + + it('rejects hex strings (must not be coerced as a number)', () => { + expect(BlockNumberSchema.safeParse('0x1234').success).toBe(false); + expect( + BlockNumberSchema.safeParse('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff').success, + ).toBe(false); + }); + + it('rejects scientific-notation strings', () => { + expect(BlockNumberSchema.safeParse('1e5').success).toBe(false); + }); + + it('rejects values above MAX_SAFE_INTEGER', () => { + expect(BlockNumberSchema.safeParse(Number.MAX_SAFE_INTEGER + 1).success).toBe(false); + }); + + it('rejects bigints above MAX_SAFE_INTEGER', () => { + expect(BlockNumberSchema.safeParse(BigInt(Number.MAX_SAFE_INTEGER) + 1n).success).toBe(false); + }); }); describe('type safety', () => { diff --git a/yarn-project/foundation/src/branded-types/block_number.ts b/yarn-project/foundation/src/branded-types/block_number.ts index 50de2cebb9d9..9092c5d95111 100644 --- a/yarn-project/foundation/src/branded-types/block_number.ts +++ b/yarn-project/foundation/src/branded-types/block_number.ts @@ -98,9 +98,27 @@ BlockNumber.add = function (bn: BlockNumber, increment: number): BlockNumber { BlockNumber.ZERO = BlockNumber(0); function makeBlockNumberSchema(minValue: number) { + const max = Number.MAX_SAFE_INTEGER; return z - .union([z.number(), z.bigint(), z.string()]) - .pipe(z.coerce.number().int().min(minValue)) + .union([ + z.number().int().min(minValue).max(max), + z + .bigint() + .min(BigInt(minValue)) + .max(BigInt(max)) + .transform(v => Number(v)), + z + .string() + .regex(/^\d+$/, 'BlockNumber string must be a non-negative decimal integer') + .transform((v, ctx) => { + const parsed = Number(v); + if (!Number.isInteger(parsed) || parsed > max || parsed < minValue) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: `BlockNumber out of range: ${v}` }); + return z.NEVER; + } + return parsed; + }), + ]) .transform(value => BlockNumber(value)); } diff --git a/yarn-project/kv-store/src/stores/l2_tips_store.test.ts b/yarn-project/kv-store/src/stores/l2_tips_store.test.ts index 8480313a4c87..6bcb995b5bfd 100644 --- a/yarn-project/kv-store/src/stores/l2_tips_store.test.ts +++ b/yarn-project/kv-store/src/stores/l2_tips_store.test.ts @@ -1,5 +1,6 @@ import type { AztecAsyncKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; +import { GENESIS_BLOCK_HEADER_HASH } from '@aztec/stdlib/block'; import { testL2TipsStore } from '@aztec/stdlib/block/test'; import { L2TipsKVStore } from './l2_tips_store.js'; @@ -13,6 +14,6 @@ describe('L2TipsStore', () => { testL2TipsStore(async () => { kvStore = await openTmpStore('test', true); - return new L2TipsKVStore(kvStore, 'test'); + return new L2TipsKVStore(kvStore, 'test', GENESIS_BLOCK_HEADER_HASH); }); }); diff --git a/yarn-project/kv-store/src/stores/l2_tips_store.ts b/yarn-project/kv-store/src/stores/l2_tips_store.ts index 92cf726c9f99..8d34baa72736 100644 --- a/yarn-project/kv-store/src/stores/l2_tips_store.ts +++ b/yarn-project/kv-store/src/stores/l2_tips_store.ts @@ -1,5 +1,5 @@ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; -import { type L2BlockTag, L2TipsStoreBase } from '@aztec/stdlib/block'; +import { type BlockHash, type L2BlockTag, L2TipsStoreBase } from '@aztec/stdlib/block'; import { PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; import type { AztecAsyncMap } from '../interfaces/map.js'; @@ -18,8 +18,9 @@ export class L2TipsKVStore extends L2TipsStoreBase { constructor( private store: AztecAsyncKVStore, namespace: string, + initialBlockHash: BlockHash, ) { - super(); + super(initialBlockHash); this.l2TipsStore = store.openMap([namespace, 'l2_tips'].join('_')); this.l2BlockHashesStore = store.openMap([namespace, 'l2_block_hashes'].join('_')); this.l2BlockNumberToCheckpointNumberStore = store.openMap( diff --git a/yarn-project/p2p/src/client/factory.ts b/yarn-project/p2p/src/client/factory.ts index 8e702494697e..8080d68ae005 100644 --- a/yarn-project/p2p/src/client/factory.ts +++ b/yarn-project/p2p/src/client/factory.ts @@ -4,7 +4,7 @@ import { type Logger, createLogger } from '@aztec/foundation/log'; import { DateProvider } from '@aztec/foundation/timer'; import type { AztecAsyncKVStore } from '@aztec/kv-store'; import { AztecLMDBStoreV2, createStore } from '@aztec/kv-store/lmdb-v2'; -import type { L2BlockSource } from '@aztec/stdlib/block'; +import type { BlockHash, L2BlockSource } from '@aztec/stdlib/block'; import type { ChainConfig } from '@aztec/stdlib/config'; import type { ContractDataSource } from '@aztec/stdlib/contract'; import type { BlockMinFeesProvider } from '@aztec/stdlib/gas'; @@ -58,6 +58,7 @@ export async function createP2PClient( dateProvider: DateProvider = new DateProvider(), telemetry: TelemetryClient = getTelemetryClient(), deps: P2PClientDeps = {}, + initialBlockHash: BlockHash, ) { const config = await configureP2PClientAddresses({ ...inputConfig, @@ -210,6 +211,8 @@ export async function createP2PClient( config, dateProvider, telemetry, + undefined, + initialBlockHash, ); } diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index e9d5f7a5ab74..12b921ca5e1b 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -58,11 +58,26 @@ describe('P2P Client', () => { mempools = { txPool, attestationPool }; kvStore = await openTmpStore('test'); - client = createClient(); + client = await createClient(); }); - const createClient = (config: Partial = {}) => - new P2PClient(kvStore, blockSource, mempools, p2pService, txCollection, undefined, epochCache, config); + const createClient = async (config: Partial = {}) => { + const initialBlockHash = await blockSource.getInitialHeader().hash(); + return new P2PClient( + kvStore, + blockSource, + mempools, + p2pService, + txCollection, + undefined, + epochCache, + config, + undefined, + undefined, + undefined, + initialBlockHash, + ); + }; const advanceToProvenBlock = async (blockNumber: BlockNumber) => { blockSource.setProvenBlockNumber(blockNumber); @@ -166,7 +181,7 @@ describe('P2P Client', () => { const synchedBlock = await client.getSyncedLatestBlockNum(); await client.stop(); - const client2 = createClient(); + const client2 = await createClient(); await expect(client2.getSyncedLatestBlockNum()).resolves.toEqual(synchedBlock); }); @@ -212,7 +227,7 @@ describe('P2P Client', () => { describe('Chain prunes', () => { it('detects checkpoint prune when checkpoint number stays the same', async () => { - client = createClient({ txPoolDeleteTxsAfterReorg: true }); + client = await createClient({ txPoolDeleteTxsAfterReorg: true }); blockSource.setProvenBlockNumber(0); // Only checkpoint up to block 90 — blocks 91-100 are proposed but not checkpointed blockSource.setCheckpointedBlockNumber(90); @@ -230,7 +245,7 @@ describe('P2P Client', () => { }); it('detects checkpoint prune when checkpoint number increases by one', async () => { - client = createClient({ txPoolDeleteTxsAfterReorg: true }); + client = await createClient({ txPoolDeleteTxsAfterReorg: true }); blockSource.setProvenBlockNumber(0); blockSource.setCheckpointedBlockNumber(100); await client.start(); @@ -254,7 +269,7 @@ describe('P2P Client', () => { }); it('detects checkpoint prune when checkpoint number decreases by one', async () => { - client = createClient({ txPoolDeleteTxsAfterReorg: true }); + client = await createClient({ txPoolDeleteTxsAfterReorg: true }); blockSource.setProvenBlockNumber(0); // Checkpoint all 100 blocks — client stores checkpoint number 100 blockSource.setCheckpointedBlockNumber(100); @@ -272,7 +287,7 @@ describe('P2P Client', () => { }); it('detects epoch prune when checkpoint number decreases by more than 1', async () => { - client = createClient({ txPoolDeleteTxsAfterReorg: true }); + client = await createClient({ txPoolDeleteTxsAfterReorg: true }); blockSource.setProvenBlockNumber(0); // Checkpoint all 100 blocks — client stores checkpoint number 100 blockSource.setCheckpointedBlockNumber(100); diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 9f99cd20de7a..65f661a4023e 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -6,9 +6,10 @@ import { DateProvider } from '@aztec/foundation/timer'; import type { AztecAsyncKVStore, AztecAsyncSingleton } from '@aztec/kv-store'; import { L2TipsKVStore } from '@aztec/kv-store/stores'; import { + type BlockData, + type BlockHash, type CheckpointId, type EthAddress, - GENESIS_BLOCK_HEADER_HASH, type L2Block, type L2BlockId, type L2BlockSource, @@ -95,6 +96,7 @@ export class P2PClient extends WithTracer implements P2P { private _dateProvider: DateProvider = new DateProvider(), private telemetry: TelemetryClient = getTelemetryClient(), private log = createLogger('p2p'), + initialBlockHash: BlockHash, ) { super(telemetry, 'P2PClient'); @@ -110,7 +112,7 @@ export class P2PClient extends WithTracer implements P2P { this.telemetry, ); - this.l2Tips = new L2TipsKVStore(store, 'p2p_client'); + this.l2Tips = new L2TipsKVStore(store, 'p2p_client', initialBlockHash); this.synchedLatestSlot = store.openSingleton('p2p_pool_last_l2_slot'); } @@ -164,7 +166,7 @@ export class P2PClient extends WithTracer implements P2P { const from = BlockNumber(oldFinalizedBlockNum + 1); const limit = event.block.number - from + 1; if (limit > 0) { - const oldBlocks = await this.l2BlockSource.getBlocks(from, limit); + const oldBlocks = await this.l2BlockSource.getBlocksData({ from, limit }); await this.handleFinalizedL2Blocks(oldBlocks); } break; @@ -584,13 +586,10 @@ export class P2PClient extends WithTracer implements P2P { */ public async getStatus(): Promise { const blockNumber = await this.getSyncedLatestBlockNum(); - const blockHash = - blockNumber === 0 - ? GENESIS_BLOCK_HEADER_HASH.toString() - : await this.l2BlockSource - .getBlockHeader(blockNumber) - .then(header => header?.hash()) - .then(hash => hash?.toString()); + const blockHash = await this.l2BlockSource + .getBlockData({ number: blockNumber }) + .then(data => data?.header.hash()) + .then(hash => hash?.toString()); return { state: this.currentState, @@ -660,7 +659,7 @@ export class P2PClient extends WithTracer implements P2P { * @param blocks - A list of finalized L2 blocks. * @returns Empty promise. */ - private async handleFinalizedL2Blocks(blocks: L2Block[]): Promise { + private async handleFinalizedL2Blocks(blocks: BlockData[]): Promise { if (!blocks.length) { return; } diff --git a/yarn-project/p2p/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts b/yarn-project/p2p/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts index 06cf85f91a1b..8be78d419bf5 100644 --- a/yarn-project/p2p/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +++ b/yarn-project/p2p/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts @@ -125,6 +125,7 @@ async function startClient(config: P2PConfig, clientIndex: number) { new DateProvider(), telemetry as TelemetryClient, deps, + await l2BlockSource.getInitialHeader().hash(), ); await client.start(); diff --git a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.test.ts b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.test.ts index b43fbd537613..e68e5113b5a8 100644 --- a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.test.ts +++ b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.test.ts @@ -46,7 +46,7 @@ describe('reqRespBlockTxsHandler', () => { peerId = mock(); attestationPool.getBlockProposal.mockResolvedValue(undefined); - archiver.getL2BlockByArchive.mockResolvedValue(undefined); + archiver.getBlock.mockResolvedValue(undefined); txPool.getTxsByHash.mockResolvedValue([]); txPool.hasTxs.mockResolvedValue([]); }); @@ -139,7 +139,7 @@ describe('reqRespBlockTxsHandler', () => { const txHashes = block.body.txEffects.map(e => e.txHash); const txs = txHashes.map(h => makeTx(h)); - archiver.getL2BlockByArchive.mockResolvedValue(block); + archiver.getBlock.mockResolvedValue(block); txPool.hasTxs.mockResolvedValue([true, true, true]); txPool.getTxsByHash.mockResolvedValue(txs); @@ -151,14 +151,14 @@ describe('reqRespBlockTxsHandler', () => { expect(response.txIndices.getTrueIndices()).toEqual([0, 1, 2]); expect(attestationPool.getBlockProposal).toHaveBeenCalledWith(block.archive.root.toString()); - expect(archiver.getL2BlockByArchive).toHaveBeenCalledWith(block.archive.root); + expect(archiver.getBlock).toHaveBeenCalledWith({ archive: block.archive.root }); }); it('returns partial availability when some txs missing from pool', async () => { const block = await L2Block.random(BlockNumber(5), { txsPerBlock: 3 }); const txHashes = block.body.txEffects.map(e => e.txHash); - archiver.getL2BlockByArchive.mockResolvedValue(block); + archiver.getBlock.mockResolvedValue(block); txPool.hasTxs.mockResolvedValue([true, false, true]); txPool.getTxsByHash.mockResolvedValue([makeTx(txHashes[0]), undefined, makeTx(txHashes[2])]); @@ -169,7 +169,7 @@ describe('reqRespBlockTxsHandler', () => { expect(response.txIndices.getTrueIndices()).toEqual([0, 2]); expect(attestationPool.getBlockProposal).toHaveBeenCalledWith(block.archive.root.toString()); - expect(archiver.getL2BlockByArchive).toHaveBeenCalledWith(block.archive.root); + expect(archiver.getBlock).toHaveBeenCalledWith({ archive: block.archive.root }); }); it('does not query archiver if attestation pool has the block', async () => { @@ -185,7 +185,7 @@ describe('reqRespBlockTxsHandler', () => { await callHandler(request); expect(attestationPool.getBlockProposal).toHaveBeenCalledWith(proposal.archive.toString()); - expect(archiver.getL2BlockByArchive).not.toHaveBeenCalled(); + expect(archiver.getBlock).not.toHaveBeenCalled(); }); }); }); diff --git a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.ts b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.ts index 2ac1612547f9..d1a4a81876d9 100644 --- a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +++ b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.ts @@ -39,7 +39,9 @@ export function reqRespBlockTxsHandler( // First try attestation pool, then fall back to archiver let txHashes = (await attestationPool.getBlockProposal(request.archiveRoot.toString()))?.txHashes; if (!txHashes) { - txHashes = (await archiver.getL2BlockByArchive(request.archiveRoot))?.body.txEffects.map(effect => effect.txHash); + txHashes = (await archiver.getBlock({ archive: request.archiveRoot }))?.body.txEffects.map( + effect => effect.txHash, + ); } let requestedTxsHashes; diff --git a/yarn-project/p2p/src/test-helpers/make-test-p2p-clients.ts b/yarn-project/p2p/src/test-helpers/make-test-p2p-clients.ts index bdfe07c00df2..2098eafeda5e 100644 --- a/yarn-project/p2p/src/test-helpers/make-test-p2p-clients.ts +++ b/yarn-project/p2p/src/test-helpers/make-test-p2p-clients.ts @@ -114,6 +114,7 @@ export async function makeTestP2PClient( logger, p2pServiceFactory: mockGossipSubNetwork && getMockPubSubP2PServiceFactory(mockGossipSubNetwork), }, + await l2BlockSource.getInitialHeader().hash(), ); return client; diff --git a/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts b/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts index cbcd849954c7..a8baa387ae16 100644 --- a/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts +++ b/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts @@ -424,6 +424,7 @@ process.on('message', async msg => { undefined, telemetry as TelemetryClient, deps, + await l2BlockSource.getInitialHeader().hash(), ); const testService = new TestLibP2PService( diff --git a/yarn-project/prover-node/src/job/epoch-proving-job.test.ts b/yarn-project/prover-node/src/job/epoch-proving-job.test.ts index 80d1a8f053a3..d6b6fcd8739f 100644 --- a/yarn-project/prover-node/src/job/epoch-proving-job.test.ts +++ b/yarn-project/prover-node/src/job/epoch-proving-job.test.ts @@ -116,10 +116,10 @@ describe('epoch-proving-job', () => { const txHashes = checkpoints.map(c => c.blocks.map(b => b.body.txEffects.map(tx => tx.txHash))).flat(2); txs = txHashes.map(txHash => ({ txHash, getTxHash: () => txHash }) as Tx); - l2BlockSource.getBlockHeader.mockResolvedValue(initialHeader); + l2BlockSource.getBlockData.mockResolvedValue({ header: initialHeader } as any); l2BlockSource.getL1Constants.mockResolvedValue({ ethereumSlotDuration: 0.1 } as L1RollupConstants); - l2BlockSource.getCheckpointedBlockHeadersForEpoch.mockResolvedValue( - checkpoints.map(c => c.blocks.map(b => b.header)).flat(), + l2BlockSource.getBlocksData.mockResolvedValue( + checkpoints.map(c => c.blocks.map(b => ({ header: b.header }) as any)).flat(), ); l2BlockSource.getCheckpoints.mockResolvedValue([ { checkpoint: checkpoints.at(-1)!, attestations } as PublishedCheckpoint, @@ -221,7 +221,7 @@ describe('epoch-proving-job', () => { it('halts if a new block for the epoch is found', async () => { const newHeaders = times(NUM_BLOCKS + 1, i => BlockHeader.random({ blockNumber: BlockNumber(i + 1) })); - l2BlockSource.getCheckpointedBlockHeadersForEpoch.mockResolvedValue(newHeaders); + l2BlockSource.getBlocksData.mockResolvedValue(newHeaders.map(h => ({ header: h }) as any)); const job = createJob(); await job.run(); diff --git a/yarn-project/prover-node/src/job/epoch-proving-job.ts b/yarn-project/prover-node/src/job/epoch-proving-job.ts index 1349cf517df6..022fcc02289e 100644 --- a/yarn-project/prover-node/src/job/epoch-proving-job.ts +++ b/yarn-project/prover-node/src/job/epoch-proving-job.ts @@ -410,7 +410,9 @@ export class EpochProvingJob implements Traceable { const intervalMs = Math.ceil((await l2BlockSource.getL1Constants()).ethereumSlotDuration / 2) * 1000; this.epochCheckPromise = new RunningPromise( async () => { - const blockHeaders = await l2BlockSource.getCheckpointedBlockHeadersForEpoch(this.epochNumber); + const blockHeaders = ( + await l2BlockSource.getBlocksData({ epoch: this.epochNumber, onlyCheckpointed: true }) + ).map(d => d.header); const blockHashes = await Promise.all(blockHeaders.map(header => header.hash())); const thisBlocks = this.checkpoints.flatMap(checkpoint => checkpoint.blocks); const thisBlockHashes = await Promise.all(thisBlocks.map(block => block.hash())); diff --git a/yarn-project/prover-node/src/monitors/epoch-monitor.test.ts b/yarn-project/prover-node/src/monitors/epoch-monitor.test.ts index f60efddf4c76..a32bb5d76933 100644 --- a/yarn-project/prover-node/src/monitors/epoch-monitor.test.ts +++ b/yarn-project/prover-node/src/monitors/epoch-monitor.test.ts @@ -34,18 +34,15 @@ describe('EpochMonitor', () => { isEpochComplete(epochNumber) { return Promise.resolve(epochNumber <= lastEpochComplete); }, - getBlockHeader(blockNumber: BlockNumber | 'latest') { - if (blockNumber === 'latest') { + getBlockData(query) { + if (!('number' in query)) { return Promise.resolve(undefined); } - const slot = blockToSlot[blockNumber]; + const slot = blockToSlot[query.number]; return Promise.resolve( slot === undefined ? undefined - : mock({ - getSlot: () => SlotNumber.fromBigInt(slot), - toString: () => `0x${slot.toString(16)}`, - }), + : ({ header: { getSlot: () => SlotNumber.fromBigInt(slot) } as unknown as BlockHeader } as any), ); }, getProvenBlockNumber() { diff --git a/yarn-project/prover-node/src/monitors/epoch-monitor.ts b/yarn-project/prover-node/src/monitors/epoch-monitor.ts index c1c7c89ddfbf..42694d81613b 100644 --- a/yarn-project/prover-node/src/monitors/epoch-monitor.ts +++ b/yarn-project/prover-node/src/monitors/epoch-monitor.ts @@ -98,7 +98,7 @@ export class EpochMonitor implements Traceable { private async getEpochNumberToProve() { const lastBlockProven = await this.l2BlockSource.getProvenBlockNumber(); const firstBlockToProve = BlockNumber(lastBlockProven + 1); - const firstBlockHeaderToProve = await this.l2BlockSource.getBlockHeader(firstBlockToProve); + const firstBlockHeaderToProve = (await this.l2BlockSource.getBlockData({ number: firstBlockToProve }))?.header; if (!firstBlockHeaderToProve) { return { epochToProve: undefined, blockNumber: firstBlockToProve }; } diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index 546c128bc234..45b09dc511a0 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -172,8 +172,12 @@ describe('prover-node', () => { proven: genesisTipId, finalized: genesisTipId, }); - l2BlockSource.getBlockHeader.mockImplementation(number => - Promise.resolve(number === checkpoints[0].blocks[0].number - 1 ? previousBlockHeader : undefined), + l2BlockSource.getBlockData.mockImplementation(query => + Promise.resolve( + 'number' in query && query.number === checkpoints[0].blocks[0].number - 1 + ? ({ header: previousBlockHeader } as any) + : undefined, + ), ); // L1 to L2 message source returns no messages diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 48359691826e..9d7a4336054e 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -360,16 +360,13 @@ export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable } private async gatherPreviousBlockHeader(epochNumber: EpochNumber, previousBlockNumber: number) { - const header = await (previousBlockNumber === 0 - ? this.worldState.getCommitted().getInitialHeader() - : this.l2BlockSource.getBlockHeader(BlockNumber(previousBlockNumber))); - - if (!header) { + const data = await this.l2BlockSource.getBlockData({ number: BlockNumber(previousBlockNumber) }); + if (!data?.header) { throw new Error(`Previous block header ${previousBlockNumber} not found for proving epoch ${epochNumber}`); } - this.log.verbose(`Gathered previous block header ${header.getBlockNumber()} for epoch ${epochNumber}`); - return header; + this.log.verbose(`Gathered previous block header ${data.header.getBlockNumber()} for epoch ${epochNumber}`); + return data.header; } /** Extracted for testing purposes. */ diff --git a/yarn-project/pxe/src/block_synchronizer/block_stream_source.test.ts b/yarn-project/pxe/src/block_synchronizer/block_stream_source.test.ts new file mode 100644 index 000000000000..3a82b36a9649 --- /dev/null +++ b/yarn-project/pxe/src/block_synchronizer/block_stream_source.test.ts @@ -0,0 +1,85 @@ +import { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types'; +import { Fr } from '@aztec/foundation/curves/bn254'; +import { BlockHash, L2Block } from '@aztec/stdlib/block'; +import type { AztecNode } from '@aztec/stdlib/interfaces/client'; + +import { type MockProxy, mock } from 'jest-mock-extended'; + +import { blockStreamSourceFromAztecNode } from './block_stream_source.js'; + +describe('blockStreamSourceFromAztecNode', () => { + let node: MockProxy; + let source: ReturnType; + + const buildResponse = async (block: L2Block) => ({ + header: block.header, + archive: block.archive, + hash: await block.hash(), + checkpointNumber: block.checkpointNumber, + indexWithinCheckpoint: block.indexWithinCheckpoint, + number: block.number, + }); + + beforeEach(() => { + node = mock(); + source = blockStreamSourceFromAztecNode(node); + }); + + describe('getBlockData', () => { + it('forwards a number query as a {number} parameter', async () => { + const block = await L2Block.random(BlockNumber(7)); + node.getBlock.mockResolvedValue((await buildResponse(block)) as any); + + const result = await source.getBlockData({ number: BlockNumber(7) }); + + expect(node.getBlock).toHaveBeenCalledWith({ number: BlockNumber(7) }); + expect(result?.header.equals(block.header)).toBe(true); + }); + + it('forwards a hash query as a {hash} parameter', async () => { + const block = await L2Block.random(BlockNumber(2)); + const hash = await block.hash(); + node.getBlock.mockResolvedValue((await buildResponse(block)) as any); + + const result = await source.getBlockData({ hash }); + + expect(node.getBlock).toHaveBeenCalledWith({ hash }); + expect(result?.blockHash.equals(hash)).toBe(true); + }); + + it('forwards an archive query as an {archive} parameter', async () => { + const block = await L2Block.random(BlockNumber(3)); + const archive = Fr.random(); + node.getBlock.mockResolvedValue((await buildResponse(block)) as any); + + const result = await source.getBlockData({ archive }); + + expect(node.getBlock).toHaveBeenCalledWith({ archive }); + expect(result?.header.equals(block.header)).toBe(true); + }); + + it('forwards a tag query as a {tag} parameter', async () => { + const block = await L2Block.random(BlockNumber(9)); + node.getBlock.mockResolvedValue((await buildResponse(block)) as any); + + const result = await source.getBlockData({ tag: 'proven' }); + + expect(node.getBlock).toHaveBeenCalledWith({ tag: 'proven' }); + expect(result?.header.equals(block.header)).toBe(true); + }); + + it('returns undefined when node returns undefined', async () => { + node.getBlock.mockResolvedValue(undefined); + const result = await source.getBlockData({ hash: BlockHash.random() }); + expect(result).toBeUndefined(); + }); + }); + + describe('getBlocks', () => { + it('throws on epoch query', async () => { + await expect(source.getBlocks({ epoch: EpochNumber(1), onlyCheckpointed: true })).rejects.toThrow( + /epoch query not supported/, + ); + }); + }); +}); diff --git a/yarn-project/pxe/src/block_synchronizer/block_stream_source.ts b/yarn-project/pxe/src/block_synchronizer/block_stream_source.ts index 507eb02699db..eb4b2ca4bfcc 100644 --- a/yarn-project/pxe/src/block_synchronizer/block_stream_source.ts +++ b/yarn-project/pxe/src/block_synchronizer/block_stream_source.ts @@ -1,28 +1,44 @@ -import type { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; +import type { CheckpointNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; -import { L2Block, type L2BlockSource } from '@aztec/stdlib/block'; +import { type BlockData, type BlockQuery, type BlocksQuery, L2Block, type L2BlockSource } from '@aztec/stdlib/block'; import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -// TODO(spl/new-rpc-api): delete once `L2BlockStream` is refactored to consume the new -// `BlockResponse` / `CheckpointResponse` shapes. For now the stream requires concrete `L2Block` -// and `PublishedCheckpoint` instances, so we rehydrate them from RPC responses. /** - * Lifts an {@link AztecNode} RPC client into the shape {@link L2BlockStream} expects. `getBlocks` - * requests transaction bodies so that real `L2Block` instances can be constructed; + * Lifts an {@link AztecNode} RPC client into the shape {@link L2BlockStream} expects. + * `getBlocks` requests transaction bodies so that real `L2Block` instances can be constructed; * `getCheckpoints` requests blocks + L1 info + attestations so that `PublishedCheckpoint` * instances are fully populated. */ export function blockStreamSourceFromAztecNode( node: AztecNode, -): Pick { +): Pick { return { getL2Tips: () => node.getL2Tips(), - getBlockHeader: number => node.getBlockHeader(number), - getCheckpointedBlocks: (from: BlockNumber, limit: number) => node.getCheckpointedBlocks(from, limit), - async getBlocks(from: BlockNumber, limit: number): Promise { - const responses = await node.getBlocks(from, limit, { includeTransactions: true }); + async getBlockData(query: BlockQuery): Promise { + const response = await node.getBlock(query); + if (!response) { + return undefined; + } + return { + header: response.header, + archive: response.archive, + blockHash: response.hash, + checkpointNumber: response.checkpointNumber, + indexWithinCheckpoint: response.indexWithinCheckpoint, + }; + }, + + async getBlocks(query: BlocksQuery): Promise { + // Epoch lookups are not exposed on the public AztecNode RPC; only `from + limit` is. + if (!('from' in query)) { + throw new Error('getBlocks with epoch query not supported via AztecNode RPC'); + } + if (query.onlyCheckpointed) { + throw new Error('getBlocks with onlyCheckpointed not supported via AztecNode RPC'); + } + const responses = await node.getBlocks(query.from, query.limit, { includeTransactions: true }); return responses.map(r => new L2Block(r.archive, r.header, r.body!, r.checkpointNumber, r.indexWithinCheckpoint)); }, diff --git a/yarn-project/pxe/src/block_synchronizer/block_synchronizer.test.ts b/yarn-project/pxe/src/block_synchronizer/block_synchronizer.test.ts index fc82fb6dcb53..af264b14f6de 100644 --- a/yarn-project/pxe/src/block_synchronizer/block_synchronizer.test.ts +++ b/yarn-project/pxe/src/block_synchronizer/block_synchronizer.test.ts @@ -4,7 +4,13 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAsyncKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { L2TipsKVStore } from '@aztec/kv-store/stores'; -import { BlockHash, GENESIS_CHECKPOINT_HEADER_HASH, L2Block, type L2BlockStream } from '@aztec/stdlib/block'; +import { + BlockHash, + GENESIS_BLOCK_HEADER_HASH, + GENESIS_CHECKPOINT_HEADER_HASH, + L2Block, + type L2BlockStream, +} from '@aztec/stdlib/block'; import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; @@ -52,7 +58,7 @@ describe('BlockSynchronizer', () => { store = await openTmpStore('test'); blockStream = mock(); aztecNode = mock(); - tipsStore = new L2TipsKVStore(store, 'pxe'); + tipsStore = new L2TipsKVStore(store, 'pxe', GENESIS_BLOCK_HEADER_HASH); anchorBlockStore = new AnchorBlockStore(store); noteStore = new NoteStore(store); privateEventStore = new PrivateEventStore(store); @@ -143,14 +149,7 @@ describe('BlockSynchronizer', () => { }); blockStream.sync.mockReturnValue(syncBlocker); const genesisBlock = await L2Block.random(BlockNumber(0)); - aztecNode.getBlock.mockResolvedValue({ - header: genesisBlock.header, - archive: genesisBlock.archive, - hash: await genesisBlock.hash(), - checkpointNumber: genesisBlock.checkpointNumber, - indexWithinCheckpoint: genesisBlock.indexWithinCheckpoint, - number: genesisBlock.number, - } as any); + aztecNode.getBlock.mockResolvedValue({ header: genesisBlock.header } as any); // Start a sync (don't await) const syncPromise = synchronizer.sync(); @@ -254,16 +253,9 @@ describe('BlockSynchronizer', () => { const initialBlock = await L2Block.random(BlockNumber(0)); await anchorBlockStore.setHeader(initialBlock.header); - // Mock node to return block header + // Mock node to return block const provenBlock = await L2Block.random(BlockNumber(5)); - aztecNode.getBlock.mockResolvedValue({ - header: provenBlock.header, - archive: provenBlock.archive, - hash: await provenBlock.hash(), - checkpointNumber: provenBlock.checkpointNumber, - indexWithinCheckpoint: provenBlock.indexWithinCheckpoint, - number: provenBlock.number, - } as any); + aztecNode.getBlock.mockResolvedValue({ header: provenBlock.header } as any); await synchronizer.handleBlockStreamEvent({ type: 'chain-proven', @@ -281,16 +273,9 @@ describe('BlockSynchronizer', () => { const initialBlock = await L2Block.random(BlockNumber(0)); await anchorBlockStore.setHeader(initialBlock.header); - // Mock node to return block header + // Mock node to return block const finalizedBlock = await L2Block.random(BlockNumber(10)); - aztecNode.getBlock.mockResolvedValue({ - header: finalizedBlock.header, - archive: finalizedBlock.archive, - hash: await finalizedBlock.hash(), - checkpointNumber: finalizedBlock.checkpointNumber, - indexWithinCheckpoint: finalizedBlock.indexWithinCheckpoint, - number: finalizedBlock.number, - } as any); + aztecNode.getBlock.mockResolvedValue({ header: finalizedBlock.header } as any); await synchronizer.handleBlockStreamEvent({ type: 'chain-finalized', diff --git a/yarn-project/pxe/src/pxe.test.ts b/yarn-project/pxe/src/pxe.test.ts index 6cecfeb2cc39..311716cd32e2 100644 --- a/yarn-project/pxe/src/pxe.test.ts +++ b/yarn-project/pxe/src/pxe.test.ts @@ -1,6 +1,6 @@ import { BBBundlePrivateKernelProver } from '@aztec/bb-prover/client/bundle'; import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'; -import { BlockNumber, CheckpointNumber, IndexWithinCheckpoint } from '@aztec/foundation/branded-types'; +import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; import { AztecLMDBStoreV2, openTmpStore } from '@aztec/kv-store/lmdb-v2'; @@ -18,7 +18,6 @@ import { randomContractInstanceWithAddress, randomDeployedContract, } from '@aztec/stdlib/testing'; -import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees'; import { BlockHeader, GlobalVariables, TxHash } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; @@ -184,14 +183,7 @@ describe('PXE', () => { globalVariables, }); node.getBlockHeader.mockResolvedValue(blockHeader); - node.getBlock.mockResolvedValue({ - header: blockHeader, - archive: AppendOnlyTreeSnapshot.empty(), - hash: GENESIS_BLOCK_HEADER_HASH, - checkpointNumber: CheckpointNumber.fromBlockNumber(lastKnownBlockNumber), - indexWithinCheckpoint: IndexWithinCheckpoint.ZERO, - number: lastKnownBlockNumber, - } as any); + node.getBlock.mockResolvedValue({ header: blockHeader } as any); // Mock getL2Tips which is needed for syncing tagged logs const tipId = { diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 427587a6c693..3145bbacfd5f 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -18,7 +18,7 @@ import { } from '@aztec/stdlib/abi'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { L2TipsProvider } from '@aztec/stdlib/block'; +import { GENESIS_BLOCK_HEADER_HASH, type L2TipsProvider } from '@aztec/stdlib/block'; import { CompleteAddress, type ContractInstanceWithAddress, @@ -212,6 +212,14 @@ export class PXE { const info = await node.getNodeInfo(); + // Source the genesis block hash from the node so PXE's L2BlockStream agrees with the node's + // archiver on the dynamic initial header hash. Without this the tip store would fall back to + // the static `GENESIS_BLOCK_HEADER_HASH` constant, which only matches deployments with the + // default empty genesis (timestamp 0, no prefilled public data) and diverges otherwise — the + // sync at block 0 would then get stuck in `areBlockHashesEqualAt` and abort. If the node does + // not return a genesis block (older node or test fixture) we fall back to the static constant. + const initialBlockHash = (await node.getBlock(BlockNumber.ZERO))?.hash ?? GENESIS_BLOCK_HEADER_HASH; + const proverEnabled = config.proverEnabled !== undefined ? config.proverEnabled : info.realProofs; const addressStore = new AddressStore(store); const privateEventStore = new PrivateEventStore(store); @@ -223,7 +231,7 @@ export class PXE { const recipientTaggingStore = new RecipientTaggingStore(store); const capsuleStore = new CapsuleStore(store); const keyStore = new KeyStore(store); - const tipsStore = new L2TipsKVStore(store, 'pxe'); + const tipsStore = new L2TipsKVStore(store, 'pxe', initialBlockHash); const contractSyncService = new ContractSyncService( node, contractStore, diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index e6ac31a7583d..4159fd2f11f9 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -297,6 +297,10 @@ describe('sequencer', () => { getBlockNumber: mockFn().mockResolvedValue(lastBlockNumber), getL2Tips: mockFn().mockResolvedValue({ proposed: { number: lastBlockNumber, hash }, + proposedCheckpoint: { + block: { number: lastBlockNumber, hash }, + checkpoint: { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() }, + }, checkpointed: { block: { number: lastBlockNumber, hash }, checkpoint: { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() }, @@ -313,7 +317,6 @@ describe('sequencer', () => { getL1Timestamp: mockFn().mockResolvedValue(1000n), isPendingChainInvalid: mockFn().mockResolvedValue(false), getPendingChainValidationStatus: mockFn().mockResolvedValue({ valid: true }), - getCheckpointedBlocksForEpoch: mockFn().mockResolvedValue([]), getCheckpointsForEpoch: mockFn().mockResolvedValue([]), getCheckpointsDataForEpoch: mockFn().mockResolvedValue([]), getSyncedL2SlotNumber: mockFn().mockResolvedValue(SlotNumber(Number.MAX_SAFE_INTEGER)), @@ -324,6 +327,10 @@ describe('sequencer', () => { getL1ToL2Messages: () => Promise.resolve(Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(Fr.ZERO)), getL2Tips: mockFn().mockResolvedValue({ proposed: { number: lastBlockNumber, hash }, + proposedCheckpoint: { + block: { number: lastBlockNumber, hash }, + checkpoint: { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() }, + }, checkpointed: { block: { number: lastBlockNumber, hash }, checkpoint: { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() }, diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 4fc638ceeae1..0e7f3ab55c29 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -1,5 +1,4 @@ import { getKzg } from '@aztec/blob-lib'; -import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; import type { EpochCache } from '@aztec/epoch-cache'; import { NoCommitteeError, type RollupContract, SimulationOverridesBuilder } from '@aztec/ethereum/contracts'; import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types'; @@ -25,7 +24,6 @@ import { import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging'; import type { CoordinationSignatureContext } from '@aztec/stdlib/p2p'; import { pickFromSchema } from '@aztec/stdlib/schemas'; -import { MerkleTreeId } from '@aztec/stdlib/trees'; import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client'; import { FullNodeCheckpointsBuilder, NodeKeystoreAdapter, type ValidatorClient } from '@aztec/validator-client'; @@ -615,23 +613,12 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter TypedEventEmitter ({ - data: obj.data, - checkpoint: obj.checkpoint, - l1: obj.l1, - attestations: obj.attestations, - })); diff --git a/yarn-project/stdlib/src/block/block_parameter.test.ts b/yarn-project/stdlib/src/block/block_parameter.test.ts new file mode 100644 index 000000000000..084b1a1ba1ab --- /dev/null +++ b/yarn-project/stdlib/src/block/block_parameter.test.ts @@ -0,0 +1,49 @@ +import { BlockNumber } from '@aztec/foundation/branded-types'; +import { Fr } from '@aztec/foundation/curves/bn254'; + +import { BlockHash } from './block_hash.js'; +import { type BlockParameter, BlockParameterSchema } from './block_parameter.js'; + +describe('BlockParameterSchema', () => { + it.each<[string, BlockParameter]>([ + ['number', BlockNumber(7)], + ['BlockHash', BlockHash.fromBuffer(Buffer.alloc(32, 1))], + ['tag latest', 'latest'], + ['tag proposed', 'proposed'], + ['tag checkpointed', 'checkpointed'], + ['tag proven', 'proven'], + ['tag finalized', 'finalized'], + ['{ number }', { number: BlockNumber(7) }], + ['{ hash }', { hash: BlockHash.fromBuffer(Buffer.alloc(32, 1)) }], + ['{ archive }', { archive: new Fr(123) }], + ['{ tag }', { tag: 'proven' }], + ])('roundtrips %s', (_, param) => { + const json = JSON.parse(JSON.stringify(param)); + const parsed = BlockParameterSchema.parse(json); + expect(parsed).toEqual(param); + }); + + it('parses a 32-byte hex string as a BlockHash, never coercing it to a JS number', () => { + const blockHash = BlockHash.fromBuffer(Buffer.alloc(32, 0x07)); + const wire = blockHash.toString(); + const parsed = BlockParameterSchema.parse(wire); + expect(BlockHash.isBlockHash(parsed)).toBe(true); + expect((parsed as BlockHash).toString()).toEqual(wire); + }); + + it('rejects huge JS numbers (above MAX_SAFE_INTEGER) for block-number parsing', () => { + expect(BlockParameterSchema.safeParse(Number.MAX_SAFE_INTEGER + 1).success).toBe(false); + }); + + it('rejects negative numbers', () => { + expect(BlockParameterSchema.safeParse(-1).success).toBe(false); + }); + + it('rejects non-integer numbers', () => { + expect(BlockParameterSchema.safeParse(1.5).success).toBe(false); + }); + + it('rejects unknown tags', () => { + expect(BlockParameterSchema.safeParse('not-a-tag').success).toBe(false); + }); +}); diff --git a/yarn-project/stdlib/src/block/block_parameter.ts b/yarn-project/stdlib/src/block/block_parameter.ts index 686fd3abeaa4..da4940895137 100644 --- a/yarn-project/stdlib/src/block/block_parameter.ts +++ b/yarn-project/stdlib/src/block/block_parameter.ts @@ -1,31 +1,64 @@ -import { BlockNumberSchema } from '@aztec/foundation/branded-types'; +import { type BlockNumber, BlockNumberSchema } from '@aztec/foundation/branded-types'; +import type { Fr } from '@aztec/foundation/curves/bn254'; import { jsonStringify } from '@aztec/foundation/json-rpc'; import { schemas } from '@aztec/foundation/schemas'; import { z } from 'zod'; -import { ChainTipSchema } from '../interfaces/chain_tips.js'; import { BlockHash } from './block_hash.js'; +export const BlockTag = ['latest', 'proposed', 'checkpointed', 'proven', 'finalized'] as const; + +/** + * Tag identifying a block by its position in the chain rather than by an absolute identifier. + * - `latest` / `proposed`: Latest L2 block proposed (not necessarily checkpointed/proven yet). + * - `checkpointed`: Latest L2 block whose enclosing checkpoint has been published on L1. + * - `proven`: Latest L2 block whose enclosing checkpoint has been proven on L1. + * - `finalized`: Latest L2 block whose proving L1 transaction has reached L1 finality. + */ +export type BlockTag = (typeof BlockTag)[number]; + +export const BlockTagWithoutLatestSchema = z.union([ + z.literal('proposed'), + z.literal('checkpointed'), + z.literal('proven'), + z.literal('finalized'), +]); + +export const BlockTagSchema: z.ZodType = z.union([z.literal('latest'), BlockTagWithoutLatestSchema]); + +/** + * Object-only form of {@link BlockParameter}. Used as the building block for {@link BlockQuery}. + */ +export type NormalizedBlockParameter = + | { number: BlockNumber } + | { hash: BlockHash } + | { archive: Fr } + | { tag: Exclude }; + +export const NormalizedBlockParameterSchema: z.ZodType = z.union([ + z.object({ number: BlockNumberSchema }).strict(), + z.object({ hash: BlockHash.schema }).strict(), + z.object({ archive: schemas.Fr }).strict(), + z.object({ tag: BlockTagWithoutLatestSchema }).strict(), +]); + /** * Selector for a block in RPC calls. * * Accepts a block number, a {@link BlockHash}, a chain-tip name (e.g. `'proven'`, `'checkpointed'`), - * `'latest'` (alias for `'proposed'`), or the explicit object variants `{ number }`, `{ hash }`, - * and `{ archive }`. + * `'latest'` (alias for `'proposed'`), or any of the {@link NormalizedBlockParameter} object variants + * (`{ number }`, `{ hash }`, `{ archive }`, `{ tag }`). */ -export const BlockParameterSchema = z.union([ +export type BlockParameter = NormalizedBlockParameter | BlockNumber | BlockHash | BlockTag; + +export const BlockParameterSchema: z.ZodType = z.union([ + NormalizedBlockParameterSchema, BlockHash.schema, + BlockTagSchema, BlockNumberSchema, - ChainTipSchema, - z.literal('latest'), - z.object({ number: BlockNumberSchema }), - z.object({ hash: BlockHash.schema }), - z.object({ archive: schemas.Fr }), ]); -export type BlockParameter = z.infer; - export function inspectBlockParameter(param: BlockParameter) { if (typeof param === 'number') { return param.toString(); @@ -37,6 +70,8 @@ export function inspectBlockParameter(param: BlockParameter) { return `hash=${param.hash.toString()}`; } else if ('archive' in param) { return `archive=${param.archive.toString()}`; + } else if ('tag' in param) { + return `tag=${param.tag}`; } else { return jsonStringify(param); } diff --git a/yarn-project/stdlib/src/block/checkpointed_l2_block.ts b/yarn-project/stdlib/src/block/checkpointed_l2_block.ts deleted file mode 100644 index 3313e4ead84f..000000000000 --- a/yarn-project/stdlib/src/block/checkpointed_l2_block.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Ignoring import issue to fix portable inferred type issue in zod schema -import { CheckpointNumber, CheckpointNumberSchema } from '@aztec/foundation/branded-types'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; -import type { FieldsOf } from '@aztec/foundation/types'; - -import { z } from 'zod'; - -import { L1PublishedData } from '../checkpoint/published_checkpoint.js'; -import { MAX_BLOCK_HASH_STRING_LENGTH, MAX_COMMITTEE_SIZE } from '../deserialization/index.js'; -import { L2Block } from './l2_block.js'; -import { CommitteeAttestation } from './proposal/committee_attestation.js'; - -/** - * Encapsulates an L2 Block along with the checkpoint data associated with it. - */ -export class CheckpointedL2Block { - constructor( - public checkpointNumber: CheckpointNumber, - public block: L2Block, - public l1: L1PublishedData, - public attestations: CommitteeAttestation[], - ) {} - static get schema() { - return z - .object({ - checkpointNumber: CheckpointNumberSchema, - block: L2Block.schema, - l1: L1PublishedData.schema, - attestations: z.array(CommitteeAttestation.schema), - }) - .transform(obj => CheckpointedL2Block.fromFields(obj)); - } - - static fromBuffer(bufferOrReader: Buffer | BufferReader): CheckpointedL2Block { - const reader = BufferReader.asReader(bufferOrReader); - const checkpointNumber = reader.readNumber(); - const block = reader.readObject(L2Block); - const l1BlockNumber = reader.readBigInt(); - const l1BlockHash = reader.readString(MAX_BLOCK_HASH_STRING_LENGTH); - const l1Timestamp = reader.readBigInt(); - const attestations = reader.readVector(CommitteeAttestation, MAX_COMMITTEE_SIZE); - return new CheckpointedL2Block( - CheckpointNumber(checkpointNumber), - block, - new L1PublishedData(l1BlockNumber, l1Timestamp, l1BlockHash), - attestations, - ); - } - - static fromFields(fields: FieldsOf) { - return new CheckpointedL2Block( - CheckpointNumber(fields.checkpointNumber), - fields.block, - fields.l1, - fields.attestations, - ); - } - - public toBuffer(): Buffer { - return serializeToBuffer( - this.checkpointNumber, - this.block, - this.l1.blockNumber, - this.l1.blockHash, - this.l1.timestamp, - this.attestations.length, - this.attestations, - ); - } -} diff --git a/yarn-project/stdlib/src/block/index.ts b/yarn-project/stdlib/src/block/index.ts index ded8ffecdf57..39b0960a7984 100644 --- a/yarn-project/stdlib/src/block/index.ts +++ b/yarn-project/stdlib/src/block/index.ts @@ -6,7 +6,6 @@ export * from './body.js'; export * from './block_parameter.js'; export * from './l2_block_source.js'; export * from './block_hash.js'; -export * from './checkpointed_l2_block.js'; export * from './proposal/index.js'; export * from './validate_block_result.js'; export * from './l2_block_info.js'; diff --git a/yarn-project/stdlib/src/block/l2_block_source.ts b/yarn-project/stdlib/src/block/l2_block_source.ts index 72cbf27e2c5c..7fd024f7e42a 100644 --- a/yarn-project/stdlib/src/block/l2_block_source.ts +++ b/yarn-project/stdlib/src/block/l2_block_source.ts @@ -4,37 +4,57 @@ import { CheckpointNumber, CheckpointNumberSchema, type EpochNumber, + EpochNumberSchema, type SlotNumber, } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { EthAddress } from '@aztec/foundation/eth-address'; +import { schemas } from '@aztec/foundation/schemas'; import type { TypedEventEmitter } from '@aztec/foundation/types'; import { z } from 'zod'; import type { Checkpoint } from '../checkpoint/checkpoint.js'; import type { CheckpointData, CommonCheckpointData, ProposedCheckpointData } from '../checkpoint/checkpoint_data.js'; -import type { L1PublishedData, PublishedCheckpoint } from '../checkpoint/published_checkpoint.js'; +import type { PublishedCheckpoint } from '../checkpoint/published_checkpoint.js'; import type { L1RollupConstants } from '../epoch-helpers/index.js'; import { CheckpointHeader } from '../rollup/checkpoint_header.js'; -import type { BlockHeader } from '../tx/block_header.js'; import type { IndexedTxEffect } from '../tx/indexed_tx_effect.js'; import type { TxHash } from '../tx/tx_hash.js'; import type { TxReceipt } from '../tx/tx_receipt.js'; import type { BlockData } from './block_data.js'; -import type { BlockHash } from './block_hash.js'; -import type { CheckpointedL2Block } from './checkpointed_l2_block.js'; +import { BlockHash } from './block_hash.js'; +import { BlockTagWithoutLatestSchema, type NormalizedBlockParameter } from './block_parameter.js'; import type { L2Block } from './l2_block.js'; -import type { CommitteeAttestation } from './proposal/committee_attestation.js'; import type { ValidateCheckpointNegativeResult, ValidateCheckpointResult } from './validate_block_result.js'; -/** Block metadata plus checkpoint-derived context (L1 publish info, attestations). */ -export type BlockDataWithCheckpointContext = { - data: BlockData; - checkpoint?: CheckpointData; - l1?: L1PublishedData; - attestations: CommitteeAttestation[]; -}; +/** Lookup a single block by block number, hash, archive root, or chain-tip tag. */ +export type BlockQuery = NormalizedBlockParameter; + +/** + * Query a range of blocks by start/limit or by epoch. + * + * The `epoch` variant requires `onlyCheckpointed: true` because epoch boundaries are only + * meaningful for the checkpointed chain — the proposed chain may include uncheckpointed + * blocks past the epoch boundary that callers should not receive. + */ +export type BlocksQuery = + | { from: BlockNumber; limit: number; onlyCheckpointed?: boolean } + | { epoch: EpochNumber; onlyCheckpointed: true }; + +export const BlockQuerySchema: z.ZodType = z.union([ + z.object({ number: BlockNumberSchema }).strict(), + z.object({ hash: BlockHash.schema }).strict(), + z.object({ archive: schemas.Fr }).strict(), + z.object({ tag: BlockTagWithoutLatestSchema }).strict(), +]); + +export const BlocksQuerySchema: z.ZodType = z.union([ + z + .object({ from: BlockNumberSchema, limit: z.number().int().min(1), onlyCheckpointed: z.boolean().optional() }) + .strict(), + z.object({ epoch: EpochNumberSchema, onlyCheckpointed: z.literal(true) }).strict(), +]); /** * Interface of classes allowing for the retrieval of L2 blocks. @@ -58,6 +78,14 @@ export interface L2BlockSource { */ getBlockNumber(): Promise; + /** + * Resolves a {@link BlockQuery} to its concrete L2 block number. + * @param query - Lookup by block number, hash, archive root, or chain-tip tag. + * @returns The block number, or undefined if no block matches the query. + */ + getBlockNumber(query: BlockQuery): Promise; + getBlockNumber(query?: BlockQuery): Promise; + /** * Gets the number of the latest L2 checkpoint processed by the block source implementation. * @returns The number of the latest L2 checkpoint processed by the block source implementation. @@ -83,23 +111,6 @@ export interface L2BlockSource { */ getFinalizedL2BlockNumber(): Promise; - /** - * Gets an l2 block header. - * @param number - The block number to return or 'latest' for the most recent one. - * @returns The requested L2 block header. - */ - getBlockHeader(number: BlockNumber | 'latest'): Promise; - - /** - * Gets a checkpointed L2 block by block number. - * Returns undefined if the block doesn't exist or hasn't been checkpointed yet. - * @param number - The block number to retrieve. - * @returns The requested checkpointed L2 block (or undefined if not found or not checkpointed). - */ - getCheckpointedBlock(number: BlockNumber): Promise; - - getCheckpointedBlocks(from: BlockNumber, limit: number): Promise; - /** * Retrieves a collection of checkpoints. * @param checkpointNumber The first checkpoint to be retrieved. @@ -143,63 +154,6 @@ export interface L2BlockSource { */ getCheckpointNumberBySlot(slot: SlotNumber): Promise; - /** - * Gets block metadata plus checkpoint-derived context (L1 publish info, attestations) - * without deserializing tx bodies. Uses checkpoint-level values when the block is - * checkpointed; otherwise returns `l1: undefined` and empty attestations. - * @param number - The block number to retrieve. - */ - getBlockDataWithCheckpointContext(number: BlockNumber): Promise; - - /** - * Gets a block header by its hash. - * @param blockHash - The block hash to retrieve. - * @returns The requested block header (or undefined if not found). - */ - getBlockHeaderByHash(blockHash: BlockHash): Promise; - - /** - * Gets a block header by its archive root. - * @param archive - The archive root to retrieve. - * @returns The requested block header (or undefined if not found). - */ - getBlockHeaderByArchive(archive: Fr): Promise; - - /** - * Gets block metadata (without tx data) by block number. - * @param number - The block number to retrieve. - * @returns The requested block data (or undefined if not found). - */ - getBlockData(number: BlockNumber): Promise; - - /** - * Gets block metadata (without tx data) by archive root. - * @param archive - The archive root to retrieve. - * @returns The requested block data (or undefined if not found). - */ - getBlockDataByArchive(archive: Fr): Promise; - - /** - * Gets an L2 block by block number. - * @param number - The block number to return. - * @returns The requested L2 block (or undefined if not found). - */ - getL2Block(number: BlockNumber): Promise; - - /** - * Gets an L2 block by its hash. - * @param blockHash - The block hash to retrieve. - * @returns The requested L2 block (or undefined if not found). - */ - getL2BlockByHash(blockHash: BlockHash): Promise; - - /** - * Gets an L2 block by its archive root. - * @param archive - The archive root to retrieve. - * @returns The requested L2 block (or undefined if not found). - */ - getL2BlockByArchive(archive: Fr): Promise; - /** * Gets a tx effect. * @param txHash - The hash of the tx corresponding to the tx effect. @@ -228,13 +182,6 @@ export interface L2BlockSource { */ getSyncedL2EpochNumber(): Promise; - /** - * Returns all checkpointed block headers for a given epoch. - * @dev Use this method only with recent epochs, since it walks the block list backwards. - * @param epochNumber - The epoch number to return headers for. - */ - getCheckpointedBlockHeadersForEpoch(epochNumber: EpochNumber): Promise; - /** * Returns whether the given epoch is completed on L1, based on the current L1 and L2 block numbers. * @param epochNumber - The epoch number to check. @@ -254,6 +201,12 @@ export interface L2BlockSource { /** Returns values for the genesis block */ getGenesisValues(): Promise<{ genesisArchiveRoot: Fr }>; + /** + * Returns the precomputed hash of the genesis block header. Synchronous because the hash + * is derived from the initial block header at construction time and cached by implementers. + */ + getGenesisBlockHash(): BlockHash; + /** Latest synced L1 timestamp. */ getL1Timestamp(): Promise; @@ -278,50 +231,36 @@ export interface L2BlockSource { /** Force a sync. */ syncImmediate(): Promise; - /* Legacy APIS */ - /** - * Gets an l2 block. If a negative number is passed, the block returned is the most recent. - * @param number - The block number to return (inclusive). - * @returns The requested L2 block. + * Gets an L2 block matching the given query. + * @param query - Lookup by block number, hash, or archive root. */ - getBlock(number: BlockNumber): Promise; + getBlock(query: BlockQuery): Promise; /** - * Returns all checkpointed blocks for a given epoch. - * @dev Use this method only with recent epochs, since it walks the block list backwards. - * @param epochNumber - The epoch number to return blocks for. - */ - getCheckpointedBlocksForEpoch(epochNumber: EpochNumber): Promise; - - /** - * Returns all blocks for a given slot. - * @dev Use this method only with recent slots, since it walks the block list backwards. - * @param slotNumber - The slot number to return blocks for. + * Gets a collection of L2 blocks matching the given query. + * @param query - Range by start/limit or by epoch; optionally restricted to checkpointed blocks. */ - getBlocksForSlot(slotNumber: SlotNumber): Promise; + getBlocks(query: BlocksQuery): Promise; /** - * Gets a checkpointed block by its block hash. - * @param blockHash - The block hash to retrieve. - * @returns The requested block (or undefined if not found). + * Gets block metadata (without tx data) matching the given query. + * @param query - Lookup by block number, hash, or archive root. */ - getCheckpointedBlockByHash(blockHash: BlockHash): Promise; + getBlockData(query: BlockQuery): Promise; /** - * Gets a checkpointed block by its archive root. - * @param archive - The archive root to retrieve. - * @returns The requested block (or undefined if not found). + * Gets a collection of block metadata entries matching the given query. + * @param query - Range by start/limit or by epoch; optionally restricted to checkpointed blocks. */ - getCheckpointedBlockByArchive(archive: Fr): Promise; + getBlocksData(query: BlocksQuery): Promise; /** - * Gets up to `limit` amount of L2 blocks starting from `from`. - * @param from - Number of the first block to return (inclusive). - * @param limit - The maximum number of blocks to return. - * @returns The requested L2 blocks. + * Returns all blocks for a given slot. + * @dev Use this method only with recent slots, since it walks the block list backwards. + * @param slotNumber - The slot number to return blocks for. */ - getBlocks(from: BlockNumber, limit: number): Promise; + getBlocksForSlot(slotNumber: SlotNumber): Promise; } /** @@ -352,11 +291,18 @@ export interface L2BlockSourceEventEmitter extends L2BlockSource { } /** - * Identifier for L2 block tags. + * Identifier for L2 block tags. Internal counterpart to {@link BlockTag} that exposes + * the additional `proposedCheckpoint` value (used for the optimistic chain tip on the + * archiver side) and omits `latest` (which is an alias for `proposed` accepted only at + * the public RPC surface). + * * - proposed: Latest block proposed on L2. - * - checkpointed: Checkpointed block on L1. - * - proven: Proven block on L1. - * - finalized: Proven block on a finalized L1 block (not implemented, set to proven for now). + * - proposedCheckpoint: Latest block in the most recent proposed checkpoint (archiver-internal). + * - checkpointed: Latest block whose enclosing checkpoint has been published on L1. + * - proven: Latest block whose enclosing checkpoint has been proven on L1. + * - finalized: Latest block whose proving L1 transaction has reached L1 finality. + * + * TODO(palla): Remove `proposedCheckpoint` and unify with `proposed`. */ export type L2BlockTag = 'proposed' | 'proposedCheckpoint' | 'checkpointed' | 'proven' | 'finalized'; diff --git a/yarn-project/stdlib/src/block/l2_block_stream/l2_block_stream.test.ts b/yarn-project/stdlib/src/block/l2_block_stream/l2_block_stream.test.ts index a13ce4b8fa36..26216f7aff2b 100644 --- a/yarn-project/stdlib/src/block/l2_block_stream/l2_block_stream.test.ts +++ b/yarn-project/stdlib/src/block/l2_block_stream/l2_block_stream.test.ts @@ -2,15 +2,22 @@ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import { compactArray } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; +import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; import times from 'lodash.times'; import type { PublishedCheckpoint } from '../../checkpoint/published_checkpoint.js'; import type { BlockHeader } from '../../tx/block_header.js'; +import type { BlockData } from '../block_data.js'; import { BlockHash, GENESIS_BLOCK_HEADER_HASH } from '../block_hash.js'; -import type { CheckpointedL2Block } from '../checkpointed_l2_block.js'; import type { L2Block } from '../l2_block.js'; -import { GENESIS_CHECKPOINT_HEADER_HASH, type L2BlockId, type L2BlockSource, type L2Tips } from '../l2_block_source.js'; +import { + type BlocksQuery, + GENESIS_CHECKPOINT_HEADER_HASH, + type L2BlockId, + type L2BlockSource, + type L2Tips, +} from '../l2_block_source.js'; import type { L2BlockStreamEvent, L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider } from './interfaces.js'; import { L2BlockStream } from './l2_block_stream.js'; import { L2TipsMemoryStore } from './l2_tips_memory_store.js'; @@ -39,11 +46,12 @@ describe('L2BlockStream', () => { hash: () => Promise.resolve(new BlockHash(new Fr(number))), }) as L2Block; - const makeCheckpointedBlock = (number: number, checkpointNum: number): CheckpointedL2Block => + const makeBlockData = (number: number, checkpointNum: number): BlockData => ({ - block: makeBlock(number), - checkpointNumber: checkpointNum, - }) as CheckpointedL2Block; + header: makeHeader(number), + checkpointNumber: CheckpointNumber(checkpointNum), + indexWithinCheckpoint: 0, + }) as unknown as BlockData; const makeHeader = (number: number) => ({ hash: () => Promise.resolve(new BlockHash(new Fr(number))) }) as BlockHeader; @@ -107,27 +115,22 @@ describe('L2BlockStream', () => { beforeEach(() => { blockSource = mock(); - // Archiver returns headers with hashes equal to the block number for simplicity - // Note that we only return block headers for blocks that have not been pruned - blockSource.getBlockHeader.mockImplementation(number => - Promise.resolve( - typeof number === 'number' && number > latest ? undefined : makeHeader(number === 'latest' ? 1 : number), - ), - ); - // Returns blocks up until what was reported as the latest block (for uncheckpointed blocks) - blockSource.getBlocks.mockImplementation((from, limit) => - Promise.resolve(compactArray(times(limit, i => (from + i > latest ? undefined : makeBlock(from + i))))), + blockSource.getBlocks.mockImplementation((query: BlocksQuery) => + 'from' in query + ? Promise.resolve( + compactArray(times(query.limit, i => (query.from + i > latest ? undefined : makeBlock(query.from + i)))), + ) + : Promise.resolve([]), ); - // Returns checkpointed blocks (for blocks up to checkpointed tip) - blockSource.getCheckpointedBlocks.mockImplementation((from, limit) => - Promise.resolve( - compactArray( - times(limit, i => (from + i > checkpointed ? undefined : makeCheckpointedBlock(from + i, from + i))), - ), - ), - ); + // Returns block data for any known block that has not been pruned. + blockSource.getBlockData.mockImplementation(query => { + if (!('number' in query)) { + return Promise.resolve(undefined); + } + return Promise.resolve(query.number > latest ? undefined : makeBlockData(query.number, query.number)); + }); // Returns published checkpoints - each checkpoint contains just the one block for simplicity // Respects the limit parameter and returns up to `limit` checkpoints @@ -179,7 +182,7 @@ describe('L2BlockStream', () => { localData.proposed.number = BlockNumber(10); await blockStream.work(); - expect(blockSource.getBlocks).toHaveBeenCalledWith(BlockNumber(11), 5); + expect(blockSource.getBlocks).toHaveBeenCalledWith({ from: BlockNumber(11), limit: 5 }); expect(handler.events).toEqual([ { type: 'blocks-added', blocks: times(5, i => makeBlock(i + 11)) }, ] satisfies L2BlockStreamEvent[]); @@ -274,7 +277,8 @@ describe('L2BlockStream', () => { expectBlocksAdded([5]), expectCheckpointed(), ]); - expect(blockSource.getCheckpointedBlocks).toHaveBeenCalledTimes(1); + // 2 calls: one for block 0 in reorg detection (hash compare at genesis), one for block 1 in loop 2. + expect(blockSource.getBlockData).toHaveBeenCalledTimes(2); expect(blockSource.getBlocks).not.toHaveBeenCalled(); }); @@ -294,8 +298,9 @@ describe('L2BlockStream', () => { expectCheckpointed(), expectBlocksAdded([4, 5]), ]); - expect(blockSource.getCheckpointedBlocks).toHaveBeenCalledTimes(1); - expect(blockSource.getBlocks).toHaveBeenCalledWith(BlockNumber(4), 2); + // 2 calls: one for block 0 in reorg detection (hash compare at genesis), one for block 1 in loop 2. + expect(blockSource.getBlockData).toHaveBeenCalledTimes(2); + expect(blockSource.getBlocks).toHaveBeenCalledWith({ from: BlockNumber(4), limit: 2 }); }); it('handles reorg with uncheckpointed reason when pruned to checkpointed tip', async () => { @@ -317,6 +322,36 @@ describe('L2BlockStream', () => { checkpoint: makeCheckpointId(3), }); }); + + it('throws a meaningful error when local and source disagree on the genesis hash', async () => { + // Source advertises blocks 1-3 with the default mock genesis hash (Fr.ZERO). + setRemoteTips(3); + localData.proposed.number = BlockNumber(3); + // Local store disagrees at every height including block 0 (e.g. different genesisTimestamp). + localData.blockHashes[0] = `0xbad0`; + for (let i = 1; i <= 3; i++) { + localData.blockHashes[i] = `0xbad${i}`; + } + + // The reorg-search loop must NOT walk past block 0; it should throw a clear error + // pointing at the genesis-hash mismatch instead of cascading into "block hash not found + // for -1" further down. The error is caught and logged by `work` rather than rethrown, + // so we assert via the logged error and ensure no events were emitted. + const errorSpy = jest.spyOn( + (blockStream as unknown as { log: { error: (...args: any[]) => void } }).log, + 'error', + ); + + await blockStream.work(); + + expect(handler.events).toEqual([]); + expect(errorSpy).toHaveBeenCalledWith( + expect.stringContaining('Error processing block stream'), + expect.objectContaining({ + message: expect.stringContaining('Genesis block hash mismatch'), + }), + ); + }); }); describe('with memory tips store', () => { @@ -376,17 +411,6 @@ describe('L2BlockStream', () => { /** Gets the last block number in a checkpoint */ const getLastBlockInCheckpoint = (checkpointNum: number) => checkpointNum * blocksPerCheckpoint; - /** Makes a block with correct checkpoint info */ - const makeBlockInCheckpoint = (blockNum: number) => { - const checkpointNum = getCheckpointForBlock(blockNum); - const firstBlockInCheckpoint = getFirstBlockInCheckpoint(checkpointNum); - return { - number: BlockNumber(blockNum), - checkpointNumber: CheckpointNumber(checkpointNum), - indexWithinCheckpoint: blockNum - firstBlockInCheckpoint, - } as L2Block; - }; - /** Makes a block with hash method (for use in mocks that need hash) */ const makeBlockInCheckpointWithHash = (blockNum: number) => { const checkpointNum = getCheckpointForBlock(blockNum); @@ -399,12 +423,13 @@ describe('L2BlockStream', () => { } as L2Block; }; - /** Makes a checkpointed block */ - const makeCheckpointedBlockInCheckpoint = (blockNum: number): CheckpointedL2Block => + /** Makes block data for a checkpointed block */ + const makeBlockDataInCheckpoint = (blockNum: number): BlockData => ({ - block: makeBlockInCheckpoint(blockNum), - checkpointNumber: getCheckpointForBlock(blockNum), - }) as CheckpointedL2Block; + header: makeHeader(blockNum), + checkpointNumber: CheckpointNumber(getCheckpointForBlock(blockNum)), + indexWithinCheckpoint: blockNum - getFirstBlockInCheckpoint(getCheckpointForBlock(blockNum)), + }) as unknown as BlockData; /** Sets the remote tips with correct checkpoint numbers for multi-block checkpoints. */ const setRemoteTipsMultiBlock = ( @@ -458,14 +483,13 @@ describe('L2BlockStream', () => { handler = new TestL2BlockStreamEventHandler(); blockStream = new TestL2BlockStream(blockSource, localData, handler, undefined, { batchSize: 10 }); - // Override the mocks to support multiple blocks per checkpoint - blockSource.getCheckpointedBlocks.mockImplementation((from, limit) => - Promise.resolve( - compactArray( - times(limit, i => (from + i > checkpointed ? undefined : makeCheckpointedBlockInCheckpoint(from + i))), - ), - ), - ); + // Override the mock to support multiple blocks per checkpoint + blockSource.getBlockData.mockImplementation(query => { + if (!('number' in query)) { + return Promise.resolve(undefined); + } + return Promise.resolve(query.number > latest ? undefined : makeBlockDataInCheckpoint(query.number)); + }); // Returns published checkpoints with multiple blocks each, respecting the limit parameter blockSource.getCheckpoints.mockImplementation((checkpointNumber: CheckpointNumber, limit: number) => { @@ -811,8 +835,30 @@ describe('L2BlockStream', () => { expect(checkpointEvents).toHaveLength(5); }); - it('does not call getCheckpointedBlocks(0) when startingBlock is 0', async () => { - // getCheckpointedBlocks rejects block 0 + it('skips Loop 1 entirely when startingBlock is past the checkpointed tip', async () => { + // proposed=15, checkpointed=9 (ckpt 3 covers blocks 7-9). startingBlock=12 is past the + // checkpointed tip, in the proposed range. Loop 1 must skip without an RPC for block 12. + setRemoteTipsMultiBlock(15, 9); + blockStream = new TestL2BlockStream(blockSource, localData, handler, undefined, { + batchSize: 10, + startingBlock: 12, + }); + + await blockStream.work(); + + // No chain-checkpointed events because startingBlock is past the checkpointed tip. + const checkpointEvents = handler.events.filter(e => e.type === 'chain-checkpointed'); + expect(checkpointEvents).toHaveLength(0); + // Loop 1 must not query block 12 — past-the-tip is decided from sourceTips alone. + const loop1Calls = blockSource.getBlockData.mock.calls.filter( + c => 'number' in c[0] && (c[0] as { number: number }).number === 12, + ); + expect(loop1Calls).toHaveLength(0); + }); + + it('calls getBlockData for block 0 only for reorg detection, not checkpoint lookup, when startingBlock is 0', async () => { + // With startingBlock=0, the stream skips the checkpoint-number lookup (line 121 path) + // so getBlockData is called for block 0 only once: for the genesis reorg detection. setRemoteTipsMultiBlock(15, 15); blockStream = new TestL2BlockStream(blockSource, localData, handler, undefined, { batchSize: 10, @@ -821,8 +867,10 @@ describe('L2BlockStream', () => { await blockStream.work(); - const calls = blockSource.getCheckpointedBlocks.mock.calls; - expect(calls.every(([blockNum]) => blockNum >= 1)).toBe(true); + const calls = blockSource.getBlockData.mock.calls; + const block0Calls = calls.filter(c => 'number' in c[0] && (c[0] as { number: number }).number === 0); + // Only the genesis reorg-detection call — not an additional checkpoint-lookup call. + expect(block0Calls).toHaveLength(1); }); }); @@ -1759,6 +1807,12 @@ class TestL2BlockStream extends L2BlockStream { } class TestL2TipsMemoryStore extends L2TipsMemoryStore { + constructor() { + // initialBlockHash must match the test mock's genesis hash (new Fr(0)) so that + // areBlockHashesEqualAt(0) compares matching values and finds no reorg at genesis. + super(new BlockHash(new Fr(0))); + } + protected override computeBlockHash(block: L2Block): Promise<`0x${string}`> { return Promise.resolve(new Fr(block.number).toString()); } diff --git a/yarn-project/stdlib/src/block/l2_block_stream/l2_block_stream.ts b/yarn-project/stdlib/src/block/l2_block_stream/l2_block_stream.ts index 9aac6c45b143..85bf1961bb77 100644 --- a/yarn-project/stdlib/src/block/l2_block_stream/l2_block_stream.ts +++ b/yarn-project/stdlib/src/block/l2_block_stream/l2_block_stream.ts @@ -17,10 +17,7 @@ export class L2BlockStream { private hasStarted = false; constructor( - private l2BlockSource: Pick< - L2BlockSource, - 'getBlocks' | 'getBlockHeader' | 'getL2Tips' | 'getCheckpoints' | 'getCheckpointedBlocks' - >, + private l2BlockSource: Pick, private localData: L2BlockStreamLocalDataProvider, private handler: L2BlockStreamEventHandler, private readonly log = createLogger('types:block_stream'), @@ -77,6 +74,22 @@ export class L2BlockStream { let latestBlockNumber = localTips.proposed.number; const sourceCache = new BlockHashCache([sourceTips.proposed]); while (!(await this.areBlockHashesEqualAt(latestBlockNumber, { sourceCache }))) { + if (latestBlockNumber === 0) { + // We walked all the way back to genesis and the hashes still differ. This means the + // local store and the source disagree on the genesis block itself — typically because + // they were configured with different `genesisTimestamp`/prefilled state. Continuing + // would underflow into negative block numbers and surface as "block hash not found + // for -1" further down. Fail loudly with a meaningful error instead. + this.log.error(`Genesis block hash mismatch between local store and source`, { + localBlockHash: await this.localData.getL2BlockHash(BlockNumber.ZERO), + sourceBlockHash: sourceCache.get(0) ?? (await this.getBlockHashFromSource(BlockNumber.ZERO)), + }); + throw new Error( + 'Genesis block hash mismatch between local store and source: refusing to walk past block 0. ' + + 'This usually indicates the two sides were configured with different genesis values ' + + '(e.g. genesisTimestamp or prefilled public data).', + ); + } latestBlockNumber--; } @@ -97,8 +110,9 @@ export class L2BlockStream { } // If we are just starting, use the starting block number from the options. - if (latestBlockNumber === 0 && this.opts.startingBlock !== undefined) { - latestBlockNumber = BlockNumber(Math.max(this.opts.startingBlock - 1, 0)); + const startingBlock = this.opts.startingBlock !== undefined ? BlockNumber(this.opts.startingBlock) : undefined; + if (latestBlockNumber === 0 && startingBlock !== undefined) { + latestBlockNumber = BlockNumber(Math.max(startingBlock - 1, 0)); } // Only log this entry once (for sanity) @@ -112,21 +126,18 @@ export class L2BlockStream { // When startingBlock is set, also skip ahead for checkpoints. if ( - this.opts.startingBlock !== undefined && - this.opts.startingBlock >= 1 && + startingBlock !== undefined && + startingBlock >= 1 && nextCheckpointToEmit <= sourceTips.checkpointed.checkpoint.number ) { - const startingBlockCheckpoints = await this.l2BlockSource.getCheckpointedBlocks( - BlockNumber(this.opts.startingBlock), - 1, - ); - if (startingBlockCheckpoints.length > 0) { - nextCheckpointToEmit = CheckpointNumber( - Math.max(nextCheckpointToEmit, startingBlockCheckpoints[0].checkpointNumber), - ); - } else { + if (startingBlock > sourceTips.checkpointed.block.number) { // startingBlock is past all checkpointed blocks; skip Loop 1 entirely. nextCheckpointToEmit = CheckpointNumber(sourceTips.checkpointed.checkpoint.number + 1); + } else { + const startingBlockData = await this.l2BlockSource.getBlockData({ number: startingBlock }); + if (startingBlockData) { + nextCheckpointToEmit = CheckpointNumber(Math.max(nextCheckpointToEmit, startingBlockData.checkpointNumber)); + } } } @@ -184,9 +195,9 @@ export class L2BlockStream { // Find the starting checkpoint number if (nextBlockNumber <= sourceTips.checkpointed.block.number) { - const blocks = await this.l2BlockSource.getCheckpointedBlocks(BlockNumber(nextBlockNumber), 1); - if (blocks.length > 0) { - nextCheckpointNumber = blocks[0].checkpointNumber; + const blockData = await this.l2BlockSource.getBlockData({ number: BlockNumber(nextBlockNumber) }); + if (blockData) { + nextCheckpointNumber = blockData.checkpointNumber; } } @@ -234,7 +245,7 @@ export class L2BlockStream { while (nextBlockNumber <= sourceTips.proposed.number) { const limit = Math.min(this.opts.batchSize ?? 50, sourceTips.proposed.number - nextBlockNumber + 1); this.log.trace(`Requesting blocks from ${nextBlockNumber} limit ${limit}`); - const blocks = await this.l2BlockSource.getBlocks(BlockNumber(nextBlockNumber), BlockNumber(limit)); + const blocks = await this.l2BlockSource.getBlocks({ from: BlockNumber(nextBlockNumber), limit }); if (blocks.length === 0) { break; } @@ -266,9 +277,6 @@ export class L2BlockStream { * @param args - A cache of data already requested from source, to avoid re-requesting it. */ private async areBlockHashesEqualAt(blockNumber: BlockNumber, args: { sourceCache: BlockHashCache }) { - if (blockNumber === 0) { - return true; - } const localBlockHash = await this.localData.getL2BlockHash(blockNumber); if (!localBlockHash && this.opts.skipFinalized) { // Failing to find a block hash when skipping finalized blocks can be highly problematic as we'd potentially need @@ -291,8 +299,8 @@ export class L2BlockStream { private getBlockHashFromSource(blockNumber: BlockNumber) { return this.l2BlockSource - .getBlockHeader(blockNumber) - .then(h => h?.hash()) + .getBlockData({ number: blockNumber }) + .then(d => d?.header.hash()) .then(hash => hash?.toString()); } diff --git a/yarn-project/stdlib/src/block/l2_block_stream/l2_tips_memory_store.ts b/yarn-project/stdlib/src/block/l2_block_stream/l2_tips_memory_store.ts index 81a316b5be58..ab5f9e5f5752 100644 --- a/yarn-project/stdlib/src/block/l2_block_stream/l2_tips_memory_store.ts +++ b/yarn-project/stdlib/src/block/l2_block_stream/l2_tips_memory_store.ts @@ -1,6 +1,7 @@ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import type { PublishedCheckpoint } from '../../checkpoint/published_checkpoint.js'; +import type { BlockHash } from '../block_hash.js'; import type { L2BlockTag } from '../l2_block_source.js'; import { L2TipsStoreBase } from './l2_tips_store_base.js'; @@ -9,6 +10,10 @@ import { L2TipsStoreBase } from './l2_tips_store_base.js'; * @dev Tests in kv-store/src/stores/l2_tips_memory_store.test.ts */ export class L2TipsMemoryStore extends L2TipsStoreBase { + constructor(initialBlockHash: BlockHash) { + super(initialBlockHash); + } + private readonly tips = new Map(); private readonly blockHashes = new Map(); private readonly blockToCheckpoint = new Map(); diff --git a/yarn-project/stdlib/src/block/l2_block_stream/l2_tips_store_base.ts b/yarn-project/stdlib/src/block/l2_block_stream/l2_tips_store_base.ts index 0289670d056e..676e732b665b 100644 --- a/yarn-project/stdlib/src/block/l2_block_stream/l2_tips_store_base.ts +++ b/yarn-project/stdlib/src/block/l2_block_stream/l2_tips_store_base.ts @@ -1,7 +1,7 @@ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import type { PublishedCheckpoint } from '../../checkpoint/published_checkpoint.js'; -import { GENESIS_BLOCK_HEADER_HASH } from '../block_hash.js'; +import type { BlockHash } from '../block_hash.js'; import type { L2Block } from '../l2_block.js'; import { type CheckpointId, @@ -17,6 +17,7 @@ import type { L2BlockStreamEvent, L2BlockStreamEventHandler, L2BlockStreamLocalD * while delegating storage operations to subclasses. */ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider { + constructor(protected readonly initialBlockHash: BlockHash) {} // Abstract storage primitives - subclasses implement these based on their backing store /** Gets the block number for a given tag. */ @@ -60,7 +61,10 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl // Public interface implementation - public getL2BlockHash(number: BlockNumber): Promise { + public async getL2BlockHash(number: BlockNumber): Promise { + if (number === 0) { + return (await this.getStoredBlockHash(number)) ?? this.initialBlockHash.toString(); + } return this.getStoredBlockHash(number); } @@ -123,7 +127,7 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl private async getBlockId(tag: L2BlockTag): Promise { const blockNumber = await this.getTip(tag); if (blockNumber === undefined || blockNumber === 0) { - return { number: BlockNumber.ZERO, hash: GENESIS_BLOCK_HEADER_HASH.toString() }; + return { number: BlockNumber.ZERO, hash: this.initialBlockHash.toString() }; } const blockHash = await this.getStoredBlockHash(blockNumber); if (!blockHash) { @@ -139,7 +143,6 @@ export abstract class L2TipsStoreBase implements L2BlockStreamEventHandler, L2Bl } const checkpointNumber = await this.getCheckpointNumberForBlock(blockNumber); if (checkpointNumber === undefined) { - // No checkpoint associated with this block yet return { number: CheckpointNumber.ZERO, hash: GENESIS_CHECKPOINT_HEADER_HASH.toString() }; } const checkpoint = await this.getCheckpoint(checkpointNumber); diff --git a/yarn-project/stdlib/src/interfaces/archiver.test.ts b/yarn-project/stdlib/src/interfaces/archiver.test.ts index be161a9cfdd4..16df1aa6f3e0 100644 --- a/yarn-project/stdlib/src/interfaces/archiver.test.ts +++ b/yarn-project/stdlib/src/interfaces/archiver.test.ts @@ -9,9 +9,14 @@ import omit from 'lodash.omit'; import type { ContractArtifact } from '../abi/abi.js'; import { FunctionSelector } from '../abi/function_selector.js'; import { AztecAddress } from '../aztec-address/index.js'; -import { CheckpointedL2Block } from '../block/checkpointed_l2_block.js'; import { type BlockData, BlockHash, CommitteeAttestation, L2Block } from '../block/index.js'; -import type { L2Tips } from '../block/l2_block_source.js'; +import { + type BlockQuery, + BlockQuerySchema, + type BlocksQuery, + BlocksQuerySchema, + type L2Tips, +} from '../block/l2_block_source.js'; import type { ValidateCheckpointResult } from '../block/validate_block_result.js'; import { Checkpoint } from '../checkpoint/checkpoint.js'; import type { CheckpointData, ProposedCheckpointData } from '../checkpoint/checkpoint_data.js'; @@ -34,7 +39,6 @@ import { CheckpointHeader } from '../rollup/checkpoint_header.js'; import { randomTxScopedPrivateL2Log } from '../tests/factories.js'; import { getTokenContractArtifact } from '../tests/fixtures.js'; import { AppendOnlyTreeSnapshot } from '../trees/append_only_tree_snapshot.js'; -import { BlockHeader } from '../tx/block_header.js'; import type { IndexedTxEffect } from '../tx/indexed_tx_effect.js'; import { TxEffect } from '../tx/tx_effect.js'; import { TxHash } from '../tx/tx_hash.js'; @@ -103,76 +107,6 @@ describe('ArchiverApiSchema', () => { expect(result).toEqual(BlockNumber(0)); }); - it('getBlock', async () => { - const result = await context.client.getBlock(BlockNumber(1)); - expect(result).toBeInstanceOf(L2Block); - }); - - it('getBlockHeader', async () => { - const result = await context.client.getBlockHeader(BlockNumber(1)); - expect(result).toBeInstanceOf(BlockHeader); - }); - - it('getBlockHeaderByArchive', async () => { - const result = await context.client.getBlockHeaderByArchive(Fr.random()); - expect(result).toBeInstanceOf(BlockHeader); - }); - - it('getBlockData', async () => { - const result = await context.client.getBlockData(BlockNumber(1)); - expect(result).toBeUndefined(); - }); - - it('getBlockDataByArchive', async () => { - const result = await context.client.getBlockDataByArchive(Fr.random()); - expect(result).toBeUndefined(); - }); - - it('getBlockDataWithCheckpointContext', async () => { - const result = await context.client.getBlockDataWithCheckpointContext(BlockNumber(1)); - expect(result).toBeUndefined(); - }); - - it('getCheckpointData', async () => { - const result = await context.client.getCheckpointData(CheckpointNumber(1)); - expect(result).toBeUndefined(); - }); - - it('getCheckpointDataRange', async () => { - const result = await context.client.getCheckpointDataRange(CheckpointNumber(1), 1); - expect(result).toEqual([]); - }); - - it('getCheckpointNumberBySlot', async () => { - const result = await context.client.getCheckpointNumberBySlot(SlotNumber(1)); - expect(result).toBeUndefined(); - }); - - it('getBlockHeaderByHash', async () => { - const result = await context.client.getBlockHeaderByHash(BlockHash.random()); - expect(result).toBeInstanceOf(BlockHeader); - }); - - it('getL2Block', async () => { - const result = await context.client.getL2Block(BlockNumber(1)); - expect(result).toBeInstanceOf(L2Block); - }); - - it('getL2BlockByHash', async () => { - const result = await context.client.getL2BlockByHash(BlockHash.random()); - expect(result).toBeInstanceOf(L2Block); - }); - - it('getL2BlockByArchive', async () => { - const result = await context.client.getL2BlockByArchive(Fr.random()); - expect(result).toBeInstanceOf(L2Block); - }); - - it('getBlocks', async () => { - const result = await context.client.getBlocks(BlockNumber(1), BlockNumber(1)); - expect(result).toEqual([expect.any(L2Block)]); - }); - it('getCheckpoints', async () => { const response = await context.client.getCheckpoints(CheckpointNumber(1), BlockNumber(1)); expect(response).toHaveLength(1); @@ -181,22 +115,6 @@ describe('ArchiverApiSchema', () => { expect(response[0].l1).toBeDefined(); }); - it('getCheckpointedBlockByArchive', async () => { - const result = await context.client.getCheckpointedBlockByArchive(Fr.random()); - expect(result).toBeDefined(); - expect(result!.block.constructor.name).toEqual('L2Block'); - expect(result!.attestations[0]).toBeInstanceOf(CommitteeAttestation); - expect(result!.l1).toBeDefined(); - }); - - it('getCheckpointedBlockByHash', async () => { - const result = await context.client.getCheckpointedBlockByHash(BlockHash.random()); - expect(result).toBeDefined(); - expect(result!.block.constructor.name).toEqual('L2Block'); - expect(result!.attestations[0]).toBeInstanceOf(CommitteeAttestation); - expect(result!.l1).toBeDefined(); - }); - it('getTxEffect', async () => { const result = await context.client.getTxEffect(TxHash.fromBuffer(Buffer.alloc(32, BlockNumber(1)))); expect(result!.data).toBeInstanceOf(TxEffect); @@ -230,38 +148,11 @@ describe('ArchiverApiSchema', () => { expect(result[0].attestations[0]).toBeInstanceOf(CommitteeAttestation); }); - it('getCheckpointedBlock', async () => { - const result = await context.client.getCheckpointedBlock(BlockNumber(1)); - expect(result).toBeDefined(); - expect(result!.block.constructor.name).toEqual('L2Block'); - expect(result!.attestations[0]).toBeInstanceOf(CommitteeAttestation); - expect(result!.l1).toBeDefined(); - }); - - it('getCheckpointedBlocks', async () => { - const result = await context.client.getCheckpointedBlocks(BlockNumber(1), 10); - expect(result).toHaveLength(1); - expect(result[0].block.constructor.name).toEqual('L2Block'); - expect(result[0].attestations[0]).toBeInstanceOf(CommitteeAttestation); - expect(result[0].l1).toBeDefined(); - }); - - it('getCheckpointedBlocksForEpoch', async () => { - const result = await context.client.getCheckpointedBlocksForEpoch(EpochNumber(1)); - expect(result).toHaveLength(1); - expect(result[0]).toBeInstanceOf(CheckpointedL2Block); - }); - it('getBlocksForSlot', async () => { const result = await context.client.getBlocksForSlot(SlotNumber(1)); expect(result).toEqual([expect.any(L2Block)]); }); - it('getCheckpointedBlockHeadersForEpoch', async () => { - const result = await context.client.getCheckpointedBlockHeadersForEpoch(EpochNumber(1)); - expect(result).toEqual([expect.any(BlockHeader)]); - }); - it('isEpochComplete', async () => { const result = await context.client.isEpochComplete(EpochNumber(1)); expect(result).toBe(true); @@ -416,14 +307,94 @@ describe('ArchiverApiSchema', () => { expect(result).toBe(false); }); + it('getCheckpointData', async () => { + const result = await context.client.getCheckpointData(CheckpointNumber(1)); + expect(result).toBeUndefined(); + }); + + it('getCheckpointDataRange', async () => { + const result = await context.client.getCheckpointDataRange(CheckpointNumber(1), 10); + expect(result).toEqual([]); + }); + + it('getCheckpointNumberBySlot', async () => { + const result = await context.client.getCheckpointNumberBySlot(SlotNumber(1)); + expect(result).toBeUndefined(); + }); + it('getGenesisValues', async () => { const result = await context.client.getGenesisValues(); expect(result).toEqual({ genesisArchiveRoot: expect.any(Fr) }); }); - it('getL2Block', async () => { - const result = await context.client.getL2Block(BlockNumber(1)); - expect(result).toEqual(expect.any(L2Block)); + it('getBlock', async () => { + const result = await context.client.getBlock({ number: BlockNumber(1) }); + expect(result).toBeInstanceOf(L2Block); + }); + + it('getBlocks', async () => { + const result = await context.client.getBlocks({ from: BlockNumber(1), limit: 1 }); + expect(result).toEqual([expect.any(L2Block)]); + }); + + it('getBlockData', async () => { + const result = await context.client.getBlockData({ number: BlockNumber(1) }); + expect(result).toBeUndefined(); + }); + + it('getBlocksData', async () => { + const result = await context.client.getBlocksData({ from: BlockNumber(1), limit: 1 }); + expect(result).toEqual([]); + }); +}); + +describe('BlockQuerySchema', () => { + it.each<[string, BlockQuery]>([ + ['{ number }', { number: BlockNumber(1) }], + ['{ hash }', { hash: BlockHash.fromBuffer(Buffer.alloc(32, 1)) }], + ['{ archive }', { archive: new Fr(123) }], + ['{ tag: proposed }', { tag: 'proposed' }], + ['{ tag: checkpointed }', { tag: 'checkpointed' }], + ['{ tag: proven }', { tag: 'proven' }], + ['{ tag: finalized }', { tag: 'finalized' }], + ])('roundtrips %s', (_, query) => { + const json = JSON.parse(JSON.stringify(query)); + const parsed = BlockQuerySchema.parse(json); + expect(parsed).toEqual(query); + }); + + it('rejects mixed-key inputs', () => { + expect(BlockQuerySchema.safeParse({ number: 1, tag: 'proven' }).success).toBe(false); + expect(BlockQuerySchema.safeParse({ hash: '0x1', archive: '0x2' }).success).toBe(false); + }); + + it('rejects extra keys (onlyCheckpointed is plural-only)', () => { + expect(BlockQuerySchema.safeParse({ number: 1, onlyCheckpointed: true }).success).toBe(false); + expect(BlockQuerySchema.safeParse({ tag: 'checkpointed', onlyCheckpointed: true }).success).toBe(false); + }); +}); + +describe('BlocksQuerySchema', () => { + it.each<[string, BlocksQuery]>([ + ['{ from, limit }', { from: BlockNumber(1), limit: 10 }], + ['{ from, limit, onlyCheckpointed }', { from: BlockNumber(1), limit: 10, onlyCheckpointed: true }], + ['{ epoch, onlyCheckpointed: true }', { epoch: EpochNumber(5), onlyCheckpointed: true }], + ])('roundtrips %s', (_, query) => { + const json = JSON.parse(JSON.stringify(query)); + const parsed = BlocksQuerySchema.parse(json); + expect(parsed).toEqual(query); + }); + + it('rejects mixed-key inputs', () => { + expect(BlocksQuerySchema.safeParse({ from: 0, limit: 10, epoch: 5 }).success).toBe(false); + }); + + it('rejects epoch query without onlyCheckpointed', () => { + expect(BlocksQuerySchema.safeParse({ epoch: 1 }).success).toBe(false); + }); + + it('rejects epoch query with onlyCheckpointed: false', () => { + expect(BlocksQuerySchema.safeParse({ epoch: 1, onlyCheckpointed: false }).success).toBe(false); }); }); @@ -463,7 +434,9 @@ class MockArchiver implements ArchiverApi { getRegistryAddress(): Promise { return Promise.resolve(EthAddress.random()); } - getBlockNumber(): Promise { + getBlockNumber(): Promise; + getBlockNumber(query: BlockQuery): Promise; + getBlockNumber(_query?: BlockQuery): Promise { return Promise.resolve(BlockNumber(1)); } getProvenBlockNumber(): Promise { @@ -478,34 +451,17 @@ class MockArchiver implements ArchiverApi { getFinalizedL2BlockNumber(): Promise { return Promise.resolve(BlockNumber(0)); } - getBlock(number: BlockNumber): Promise { - return L2Block.random(number); - } - getBlockHeader(_number: BlockNumber | 'latest'): Promise { - return Promise.resolve(BlockHeader.empty()); + getBlock(_query: BlockQuery): Promise { + return L2Block.random(BlockNumber(1)); } - async getCheckpointedBlock(number: BlockNumber): Promise { - return Promise.resolve( - CheckpointedL2Block.fromFields({ - checkpointNumber: CheckpointNumber(1), - block: await L2Block.random(number), - attestations: [CommitteeAttestation.random()], - l1: new L1PublishedData(1n, 0n, `0x`), - }), - ); + async getBlocks(_query: BlocksQuery): Promise { + return [await L2Block.random(BlockNumber(1))]; } - async getCheckpointedBlocks(from: BlockNumber, _limit: number): Promise { - return [ - CheckpointedL2Block.fromFields({ - checkpointNumber: CheckpointNumber(1), - block: await L2Block.random(from), - attestations: [CommitteeAttestation.random()], - l1: new L1PublishedData(1n, 0n, `0x`), - }), - ]; + getBlockData(_query: BlockQuery): Promise { + return Promise.resolve(undefined); } - async getBlocks(from: BlockNumber, _limit: number): Promise { - return [await L2Block.random(from)]; + getBlocksData(_query: BlocksQuery): Promise { + return Promise.resolve([]); } async getCheckpoints(from: CheckpointNumber, _limit: number): Promise { return [ @@ -516,47 +472,6 @@ class MockArchiver implements ArchiverApi { }), ]; } - getCheckpointByArchive(_archive: Fr): Promise { - return Promise.resolve(Checkpoint.random()); - } - - async getCheckpointedBlockByHash(_blockHash: BlockHash): Promise { - return CheckpointedL2Block.fromFields({ - checkpointNumber: CheckpointNumber(1), - block: await L2Block.random(BlockNumber(1)), - attestations: [CommitteeAttestation.random()], - l1: new L1PublishedData(1n, 0n, `0x`), - }); - } - async getCheckpointedBlockByArchive(_archive: Fr): Promise { - return CheckpointedL2Block.fromFields({ - checkpointNumber: CheckpointNumber(1), - block: await L2Block.random(BlockNumber(1)), - attestations: [CommitteeAttestation.random()], - l1: new L1PublishedData(1n, 0n, `0x`), - }); - } - getBlockHeaderByHash(_blockHash: BlockHash): Promise { - return Promise.resolve(BlockHeader.empty()); - } - getBlockHeaderByArchive(_archive: Fr): Promise { - return Promise.resolve(BlockHeader.empty()); - } - getBlockData(_number: BlockNumber): Promise { - return Promise.resolve(undefined); - } - getBlockDataByArchive(_archive: Fr): Promise { - return Promise.resolve(undefined); - } - getL2Block(number: BlockNumber): Promise { - return L2Block.random(number); - } - getL2BlockByHash(_blockHash: BlockHash): Promise { - return L2Block.random(BlockNumber(1)); - } - getL2BlockByArchive(_archive: Fr): Promise { - return L2Block.random(BlockNumber(1)); - } async getTxEffect(_txHash: TxHash): Promise { expect(_txHash).toBeInstanceOf(TxHash); return { @@ -606,30 +521,10 @@ class MockArchiver implements ArchiverApi { getCheckpointNumberBySlot(_slot: SlotNumber): Promise { return Promise.resolve(undefined); } - getBlockDataWithCheckpointContext(_n: BlockNumber) { - return Promise.resolve(undefined); - } - async getCheckpointedBlocksForEpoch(epochNumber: EpochNumber): Promise { - expect(epochNumber).toEqual(EpochNumber(1)); - const block = await L2Block.random(BlockNumber(Number(epochNumber))); - return [ - CheckpointedL2Block.fromFields({ - checkpointNumber: CheckpointNumber(1), - block, - l1: new L1PublishedData(1n, 1n, `0x01`), - attestations: [CommitteeAttestation.random()], - }), - ]; - } async getBlocksForSlot(slotNumber: SlotNumber): Promise { expect(slotNumber).toEqual(SlotNumber(1)); return [await L2Block.random(BlockNumber(Number(slotNumber)))]; } - async getCheckpointedBlockHeadersForEpoch(epochNumber: EpochNumber): Promise { - expect(epochNumber).toEqual(EpochNumber(1)); - const block = await L2Block.random(BlockNumber(Number(epochNumber))); - return [block.header]; - } isEpochComplete(epochNumber: EpochNumber): Promise { expect(epochNumber).toEqual(EpochNumber(1)); return Promise.resolve(true); diff --git a/yarn-project/stdlib/src/interfaces/archiver.ts b/yarn-project/stdlib/src/interfaces/archiver.ts index 2fb4049ca4da..9809a2d1af15 100644 --- a/yarn-project/stdlib/src/interfaces/archiver.ts +++ b/yarn-project/stdlib/src/interfaces/archiver.ts @@ -4,11 +4,9 @@ import type { ApiSchemaFor } from '@aztec/foundation/schemas'; import { z } from 'zod'; -import { BlockDataSchema, BlockDataWithCheckpointContextSchema } from '../block/block_data.js'; -import { BlockHash } from '../block/block_hash.js'; -import { CheckpointedL2Block } from '../block/checkpointed_l2_block.js'; +import { BlockDataSchema } from '../block/block_data.js'; import { L2Block } from '../block/l2_block.js'; -import { type L2BlockSource, L2TipsSchema } from '../block/l2_block_source.js'; +import { BlockQuerySchema, BlocksQuerySchema, type L2BlockSource, L2TipsSchema } from '../block/l2_block_source.js'; import { ValidateCheckpointResultSchema } from '../block/validate_block_result.js'; import { Checkpoint } from '../checkpoint/checkpoint.js'; import { CheckpointDataSchema, ProposedCheckpointDataSchema } from '../checkpoint/checkpoint_data.js'; @@ -25,7 +23,6 @@ import { Tag } from '../logs/tag.js'; import { TxScopedL2Log } from '../logs/tx_scoped_l2_log.js'; import type { L1ToL2MessageSource } from '../messaging/l1_to_l2_message_source.js'; import { optional, schemas } from '../schemas/schemas.js'; -import { BlockHeader } from '../tx/block_header.js'; import { indexedTxSchema } from '../tx/indexed_tx_effect.js'; import { TxHash } from '../tx/tx_hash.js'; import { TxReceipt } from '../tx/tx_receipt.js'; @@ -85,60 +82,34 @@ export const ArchiverSpecificConfigSchema = z.object({ export type ArchiverApi = Omit< L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource, - 'start' | 'stop' + 'start' | 'stop' | 'getGenesisBlockHash' >; export const ArchiverApiSchema: ApiSchemaFor = { getRollupAddress: z.function().args().returns(schemas.EthAddress), getRegistryAddress: z.function().args().returns(schemas.EthAddress), - getBlockNumber: z.function().args().returns(BlockNumberSchema), + getBlockNumber: z.function().args(optional(BlockQuerySchema)).returns(BlockNumberSchema.optional()), getProvenBlockNumber: z.function().args().returns(BlockNumberSchema), getCheckpointedL2BlockNumber: z.function().args().returns(BlockNumberSchema), getCheckpointNumber: z.function().args().returns(CheckpointNumberSchema), getFinalizedL2BlockNumber: z.function().args().returns(BlockNumberSchema), - getBlock: z.function().args(BlockNumberSchema).returns(L2Block.schema.optional()), - getBlockHeader: z - .function() - .args(z.union([BlockNumberSchema, z.literal('latest')])) - .returns(BlockHeader.schema.optional()), - getCheckpointedBlock: z.function().args(BlockNumberSchema).returns(CheckpointedL2Block.schema.optional()), - getCheckpointedBlocks: z - .function() - .args(BlockNumberSchema, schemas.Integer) - .returns(z.array(CheckpointedL2Block.schema)), - getBlocks: z.function().args(BlockNumberSchema, schemas.Integer).returns(z.array(L2Block.schema)), getCheckpoints: z .function() .args(CheckpointNumberSchema, schemas.Integer) .returns(z.array(PublishedCheckpoint.schema)), - getCheckpointedBlockByHash: z.function().args(BlockHash.schema).returns(CheckpointedL2Block.schema.optional()), - getCheckpointedBlockByArchive: z.function().args(schemas.Fr).returns(CheckpointedL2Block.schema.optional()), - getBlockHeaderByHash: z.function().args(BlockHash.schema).returns(BlockHeader.schema.optional()), - getBlockHeaderByArchive: z.function().args(schemas.Fr).returns(BlockHeader.schema.optional()), - getBlockData: z.function().args(BlockNumberSchema).returns(BlockDataSchema.optional()), - getBlockDataByArchive: z.function().args(schemas.Fr).returns(BlockDataSchema.optional()), - getBlockDataWithCheckpointContext: z - .function() - .args(BlockNumberSchema) - .returns(BlockDataWithCheckpointContextSchema.optional()), getCheckpointData: z.function().args(CheckpointNumberSchema).returns(CheckpointDataSchema.optional()), getCheckpointDataRange: z .function() .args(CheckpointNumberSchema, schemas.Integer) .returns(z.array(CheckpointDataSchema)), getCheckpointNumberBySlot: z.function().args(schemas.SlotNumber).returns(CheckpointNumberSchema.optional()), - getL2Block: z.function().args(BlockNumberSchema).returns(L2Block.schema.optional()), - getL2BlockByHash: z.function().args(BlockHash.schema).returns(L2Block.schema.optional()), - getL2BlockByArchive: z.function().args(schemas.Fr).returns(L2Block.schema.optional()), getTxEffect: z.function().args(TxHash.schema).returns(indexedTxSchema().optional()), getSettledTxReceipt: z.function().args(TxHash.schema).returns(TxReceipt.schema.optional()), getSyncedL2SlotNumber: z.function().args().returns(schemas.SlotNumber.optional()), getSyncedL2EpochNumber: z.function().args().returns(EpochNumberSchema.optional()), getCheckpointsForEpoch: z.function().args(EpochNumberSchema).returns(z.array(Checkpoint.schema)), getCheckpointsDataForEpoch: z.function().args(EpochNumberSchema).returns(z.array(CheckpointDataSchema)), - getCheckpointedBlocksForEpoch: z.function().args(EpochNumberSchema).returns(z.array(CheckpointedL2Block.schema)), getBlocksForSlot: z.function().args(schemas.SlotNumber).returns(z.array(L2Block.schema)), - getCheckpointedBlockHeadersForEpoch: z.function().args(EpochNumberSchema).returns(z.array(BlockHeader.schema)), isEpochComplete: z.function().args(EpochNumberSchema).returns(z.boolean()), getL2Tips: z.function().args().returns(L2TipsSchema), getPrivateLogsByTags: z @@ -173,4 +144,8 @@ export const ArchiverApiSchema: ApiSchemaFor = { syncImmediate: z.function().args().returns(z.void()), isPendingChainInvalid: z.function().args().returns(z.boolean()), getPendingChainValidationStatus: z.function().args().returns(ValidateCheckpointResultSchema), + getBlock: z.function().args(BlockQuerySchema).returns(L2Block.schema.optional()), + getBlocks: z.function().args(BlocksQuerySchema).returns(z.array(L2Block.schema)), + getBlockData: z.function().args(BlockQuerySchema).returns(BlockDataSchema.optional()), + getBlocksData: z.function().args(BlocksQuerySchema).returns(z.array(BlockDataSchema)), }; diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts index f648ae991c47..dd3cdc3fab00 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts @@ -14,7 +14,7 @@ import times from 'lodash.times'; import type { ContractArtifact } from '../abi/abi.js'; import { AztecAddress } from '../aztec-address/index.js'; import type { DataInBlock } from '../block/in_block.js'; -import { BlockHash, type BlockParameter, type CheckpointedL2Block } from '../block/index.js'; +import { BlockHash, type BlockParameter } from '../block/index.js'; import type { L2Tips } from '../block/l2_block_source.js'; import { type ContractClassPublic, @@ -75,7 +75,7 @@ describe('AztecNodeApiSchema', () => { }); afterEach(() => { - tested.add(/^AztecNodeApiSchema\s+([^(]+)/.exec(expect.getState().currentTestName!)![1]); + tested.add(/^AztecNodeApiSchema\s+([^(]+?)\s*(\(|$)/.exec(expect.getState().currentTestName!)![1]); context.httpServer.close(); }); @@ -98,6 +98,21 @@ describe('AztecNodeApiSchema', () => { }); }); + it('getL2Tips', async () => { + const result = await context.client.getL2Tips(); + const expectedTipId = { + block: { number: 1, hash: `0x01` }, + checkpoint: { number: 1, hash: `0x01` }, + }; + expect(result).toEqual({ + proposed: { number: 1, hash: `0x01` }, + checkpointed: expectedTipId, + proposedCheckpoint: expectedTipId, + proven: expectedTipId, + finalized: expectedTipId, + }); + }); + it('findLeavesIndexes', async () => { const response = await context.client.findLeavesIndexes(BlockNumber(1), MerkleTreeId.ARCHIVE, [ Fr.random(), @@ -166,29 +181,14 @@ describe('AztecNodeApiSchema', () => { it('getBlockHeader', async () => { const response = await context.client.getBlockHeader(BlockNumber(1)); - expect(response).toEqual(BlockHeader.empty()); + expect(response).toBeInstanceOf(BlockHeader); }); it('getCheckpointedBlocks', async () => { - const response = await context.client.getCheckpointedBlocks(BlockNumber(1), 1); + const response = await context.client.getCheckpointedBlocks(BlockNumber(1), 10); expect(response).toEqual([]); }); - it('getL2Tips', async () => { - const response = await context.client.getL2Tips(); - const tipId = { - block: { number: 1, hash: `0x01` }, - checkpoint: { number: 1, hash: `0x01` }, - }; - expect(response).toEqual({ - proposed: { number: 1, hash: `0x01` }, - checkpointed: tipId, - proposedCheckpoint: tipId, - proven: tipId, - finalized: tipId, - }); - }); - it('getCheckpoint', async () => { const response = await context.client.getCheckpoint(CheckpointNumber(1)); expect(response).toBeUndefined(); @@ -347,6 +347,25 @@ describe('AztecNodeApiSchema', () => { expect(response).toBeInstanceOf(Fr); }); + it.each<[string, BlockParameter]>([ + ['BlockNumber', BlockNumber(7)], + ['BlockHash', new BlockHash(new Fr(0x1234))], + ['{ archive }', { archive: new Fr(0x5678) }], + ['tag latest', 'latest'], + ['tag proven', 'proven'], + ])('getPublicStorageAt (round-trips %s)', async (_, block) => { + handler.lastReferenceBlock = undefined; + await context.client.getPublicStorageAt(block, await AztecAddress.random(), Fr.random()); + if (typeof block === 'object' && 'archive' in block) { + expect(handler.lastReferenceBlock).toEqual({ archive: expect.any(Fr) }); + } else if (BlockHash.isBlockHash(block)) { + expect(BlockHash.isBlockHash(handler.lastReferenceBlock)).toBe(true); + expect((handler.lastReferenceBlock as unknown as BlockHash).toString()).toEqual(block.toString()); + } else { + expect(handler.lastReferenceBlock).toEqual(block); + } + }); + it('getValidatorsStats', async () => { handler.validatorStats = { stats: { @@ -502,6 +521,7 @@ describe('AztecNodeApiSchema', () => { class MockAztecNode implements AztecNode { public validatorStats: ValidatorsStats | undefined; public singleValidatorStats: SingleValidatorStats | undefined; + public lastReferenceBlock: BlockParameter | undefined; constructor(private artifact: ContractArtifact) {} @@ -580,7 +600,7 @@ class MockAztecNode implements AztecNode { return Promise.resolve(BlockHeader.empty()); } - getCheckpointedBlocks(_from: BlockNumber, _limit: number): Promise { + getCheckpointedBlocks(_from: BlockNumber, _limit: number): Promise { return Promise.resolve([]); } @@ -790,9 +810,15 @@ class MockAztecNode implements AztecNode { return Promise.resolve([Tx.random()]); } getPublicStorageAt(block: BlockParameter, contract: AztecAddress, slot: Fr): Promise { - expect(block === 'latest' || block instanceof Fr || typeof block === 'number').toBe(true); + expect( + typeof block === 'number' || + typeof block === 'string' || + BlockHash.isBlockHash(block) || + (typeof block === 'object' && block !== null), + ).toBe(true); expect(contract).toBeInstanceOf(AztecAddress); expect(slot).toBeInstanceOf(Fr); + this.lastReferenceBlock = block; return Promise.resolve(Fr.random()); } getValidatorsStats(): Promise { diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.ts b/yarn-project/stdlib/src/interfaces/aztec-node.ts index 2d0660fe3183..4e71f392d04a 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.ts @@ -21,7 +21,6 @@ import { z } from 'zod'; import type { AztecAddress } from '../aztec-address/index.js'; import { BlockHash } from '../block/block_hash.js'; import { type BlockParameter, BlockParameterSchema } from '../block/block_parameter.js'; -import { CheckpointedL2Block } from '../block/checkpointed_l2_block.js'; import { type DataInBlock, dataInBlockSchemaFor } from '../block/in_block.js'; import { type L2Tips, L2TipsSchema } from '../block/l2_block_source.js'; import { type CheckpointData, CheckpointDataSchema } from '../checkpoint/checkpoint_data.js'; @@ -227,7 +226,7 @@ export interface AztecNode { /** @deprecated Scheduled for removal; use `getBlock(param).then(r => r?.header)`. */ getBlockHeader(number: BlockNumber | 'latest'): Promise; /** @deprecated Scheduled for removal; use `getBlocks(from, limit, { includeL1PublishInfo: true, includeAttestations: true })`. */ - getCheckpointedBlocks(from: BlockNumber, limit: number): Promise; + getCheckpointedBlocks(from: BlockNumber, limit: number): Promise; /** @deprecated Scheduled for removal; use `getCheckpoints(from, limit)` over an explicit checkpoint range. */ getCheckpointsDataForEpoch(epoch: EpochNumber): Promise; @@ -568,7 +567,7 @@ export const AztecNodeApiSchema: ApiSchemaFor = { getCheckpointedBlocks: z .function() .args(BlockNumberPositiveSchema, z.number().gt(0).lte(MAX_RPC_BLOCKS_LEN)) - .returns(z.array(CheckpointedL2Block.schema)), + .returns(z.array(BlockResponseSchema)), getCheckpointsDataForEpoch: z.function().args(EpochNumberSchema).returns(z.array(CheckpointDataSchema)), diff --git a/yarn-project/stdlib/src/interfaces/checkpoint_parameter.ts b/yarn-project/stdlib/src/interfaces/checkpoint_parameter.ts index d6244456febe..36725a304ecc 100644 --- a/yarn-project/stdlib/src/interfaces/checkpoint_parameter.ts +++ b/yarn-project/stdlib/src/interfaces/checkpoint_parameter.ts @@ -12,11 +12,11 @@ import { ChainTipSchema } from './chain_tips.js'; * means the most recent confirmed checkpoint). */ export const CheckpointParameterSchema = z.union([ - CheckpointNumberSchema, + z.object({ number: CheckpointNumberSchema }).strict(), + z.object({ slot: SlotNumberSchema }).strict(), ChainTipSchema, z.literal('latest'), - z.object({ number: CheckpointNumberSchema }), - z.object({ slot: SlotNumberSchema }), + CheckpointNumberSchema, ]); export type CheckpointParameter = z.infer; diff --git a/yarn-project/stdlib/src/tests/mocks.ts b/yarn-project/stdlib/src/tests/mocks.ts index 39439d46f136..0c80d6bc6d1e 100644 --- a/yarn-project/stdlib/src/tests/mocks.ts +++ b/yarn-project/stdlib/src/tests/mocks.ts @@ -26,10 +26,9 @@ import { AvmCircuitPublicInputs } from '../avm/avm_circuit_public_inputs.js'; import { PublicDataWrite } from '../avm/public_data_write.js'; import { RevertCode } from '../avm/revert_code.js'; import { AztecAddress } from '../aztec-address/index.js'; -import { CheckpointedL2Block, CommitteeAttestation, L2Block } from '../block/index.js'; +import { L2Block } from '../block/index.js'; import type { CommitteeAttestationsAndSigners } from '../block/proposal/attestations_and_signers.js'; import { Checkpoint } from '../checkpoint/checkpoint.js'; -import { L1PublishedData } from '../checkpoint/published_checkpoint.js'; import { computeContractAddressFromInstance } from '../contract/contract_address.js'; import { getContractClassFromArtifact } from '../contract/contract_class.js'; import { SerializableContractInstance } from '../contract/contract_instance.js'; @@ -746,32 +745,3 @@ export const makeCheckpointAttestationFromBlock = ( return makeCheckpointAttestation({ header, archive, attesterSigner, proposerSigner }); }; - -export async function randomPublishedL2Block( - l2BlockNumber: number, - opts: { signers?: Secp256k1Signer[] } = {}, -): Promise { - const block = await L2Block.random(BlockNumber(l2BlockNumber)); - const l1 = L1PublishedData.fromFields({ - blockNumber: BigInt(block.number), - timestamp: block.header.globalVariables.timestamp, - blockHash: Buffer32.random().toString(), - }); - - const signers = opts.signers ?? times(3, () => Secp256k1Signer.random()); - const checkpoint = await Checkpoint.random(CheckpointNumber.fromBlockNumber(BlockNumber(l2BlockNumber)), { - numBlocks: 0, - }); - checkpoint.blocks = [block]; - const atts = signers.map(signer => - makeCheckpointAttestation({ - signer, - archive: block.archive.root, - header: checkpoint.header, - }), - ); - const attestations = atts.map( - (attestation, i) => new CommitteeAttestation(signers[i].address, attestation.signature), - ); - return new CheckpointedL2Block(CheckpointNumber.fromBlockNumber(BlockNumber(l2BlockNumber)), block, l1, attestations); -} diff --git a/yarn-project/stdlib/src/world-state/genesis_data.ts b/yarn-project/stdlib/src/world-state/genesis_data.ts index 83042b957786..2efc5c900296 100644 --- a/yarn-project/stdlib/src/world-state/genesis_data.ts +++ b/yarn-project/stdlib/src/world-state/genesis_data.ts @@ -13,3 +13,15 @@ export const EMPTY_GENESIS_DATA: GenesisData = { prefilledPublicData: [], genesisTimestamp: 0n, }; + +/** Returns if an object looks like genesis data */ +export function isGenesisData(obj: any): obj is GenesisData { + return ( + obj && + typeof obj === 'object' && + 'prefilledPublicData' in obj && + Array.isArray(obj.prefilledPublicData) && + 'genesisTimestamp' in obj && + typeof obj.genesisTimestamp === 'bigint' + ); +} diff --git a/yarn-project/telemetry-client/src/wrappers/l2_block_stream.ts b/yarn-project/telemetry-client/src/wrappers/l2_block_stream.ts index 665b8f83329b..90b58b6a5a62 100644 --- a/yarn-project/telemetry-client/src/wrappers/l2_block_stream.ts +++ b/yarn-project/telemetry-client/src/wrappers/l2_block_stream.ts @@ -11,10 +11,7 @@ import { type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client' /** Extends an L2BlockStream with a tracer to create a new trace per iteration. */ export class TraceableL2BlockStream extends L2BlockStream implements Traceable { constructor( - l2BlockSource: Pick< - L2BlockSource, - 'getBlocks' | 'getBlockHeader' | 'getL2Tips' | 'getCheckpoints' | 'getCheckpointedBlocks' - >, + l2BlockSource: Pick, localData: L2BlockStreamLocalDataProvider, handler: L2BlockStreamEventHandler, public readonly tracer: Tracer, diff --git a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts index fc8e72b231ea..dd9a63f8e36a 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts @@ -175,7 +175,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl async getLastTxEffects() { const latestBlockNumber = await this.stateMachine.archiver.getBlockNumber(); - const block = await this.stateMachine.archiver.getBlock(latestBlockNumber); + const block = await this.stateMachine.archiver.getBlock({ number: latestBlockNumber }); if (block!.body.txEffects.length != 1) { // Note that calls like env.mine() will result in blocks with no transactions, hitting this diff --git a/yarn-project/txe/src/state_machine/archiver.ts b/yarn-project/txe/src/state_machine/archiver.ts index 5a8cc437f9c0..ec153566daa1 100644 --- a/yarn-project/txe/src/state_machine/archiver.ts +++ b/yarn-project/txe/src/state_machine/archiver.ts @@ -4,9 +4,17 @@ import { CheckpointNumber, type EpochNumber, type SlotNumber } from '@aztec/foun import { Fr } from '@aztec/foundation/curves/bn254'; import type { EthAddress } from '@aztec/foundation/eth-address'; import type { AztecAsyncKVStore } from '@aztec/kv-store'; -import type { CheckpointId, L2BlockId, L2TipId, L2Tips, ValidateCheckpointResult } from '@aztec/stdlib/block'; +import { + type CheckpointId, + GENESIS_BLOCK_HEADER_HASH, + type L2BlockId, + type L2TipId, + type L2Tips, + type ValidateCheckpointResult, +} from '@aztec/stdlib/block'; import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; +import { BlockHeader } from '@aztec/stdlib/tx'; /** * TXE Archiver implementation. @@ -17,7 +25,13 @@ export class TXEArchiver extends ArchiverDataSourceBase { private readonly updater = new ArchiverDataStoreUpdater(this.stores); constructor(db: AztecAsyncKVStore) { - super(createArchiverDataStores(db, { logsMaxPageSize: 9999 })); + super( + createArchiverDataStores(db, { logsMaxPageSize: 9999 }), + undefined, + BlockHeader.empty(), + GENESIS_BLOCK_HEADER_HASH, + new Fr(GENESIS_ARCHIVE_ROOT), + ); } public async addCheckpoints(checkpoints: PublishedCheckpoint[], result?: ValidateCheckpointResult): Promise { @@ -47,17 +61,18 @@ export class TXEArchiver extends ArchiverDataSourceBase { public async getL2Tips(): Promise { // In TXE there is no possibility of reorgs and no blocks are ever getting proven so we just set 'latest', 'proven' // and 'finalized' to the latest block. - const blockHeader = await this.getBlockHeader('latest'); - if (!blockHeader) { + const latestBlockNumber = await this.stores.blocks.getLatestL2BlockNumber(); + if (latestBlockNumber === 0) { + throw new Error('L2Tips requested from TXE Archiver but no block found'); + } + const latestBlockData = await this.stores.blocks.getBlockData({ number: latestBlockNumber }); + if (!latestBlockData) { throw new Error('L2Tips requested from TXE Archiver but no block header found'); } - const number = blockHeader.globalVariables.blockNumber; - const hash = (await blockHeader.hash()).toString(); - const checkpointedBlock = await this.getCheckpointedBlock(number); - if (!checkpointedBlock) { - throw new Error(`L2Tips requested from TXE Archiver but no checkpointed block found for block number ${number}`); - } + const number = latestBlockData.header.globalVariables.blockNumber; + const hash = latestBlockData.blockHash.toString(); + // TXE uses 1-block-per-checkpoint for testing simplicity, so we can use block number as checkpoint number. // This uses the deprecated fromBlockNumber method intentionally for the TXE testing environment. const checkpoint = await this.stores.blocks.getRangeOfCheckpoints(CheckpointNumber.fromBlockNumber(number), 1); diff --git a/yarn-project/validator-client/src/proposal_handler.test.ts b/yarn-project/validator-client/src/proposal_handler.test.ts index 2dcb6512b34a..5023c7db4861 100644 --- a/yarn-project/validator-client/src/proposal_handler.test.ts +++ b/yarn-project/validator-client/src/proposal_handler.test.ts @@ -5,11 +5,12 @@ import { MAX_FEE_ASSET_PRICE_MODIFIER_BPS } from '@aztec/ethereum/contracts'; import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { TestDateProvider } from '@aztec/foundation/timer'; -import type { FieldsOf } from '@aztec/foundation/types'; +import { type FieldsOf, unfreeze } from '@aztec/foundation/types'; import type { P2P } from '@aztec/p2p'; import type { BlockProposalValidator } from '@aztec/p2p/msg_validators'; -import type { L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block'; +import type { BlockData, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block'; import type { Checkpoint } from '@aztec/stdlib/checkpoint'; +import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import type { ITxProvider, ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server'; import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging'; import { accumulateCheckpointOutHashes } from '@aztec/stdlib/messaging'; @@ -75,7 +76,7 @@ describe('ProposalHandler checkpoint validation', () => { l1GenesisTime: 0n, slotDuration: 24, epochDuration: 8, - } as any); + } as L1RollupConstants); epochCache.isProposerPipeliningEnabled.mockReturnValue(true); epochCache.pipeliningOffset.mockReturnValue(1); @@ -126,14 +127,14 @@ describe('ProposalHandler checkpoint validation', () => { }); it('returns last_block_not_found when block is not found before timeout', async () => { - blockSource.getBlockHeaderByArchive.mockResolvedValue(undefined); + blockSource.getBlockData.mockResolvedValue(undefined); const result = await handler.handleCheckpointProposal(await makeProposal(), proposalInfo); expect(result).toEqual({ isValid: false, reason: 'last_block_not_found' }); }); it('returns no_blocks_for_slot when no blocks exist for the slot', async () => { - blockSource.getBlockHeaderByArchive.mockResolvedValue(makeBlockHeader()); + blockSource.getBlockData.mockResolvedValue({ header: makeBlockHeader() } as BlockData); blockSource.getBlocksForSlot.mockResolvedValue([]); const result = await handler.handleCheckpointProposal(await makeProposal(), proposalInfo); @@ -141,7 +142,7 @@ describe('ProposalHandler checkpoint validation', () => { }); it('returns last_block_archive_mismatch when last block archive does not match', async () => { - blockSource.getBlockHeaderByArchive.mockResolvedValue(makeBlockHeader()); + blockSource.getBlockData.mockResolvedValue({ header: makeBlockHeader() } as BlockData); const blocks = [ { archive: new AppendOnlyTreeSnapshot(Fr.random(), 1), number: 1 }, { archive: new AppendOnlyTreeSnapshot(Fr.random(), 2), number: 2 }, @@ -171,7 +172,7 @@ describe('ProposalHandler checkpoint validation', () => { ); const archiveRoot = Fr.random(); - blockSource.getBlockHeaderByArchive.mockResolvedValue(makeBlockHeader()); + blockSource.getBlockData.mockResolvedValue({ header: makeBlockHeader() } as BlockData); const blocks = [ { archive: new AppendOnlyTreeSnapshot(Fr.random(), 1), number: 1 }, { archive: new AppendOnlyTreeSnapshot(Fr.random(), 2), number: 2 }, @@ -185,14 +186,14 @@ describe('ProposalHandler checkpoint validation', () => { }); it('caches validation result and returns it on second call', async () => { - blockSource.getBlockHeaderByArchive.mockResolvedValue(undefined); + blockSource.getBlockData.mockResolvedValue(undefined); const proposal = await makeProposal(); const result1 = await handler.handleCheckpointProposal(proposal, proposalInfo); expect(result1.isValid).toBe(false); // Reset mocks to verify they're NOT called again - blockSource.getBlockHeaderByArchive.mockClear(); + blockSource.getBlockData.mockClear(); blockSource.syncImmediate.mockClear(); const result2 = await handler.handleCheckpointProposal(proposal, proposalInfo); @@ -201,7 +202,7 @@ describe('ProposalHandler checkpoint validation', () => { }); it('does not use cache for a different proposal', async () => { - blockSource.getBlockHeaderByArchive.mockResolvedValue(undefined); + blockSource.getBlockData.mockResolvedValue(undefined); await handler.handleCheckpointProposal(await makeProposal({ archiveRoot: Fr.random() }), proposalInfo); blockSource.syncImmediate.mockClear(); @@ -210,8 +211,8 @@ describe('ProposalHandler checkpoint validation', () => { expect(blockSource.syncImmediate).toHaveBeenCalled(); }); - it('returns block_fetch_error when getBlockHeaderByArchive throws', async () => { - blockSource.getBlockHeaderByArchive.mockRejectedValue(new Error('db connection failed')); + it('returns block_fetch_error when getBlockData throws', async () => { + blockSource.getBlockData.mockRejectedValue(new Error('db connection failed')); const result = await handler.handleCheckpointProposal(await makeProposal(), proposalInfo); expect(result).toEqual({ isValid: false, reason: 'block_fetch_error' }); @@ -234,8 +235,8 @@ describe('ProposalHandler checkpoint validation', () => { checkpointNumber: CheckpointNumber(3), header: { getBlockNumber: () => 9 }, indexWithinCheckpoint: 2, - } as any; - blockSource.getBlockDataByArchive.mockResolvedValue(blockData); + } as BlockData; + blockSource.getBlockData.mockResolvedValue(blockData); jest .spyOn(handler, 'handleCheckpointProposal') @@ -265,7 +266,7 @@ describe('ProposalHandler checkpoint validation', () => { header: { globalVariables: GlobalVariables.empty({ slotNumber: SlotNumber(1) }) }, } as unknown as L2Block; - blockSource.getBlockHeaderByArchive.mockResolvedValue(makeBlockHeader()); + blockSource.getBlockData.mockResolvedValue({ header: makeBlockHeader() } as BlockData); blockSource.getBlocksForSlot.mockResolvedValue([block]); mockDispose = jest.fn(); @@ -360,7 +361,7 @@ describe('ProposalHandler checkpoint validation', () => { gasFees: header.gasFees, timestamp: header.timestamp, }); - (blockHeader as any).lastArchive = new AppendOnlyTreeSnapshot(lastArchiveRoot, 0); + unfreeze(blockHeader).lastArchive = new AppendOnlyTreeSnapshot(lastArchiveRoot, 0); const minimalBlock = { archive: new AppendOnlyTreeSnapshot(archiveRoot, 1), diff --git a/yarn-project/validator-client/src/proposal_handler.ts b/yarn-project/validator-client/src/proposal_handler.ts index c1b5026a3c2a..6bc78992c4b3 100644 --- a/yarn-project/validator-client/src/proposal_handler.ts +++ b/yarn-project/validator-client/src/proposal_handler.ts @@ -292,7 +292,7 @@ export class ProposalHandler { proposalInfo.blockNumber = blockNumber; // Check that this block number does not exist already - const existingBlock = await this.blockSource.getBlockHeader(blockNumber); + const existingBlock = await this.blockSource.getBlockData({ number: blockNumber }); if (existingBlock) { this.log.warn(`Block number ${blockNumber} already exists, skipping processing`, proposalInfo); return { isValid: false, blockNumber, reason: 'block_number_already_exists' }; @@ -393,11 +393,12 @@ export class ProposalHandler { try { return ( - (await this.blockSource.getBlockDataByArchive(parentArchive)) ?? + (await this.blockSource.getBlockData({ archive: parentArchive })) ?? (timeoutDurationMs <= 0 ? undefined : await retryUntil( - () => this.blockSource.syncImmediate().then(() => this.blockSource.getBlockDataByArchive(parentArchive)), + () => + this.blockSource.syncImmediate().then(() => this.blockSource.getBlockData({ archive: parentArchive })), 'force archiver sync', timeoutDurationMs / 1000, 0.5, @@ -794,7 +795,7 @@ export class ProposalHandler { lastBlockHeader = await retryUntil( async () => { await this.blockSource.syncImmediate(); - return this.blockSource.getBlockHeaderByArchive(proposal.archive); + return (await this.blockSource.getBlockData({ archive: proposal.archive }))?.header; }, `waiting for block with archive ${proposal.archive.toString()} for slot ${slot}`, timeoutSeconds, @@ -953,7 +954,7 @@ export class ProposalHandler { /** Uploads blobs for a checkpoint to the filestore. */ protected async uploadBlobsForCheckpoint(proposal: CheckpointProposalCore, proposalInfo: LogData): Promise { try { - const lastBlockHeader = await this.blockSource.getBlockHeaderByArchive(proposal.archive); + const lastBlockHeader = (await this.blockSource.getBlockData({ archive: proposal.archive }))?.header; if (!lastBlockHeader) { this.log.warn(`Failed to get last block header for blob upload`, proposalInfo); return; @@ -987,7 +988,7 @@ export class ProposalHandler { if (!this.archiver) { return false; } - const blockData = await this.blockSource.getBlockDataByArchive(proposal.archive); + const blockData = await this.blockSource.getBlockData({ archive: proposal.archive }); if (!blockData) { this.log.debug(`Block data not found for checkpoint proposal archive, cannot set proposed checkpoint`, { archive: proposal.archive.toString(), @@ -1015,7 +1016,7 @@ export class ProposalHandler { if (!this.archiver) { return false; } - let blockData = await this.blockSource.getBlockDataByArchive(proposal.archive); + let blockData = await this.blockSource.getBlockData({ archive: proposal.archive }); if (!blockData) { // The checkpoint proposal often arrives before the last block finishes re-execution. @@ -1025,7 +1026,7 @@ export class ProposalHandler { const timeoutSeconds = Math.max(1, Number(timeOfNextSlot) - Math.floor(this.dateProvider.now() / 1000)); blockData = await retryUntil( - () => this.blockSource.getBlockDataByArchive(proposal.archive), + () => this.blockSource.getBlockData({ archive: proposal.archive }), 'block data for own checkpoint proposal', timeoutSeconds, 0.25, diff --git a/yarn-project/validator-client/src/validator.integration.test.ts b/yarn-project/validator-client/src/validator.integration.test.ts index bf61ac2e3566..249d6e026043 100644 --- a/yarn-project/validator-client/src/validator.integration.test.ts +++ b/yarn-project/validator-client/src/validator.integration.test.ts @@ -104,10 +104,10 @@ describe('ValidatorClient Integration', () => { dataStoreMapSizeKb: 1024 * 1024, }); await registerProtocolContracts(archiverStore); - const archiver = await createNoopL1Archiver(archiverStore, { ...l1Constants, genesisArchiveRoot }); - await archiver.start(); - // Create world state synchronizer + // Construct world-state first so we can pass its initial header to the archiver, mirroring + // production wiring (see aztec-node/server.ts). Both sides must agree on the genesis hash for + // L2BlockStream's `areBlockHashesEqualAt` check to succeed at block 0. const wsConfig = { l1Contracts: { rollupAddress }, worldStateBlockCheckIntervalMS: 20, @@ -116,6 +116,14 @@ describe('ValidatorClient Integration', () => { worldStateCheckpointHistory: 0, }; const worldStateDb = await NativeWorldStateService.tmp(rollupAddress, true, genesis); + const archiver = await createNoopL1Archiver( + archiverStore, + { ...l1Constants, genesisArchiveRoot }, + undefined, + worldStateDb.getInitialHeader(), + ); + await archiver.start(); + const synchronizer = new ServerWorldStateSynchronizer(worldStateDb, archiver, wsConfig); await synchronizer.start(); @@ -406,7 +414,7 @@ describe('ValidatorClient Integration', () => { // Verify blocks are in archiver and hashes match await attestor.archiver.syncImmediate(); - const attestorBlocks = await attestor.archiver.getBlocks(BlockNumber(1), 3); + const attestorBlocks = await attestor.archiver.getBlocks({ from: BlockNumber(1), limit: 3 }); expect(attestorBlocks.length).toBe(3); const attestorBlockHashes = await Promise.all(attestorBlocks.map(b => b.header.hash())); @@ -441,7 +449,7 @@ describe('ValidatorClient Integration', () => { // Verify blocks are in archiver and hashes match await attestor.archiver.syncImmediate(); - const attestorBlocks = await attestor.archiver.getBlocks(BlockNumber(1), 3); + const attestorBlocks = await attestor.archiver.getBlocks({ from: BlockNumber(1), limit: 3 }); expect(attestorBlocks.length).toBe(3); const attestorBlockHashes = await Promise.all(attestorBlocks.map(b => b.header.hash())); @@ -497,7 +505,7 @@ describe('ValidatorClient Integration', () => { // Verify all blocks are in archiver await attestor.archiver.syncImmediate(); - const attestorBlocks = await attestor.archiver.getBlocks(BlockNumber(1), 4); + const attestorBlocks = await attestor.archiver.getBlocks({ from: BlockNumber(1), limit: 4 }); expect(attestorBlocks.length).toBe(4); const attestorBlockHashes = await Promise.all(attestorBlocks.map(b => b.header.hash())); diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index f3d66d088920..bf17e92b2dec 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -151,7 +151,7 @@ describe('ValidatorClient', () => { }); blockSource = mock(); - blockSource.getCheckpointedBlocksForEpoch.mockResolvedValue([]); + blockSource.getBlocks.mockResolvedValue([]); blockSource.getCheckpointsDataForEpoch.mockResolvedValue([]); blockSource.getBlocksForSlot.mockResolvedValue([]); blockSource.getSyncedL2SlotNumber.mockResolvedValue(SlotNumber(Number.MAX_SAFE_INTEGER)); @@ -338,6 +338,7 @@ describe('ValidatorClient', () => { let sender: PeerId; let blockBuildResult: BuildBlockInCheckpointResult; let mockCheckpointBuilder: MockProxy; + let parentBlockData: BlockData; const makeTxFromHash = (txHash: TxHash) => ({ getTxHash: () => txHash, txHash }) as Tx; const getExpectedWallClockDeadline = (currentSlot: SlotNumber) => @@ -393,9 +394,10 @@ describe('ValidatorClient', () => { epochCache.filterInCommittee.mockResolvedValue([EthAddress.fromString(validatorAccounts[0].address)]); epochCache.isEscapeHatchOpenAtSlot.mockResolvedValue(false); - // Return parent block data when requested (includes checkpoint info, avoids loading full L2Block) + // Return parent block data when requested by archive root (parent block lookup). + // Return undefined for number-based queries (existence check — the proposed block must not exist yet). const parentSlot = SlotNumber(Number(blockHeader.globalVariables.slotNumber) - 1); - blockSource.getBlockDataByArchive.mockResolvedValue({ + parentBlockData = { header: { getBlockNumber: () => blockNumber - 1, getSlot: () => parentSlot, @@ -405,7 +407,10 @@ describe('ValidatorClient', () => { blockHash: BlockHash.random(), checkpointNumber: CheckpointNumber(1), indexWithinCheckpoint: IndexWithinCheckpoint(0), - } as unknown as BlockData); + } as unknown as BlockData; + blockSource.getBlockData.mockImplementation(query => + Promise.resolve('number' in query ? undefined : parentBlockData), + ); blockSource.getGenesisValues.mockResolvedValue({ genesisArchiveRoot: new Fr(GENESIS_ARCHIVE_ROOT) }); blockSource.syncImmediate.mockImplementation(() => Promise.resolve()); @@ -636,8 +641,8 @@ describe('ValidatorClient', () => { }, }); - // Mock getBlockHeaderByArchive to return a header so retryUntil succeeds - blockSource.getBlockHeaderByArchive.mockResolvedValue(makeBlockHeader()); + // Mock getBlockData to return block data so retryUntil succeeds + blockSource.getBlockData.mockResolvedValue({ header: makeBlockHeader() } as any); blockSource.getBlocksForSlot.mockResolvedValue(blocks); // Checkpoint validation should fail: proposal points to block 2 but last block in slot is block 3 @@ -648,12 +653,13 @@ describe('ValidatorClient', () => { it('should wait for previous block to sync', async () => { epochCache.filterInCommittee.mockResolvedValue([EthAddress.fromString(validatorAccounts[0].address)]); - blockSource.getBlockDataByArchive.mockResolvedValueOnce(undefined); - blockSource.getBlockDataByArchive.mockResolvedValueOnce(undefined); - blockSource.getBlockDataByArchive.mockResolvedValueOnce(undefined); + blockSource.getBlockData.mockResolvedValueOnce(undefined); + blockSource.getBlockData.mockResolvedValueOnce(undefined); + blockSource.getBlockData.mockResolvedValueOnce(undefined); const isValid = await validatorClient.validateBlockProposal(proposal, sender); - // Direct call returns undefined, then retryUntil: 2 undefined + 1 success = 4 total - expect(blockSource.getBlockDataByArchive).toHaveBeenCalledTimes(4); + // Archive lookups: 1 direct + 2 retryUntil (undefined) + 1 retryUntil (success) = 4 + // Plus 1 number-based existence check after parent block is found = 5 total + expect(blockSource.getBlockData).toHaveBeenCalledTimes(5); expect(isValid).toBe(true); }); @@ -694,10 +700,13 @@ describe('ValidatorClient', () => { }); it('should not validate proposal if the proposed block number is taken', async () => { - blockSource.getBlockHeader.mockResolvedValue({} as BlockHeader); + // Parent block lookup (by archive) returns valid data; existence check (by number) also returns data → block taken. + blockSource.getBlockData.mockImplementation(query => + Promise.resolve('number' in query ? ({ header: {} as BlockHeader } as any) : parentBlockData), + ); const isValid = await validatorClient.validateBlockProposal(proposal, sender); expect(isValid).toBe(false); - expect(blockSource.getBlockHeader).toHaveBeenCalledWith(blockNumber); + expect(blockSource.getBlockData).toHaveBeenCalledWith({ number: blockNumber }); }); it('should not emit WANT_TO_SLASH_EVENT if slashing is disabled', async () => { @@ -889,8 +898,8 @@ describe('ValidatorClient', () => { nowSeconds: 0n, }); - // Mock parent block data returned by getBlockDataByArchive - blockSource.getBlockDataByArchive.mockResolvedValue({ + // Mock parent block data returned by getBlockData + blockSource.getBlockData.mockResolvedValue({ header: { getBlockNumber: () => BlockNumber(parentBlockNumber), getSlot: () => SlotNumber(parentSlotNumber), @@ -1025,7 +1034,7 @@ describe('ValidatorClient', () => { it('should send blobs from blocks in the slot to filestore', async () => { const mockBlock = L2Block.empty(); - blockSource.getBlockHeaderByArchive.mockResolvedValue(makeBlockHeader()); + blockSource.getBlockData.mockResolvedValue({ header: makeBlockHeader() } as any); blockSource.getBlocksForSlot.mockResolvedValue([mockBlock]); const proposal = await makeCheckpointProposal({ lastBlock: {} }); @@ -1039,7 +1048,7 @@ describe('ValidatorClient', () => { }); it('should not upload if last block header is not found', async () => { - blockSource.getBlockHeaderByArchive.mockResolvedValue(undefined); + blockSource.getBlockData.mockResolvedValue(undefined); const proposal = await makeCheckpointProposal({ lastBlock: {} }); await (validatorClient.getProposalHandler() as TestProposalHandler).uploadBlobsForCheckpoint( @@ -1052,7 +1061,7 @@ describe('ValidatorClient', () => { it('should not throw when blob upload fails', async () => { const mockBlock = L2Block.empty(); - blockSource.getBlockHeaderByArchive.mockResolvedValue(makeBlockHeader()); + blockSource.getBlockData.mockResolvedValue({ header: makeBlockHeader() } as any); blockSource.getBlocksForSlot.mockResolvedValue([mockBlock]); blobClient.sendBlobsToFilestore.mockRejectedValue(new Error('upload failed')); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index eaebe669fa47..98a60a50a08c 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -645,7 +645,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter) */ protected async uploadBlobsForCheckpoint(proposal: CheckpointProposalCore, proposalInfo: LogData): Promise { try { - const lastBlockHeader = await this.blockSource.getBlockHeaderByArchive(proposal.archive); + const lastBlockHeader = (await this.blockSource.getBlockData({ archive: proposal.archive }))?.header; if (!lastBlockHeader) { this.log.warn(`Failed to get last block header for blob upload`, proposalInfo); return; diff --git a/yarn-project/world-state/src/synchronizer/factory.ts b/yarn-project/world-state/src/synchronizer/factory.ts index 47eb9d601339..5f8c2a5f52f9 100644 --- a/yarn-project/world-state/src/synchronizer/factory.ts +++ b/yarn-project/world-state/src/synchronizer/factory.ts @@ -2,7 +2,7 @@ import type { LoggerBindings } from '@aztec/foundation/log'; import type { L2BlockSource } from '@aztec/stdlib/block'; import type { DataStoreConfig } from '@aztec/stdlib/kv-store'; import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging'; -import { EMPTY_GENESIS_DATA, type GenesisData } from '@aztec/stdlib/world-state'; +import { EMPTY_GENESIS_DATA, type GenesisData, isGenesisData } from '@aztec/stdlib/world-state'; import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client'; import { WorldStateInstrumentation } from '../instrumentation/instrumentation.js'; @@ -21,12 +21,14 @@ export interface WorldStateTreeMapSizes { export async function createWorldStateSynchronizer( config: WorldStateConfig & DataStoreConfig, l2BlockSource: L2BlockSource & L1ToL2MessageSource, - genesis: GenesisData = EMPTY_GENESIS_DATA, + genesisOrNativeWorldState: GenesisData | NativeWorldStateService, client: TelemetryClient = getTelemetryClient(), bindings?: LoggerBindings, ) { const instrumentation = new WorldStateInstrumentation(client); - const merkleTrees = await createWorldState(config, genesis, instrumentation, bindings); + const merkleTrees = isGenesisData(genesisOrNativeWorldState) + ? await createWorldState(config, genesisOrNativeWorldState, instrumentation, bindings) + : genesisOrNativeWorldState; return new ServerWorldStateSynchronizer(merkleTrees, l2BlockSource, config, instrumentation); } diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts index fec1b9eca5f0..42f5471d609c 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.test.ts @@ -2,13 +2,7 @@ import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import { timesParallel } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; import { type Logger, createLogger } from '@aztec/foundation/log'; -import { - BlockHash, - GENESIS_BLOCK_HEADER_HASH, - L2Block, - type L2BlockSource, - type L2BlockStream, -} from '@aztec/stdlib/block'; +import { BlockHash, L2Block, type L2BlockSource, type L2BlockStream } from '@aztec/stdlib/block'; import type { Checkpoint } from '@aztec/stdlib/checkpoint'; import { type MerkleTreeReadOperations, WorldStateRunningState } from '@aztec/stdlib/interfaces/server'; import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging'; @@ -285,9 +279,9 @@ describe('ServerWorldStateSynchronizer', () => { }); class TestWorldStateSynchronizer extends ServerWorldStateSynchronizer { - public latest = { number: BlockNumber.ZERO, hash: GENESIS_BLOCK_HEADER_HASH.toString() }; - public finalized = { number: BlockNumber.ZERO, hash: GENESIS_BLOCK_HEADER_HASH.toString() }; - public proven = { number: BlockNumber.ZERO, hash: GENESIS_BLOCK_HEADER_HASH.toString() }; + public latest = { number: BlockNumber.ZERO, hash: BlockHash.random().toString() }; + public finalized = { number: BlockNumber.ZERO, hash: BlockHash.random().toString() }; + public proven = { number: BlockNumber.ZERO, hash: BlockHash.random().toString() }; constructor( merkleTrees: MerkleTreeAdminDatabase, diff --git a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts index eaf2666336de..d8e939fba8ae 100644 --- a/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts +++ b/yarn-project/world-state/src/synchronizer/server_world_state_synchronizer.ts @@ -1,4 +1,4 @@ -import { INITIAL_CHECKPOINT_NUMBER, INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; +import { INITIAL_CHECKPOINT_NUMBER } from '@aztec/constants'; import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import { type Logger, createLogger } from '@aztec/foundation/log'; @@ -6,7 +6,6 @@ import { promiseWithResolvers } from '@aztec/foundation/promise'; import { elapsed } from '@aztec/foundation/timer'; import { type BlockHash, - GENESIS_BLOCK_HEADER_HASH, GENESIS_CHECKPOINT_HEADER_HASH, type L2Block, type L2BlockId, @@ -288,14 +287,15 @@ export class ServerWorldStateSynchronizer // but we use a block stream so we need to provide 'local' L2Tips. // We configure the block stream to ignore checkpoints and set checkpoint values to genesis here. const genesisCheckpointHeaderHash = GENESIS_CHECKPOINT_HEADER_HASH.toString(); + const initialBlockHash = (await this.merkleTreeCommitted.getInitialHeader().hash()).toString(); return { proposed: latestBlockId, checkpointed: { - block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() }, + block: { number: BlockNumber.ZERO, hash: initialBlockHash }, checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash }, }, proposedCheckpoint: { - block: { number: INITIAL_L2_BLOCK_NUM, hash: GENESIS_BLOCK_HEADER_HASH.toString() }, + block: { number: BlockNumber.ZERO, hash: initialBlockHash }, checkpoint: { number: INITIAL_CHECKPOINT_NUMBER, hash: genesisCheckpointHeaderHash }, }, finalized: { @@ -409,16 +409,15 @@ export class ServerWorldStateSynchronizer if (this.historyToKeep === undefined) { return; } - // Get the checkpointed block for the finalized block number - const finalisedCheckpoint = await this.l2BlockSource.getCheckpointedBlock(summary.finalizedBlockNumber); - if (finalisedCheckpoint === undefined) { + const finalisedBlockData = await this.l2BlockSource.getBlockData({ number: summary.finalizedBlockNumber }); + if (finalisedBlockData === undefined) { this.log.warn( `Failed to retrieve checkpointed block for finalized block number: ${summary.finalizedBlockNumber}`, ); return; } // Compute the required historic checkpoint number - const newHistoricCheckpointNumber = finalisedCheckpoint.checkpointNumber - this.historyToKeep + 1; + const newHistoricCheckpointNumber = finalisedBlockData.checkpointNumber - this.historyToKeep + 1; if (newHistoricCheckpointNumber <= 1) { return; } diff --git a/yarn-project/world-state/src/test/integration.test.ts b/yarn-project/world-state/src/test/integration.test.ts index 893a50c8746d..a594ac0bf229 100644 --- a/yarn-project/world-state/src/test/integration.test.ts +++ b/yarn-project/world-state/src/test/integration.test.ts @@ -1,7 +1,8 @@ import { MockPrefilledArchiver } from '@aztec/archiver/test'; +import { GENESIS_ARCHIVE_ROOT } from '@aztec/constants'; import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import { timesAsync } from '@aztec/foundation/collection'; -import type { Fr } from '@aztec/foundation/curves/bn254'; +import { Fr } from '@aztec/foundation/curves/bn254'; import { EthAddress } from '@aztec/foundation/eth-address'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; @@ -58,6 +59,8 @@ describe('world-state integration', () => { archiver = new MockPrefilledArchiver(checkpoints); db = (await createWorldState(config)) as NativeWorldStateService; + await archiver.setInitialHeader(db.getInitialHeader()); + archiver.setGenesisArchiveRoot(new Fr(GENESIS_ARCHIVE_ROOT)); synchronizer = new TestWorldStateSynchronizer(db, archiver, config); log.info(`Created synchronizer`); }, 30_000); @@ -94,7 +97,7 @@ describe('world-state integration', () => { const expectSynchedBlockHashMatches = async (number: number) => { const syncedBlockHash = await db.getCommitted().getLeafValue(MerkleTreeId.ARCHIVE, BigInt(number)); - const archiverBlockHash = await (await archiver.getBlockHeader(number))?.hash(); + const archiverBlockHash = await (await archiver.getBlockData({ number: BlockNumber(number) }))?.header.hash(); expect(syncedBlockHash).toEqual(archiverBlockHash); }; @@ -143,8 +146,6 @@ describe('world-state integration', () => { }); it('syncs from latest block when restarting', async () => { - const getBlocksSpy = jest.spyOn(archiver, 'getBlocks'); - await synchronizer.start(); await archiver.createBlocks(5); await awaitSync(5); @@ -160,10 +161,6 @@ describe('world-state integration', () => { await archiver.createBlocks(4); await awaitSync(12); await expectSynchedToBlock(12); - - expect(getBlocksSpy).toHaveBeenCalledWith(1, 5); - expect(getBlocksSpy).toHaveBeenCalledWith(6, 3); - expect(getBlocksSpy).toHaveBeenCalledWith(9, 4); }); }); From cb1fa2b8586d02a616f8a2e576f93bcbe088cd14 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 5 May 2026 06:07:58 -0300 Subject: [PATCH 02/15] chore(lint): allow branded primitive types as keys in collections (#22935) Updates the custom no-non-primitive-in-collections rule to allow for branded primitive types like BlockNumber as keys in Maps or Sets. --- .../aztec-node/src/sentinel/sentinel.ts | 1 - yarn-project/epoch-cache/src/epoch_cache.ts | 1 - .../no-non-primitive-in-collections.js | 72 +++++++++++++++++++ .../src/orchestrator/orchestrator.ts | 1 - 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/yarn-project/aztec-node/src/sentinel/sentinel.ts b/yarn-project/aztec-node/src/sentinel/sentinel.ts index 7c1a67f245fe..a5e1c1f5e8fe 100644 --- a/yarn-project/aztec-node/src/sentinel/sentinel.ts +++ b/yarn-project/aztec-node/src/sentinel/sentinel.ts @@ -62,7 +62,6 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme protected initialSlot: SlotNumber | undefined; protected lastProcessedSlot: SlotNumber | undefined; - // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections protected slotNumberToCheckpoint: Map< SlotNumber, { checkpointNumber: CheckpointNumber; archive: string; attestors: EthAddress[] } diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index 24b4b0048bb3..d3967c03979c 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -97,7 +97,6 @@ export class EpochCache implements EpochCacheInterface { * Single map holding both resolved entries and in-flight promises. * A `Promise` value means a fetch is in progress; concurrent callers await it. */ - // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections protected cache: Map> = new Map(); private allValidators: Set = new Set(); private lastValidatorRefresh = 0; diff --git a/yarn-project/foundation/eslint-rules/no-non-primitive-in-collections.js b/yarn-project/foundation/eslint-rules/no-non-primitive-in-collections.js index 25bae69abc41..eff593fa3d20 100644 --- a/yarn-project/foundation/eslint-rules/no-non-primitive-in-collections.js +++ b/yarn-project/foundation/eslint-rules/no-non-primitive-in-collections.js @@ -5,6 +5,21 @@ import * as ts from 'typescript'; * @fileoverview Rule to disallow non-primitive types in Set and Map collections */ +/** + * Branded primitive types whose underlying representation is a primitive (number/string/etc.) + * and are therefore safe to use as Set/Map keys. Used as a fallback when the TypeScript type + * checker is unavailable (AST-only mode). The type-checker path detects these structurally + * via the `Branded` intersection encoding, so this list only needs to cover the + * names — not their full definitions. + */ +const BRANDED_PRIMITIVE_TYPES = new Set([ + 'BlockNumber', + 'SlotNumber', + 'CheckpointNumber', + 'EpochNumber', + 'IndexWithinCheckpoint', +]); + /** @type {import('eslint').Rule.RuleModule} */ export default { meta: { @@ -100,9 +115,61 @@ export default { } } + // Check for branded primitive types: T & { _branding: Brand } where T is a primitive. + // Permits SlotNumber, BlockNumber, etc. while still rejecting branded class types + // such as BlockProposalHash = Branded. + if (flags & ts.TypeFlags.Intersection) { + if (tsType.isIntersection && tsType.isIntersection()) { + return isBrandedPrimitive(tsType); + } + } + return false; } + /** + * Detect a Branded intersection. The intersection must contain + * at least one primitive constituent and all non-primitive constituents must be + * the brand marker object (a single `_branding` property and nothing else). + */ + function isBrandedPrimitive(tsType) { + const components = tsType.types; + if (!components || components.length === 0) return false; + + const allowedPrimitiveFlags = + ts.TypeFlags.String | + ts.TypeFlags.Number | + ts.TypeFlags.BigInt | + ts.TypeFlags.Boolean | + ts.TypeFlags.ESSymbol | + ts.TypeFlags.Literal | + ts.TypeFlags.TemplateLiteral | + ts.TypeFlags.StringMapping | + ts.TypeFlags.Enum | + ts.TypeFlags.EnumLiteral; + + let hasPrimitive = false; + let hasBrandMarker = false; + + for (const component of components) { + const componentFlags = component.getFlags(); + if (componentFlags & allowedPrimitiveFlags) { + hasPrimitive = true; + continue; + } + if (componentFlags & ts.TypeFlags.Object) { + const props = component.getProperties ? component.getProperties() : []; + if (props.length === 1 && props[0].getName() === '_branding') { + hasBrandMarker = true; + continue; + } + } + return false; + } + + return hasPrimitive && hasBrandMarker; + } + /** * Fallback: Check if a type node represents a primitive type (AST-only) * This is only used if TypeScript type checker is not available @@ -148,6 +215,11 @@ export default { if (primitives.includes(name)) { return true; } + + // Branded primitives that wrap number/string — safe to use as keys + if (BRANDED_PRIMITIVE_TYPES.has(name)) { + return true; + } } return false; diff --git a/yarn-project/prover-client/src/orchestrator/orchestrator.ts b/yarn-project/prover-client/src/orchestrator/orchestrator.ts index 1dd893fe6af6..a74e7bb452c2 100644 --- a/yarn-project/prover-client/src/orchestrator/orchestrator.ts +++ b/yarn-project/prover-client/src/orchestrator/orchestrator.ts @@ -93,7 +93,6 @@ export class ProvingOrchestrator implements EpochProver { private provingPromise: Promise | undefined = undefined; private metrics: ProvingOrchestratorMetrics; - // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections private dbs: Map = new Map(); private logger: Logger; private deferredJobQueue = new SerialQueue(); From fa098ff0462e5793585195b9c44827a61a8cd0e5 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 5 May 2026 06:54:28 -0300 Subject: [PATCH 03/15] test(e2e): test missed l1 publishing under pipelining (#22926) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit End-to-end test for the "missed L1 publish" scenario under proposer pipelining. Each of 4 nodes holds exactly one validator key. We pick four consecutive slots (slotZero, slotOne, slotTwo, slotThree) such that the proposers for slotOne, slotTwo, and slotThree are three distinct validators, then warp to one L1 block before slotZero begins. The proposer for slotOne is configured to skip its L1 publish. With pipelining, the proposer for slot N+1 builds and gossips its checkpoint during slot N, then publishes that checkpoint to L1 during slot N+1. So gossip-driven `proposed` chain advances arrive one slot earlier than the L1-driven `checkpointed` advance. Expected behavior: - During slotZero, the pipelined proposer for slotOne gossips its build → every node's `proposed` tip advances to a block at slotOne. - During slotOne, the pipelined proposer for slotTwo gossips on top of the slotOne proposal → `proposed` advances to a block at slotTwo. Meanwhile the proposer for slotOne attempts L1 publish but is configured to skip it, so no checkpoint lands. - When slotOne ends with no checkpoint mined, every node's archiver prunes the uncheckpointed slotOne and slotTwo blocks; we verify rollback via the prune event. We then re-enable publishing on the formerly suppressed node so recovery can proceed. - During slotTwo, the pipelined proposer for slotThree builds on top of the (now genesis) checkpointed tip → `proposed` advances again. - During slotThree, that pipelined work is published → `checkpointed` finally advances. --- .../epochs_missed_l1_publish.test.ts | 365 ++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_publish.test.ts diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_publish.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_publish.test.ts new file mode 100644 index 000000000000..9ab521186a71 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_missed_l1_publish.test.ts @@ -0,0 +1,365 @@ +import type { Archiver } from '@aztec/archiver'; +import type { AztecNodeService } from '@aztec/aztec-node'; +import { EthAddress } from '@aztec/aztec.js/addresses'; +import { Fr } from '@aztec/aztec.js/fields'; +import type { Logger } from '@aztec/aztec.js/log'; +import { waitUntilL1Timestamp } from '@aztec/ethereum/l1-tx-utils'; +import { asyncMap } from '@aztec/foundation/async-map'; +import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { times } from '@aztec/foundation/collection'; +import { SecretValue } from '@aztec/foundation/config'; +import { retryUntil } from '@aztec/foundation/retry'; +import { bufferToHex } from '@aztec/foundation/string'; +import { timeoutPromise } from '@aztec/foundation/timer'; +import { type L2Block, L2BlockSourceEvents, type L2Tips } from '@aztec/stdlib/block'; +import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers'; + +import { jest } from '@jest/globals'; +import { privateKeyToAccount } from 'viem/accounts'; + +import { getPrivateKeyFromIndex } from '../fixtures/utils.js'; +import { EpochsTestContext } from './epochs_test.js'; + +jest.setTimeout(1000 * 60 * 15); + +const NODE_COUNT = 4; + +/** + * E2E test for the "missed L1 publish" scenario under proposer pipelining. + * + * Each of 4 nodes holds exactly one validator key. We pick four consecutive slots + * (slotZero, slotOne, slotTwo, slotThree) such that the proposers for slotOne, slotTwo, and + * slotThree are three distinct validators, then warp to one L1 block before slotZero begins. + * The proposer for slotOne is configured to skip its L1 publish. + * + * With pipelining, the proposer for slot N+1 builds and gossips its checkpoint during slot N, + * then publishes that checkpoint to L1 during slot N+1. So gossip-driven `proposed` chain + * advances arrive one slot earlier than the L1-driven `checkpointed` advance. + * + * Expected behavior: + * - During slotZero, the pipelined proposer for slotOne gossips its build → every node's + * `proposed` tip advances to a block at slotOne. + * - During slotOne, the pipelined proposer for slotTwo gossips on top of the slotOne proposal → + * `proposed` advances to a block at slotTwo. Meanwhile the proposer for slotOne attempts L1 + * publish but is configured to skip it, so no checkpoint lands. + * - When slotOne ends with no checkpoint mined, every node's archiver prunes the + * uncheckpointed slotOne and slotTwo blocks; we verify rollback via the prune event. + * We then re-enable publishing on the formerly suppressed node so recovery can proceed. + * - During slotTwo, the pipelined proposer for slotThree builds on top of the (now genesis) + * checkpointed tip → `proposed` advances again. + * - During slotThree, that pipelined work is published → `checkpointed` finally advances. + */ +describe('e2e_epochs/epochs_missed_l1_publish', () => { + let logger: Logger; + let test: EpochsTestContext; + let nodes: AztecNodeService[]; + + afterEach(async () => { + jest.restoreAllMocks(); + await test?.teardown(); + }); + + it('all nodes prune and recover when proposer fails to publish to L1', async () => { + // Build 4 distinct validators (V1..V4). One key per node, no overlap. + const validators = times(NODE_COUNT, i => { + const privateKey = bufferToHex(getPrivateKeyFromIndex(i + 3)!); + const attester = EthAddress.fromString(privateKeyToAccount(privateKey).address); + return { attester, withdrawer: attester, privateKey, bn254SecretKey: new SecretValue(Fr.random().toBigInt()) }; + }); + + test = await EpochsTestContext.setup({ + numberOfAccounts: 0, + initialValidators: validators, + enableProposerPipelining: true, + inboxLag: 2, + mockGossipSubNetwork: true, + disableAnvilTestWatcher: true, + startProverNode: false, + aztecEpochDuration: 4, + aztecProofSubmissionEpochs: 1024, + enforceTimeTable: true, + ethereumSlotDuration: 6, + aztecSlotDuration: 36, + blockDurationMs: 8000, + attestationPropagationTime: 0.5, + l1PublishingTime: 2, + aztecTargetCommitteeSize: NODE_COUNT, + skipInitialSequencer: true, + }); + + logger = test.logger; + + // One node per validator. dontStartSequencer until after the warp so timing is deterministic. + nodes = await asyncMap(validators, ({ privateKey }, i) => + test.createValidatorNode([privateKey], { + dontStartSequencer: true, + coinbase: EthAddress.fromNumber(0xa + i), + buildCheckpointIfEmpty: true, + minTxsPerBlock: 0, + }), + ); + + const attesterAddresses = validators.map(v => v.attester); + logger.warn('Validator nodes created', { + validators: attesterAddresses.map((a, i) => ({ idx: i, attester: a.toString() })), + }); + + // Find slotOne (>=4 ahead) such that proposers for slotOne, slotTwo, slotThree are three + // distinct validators. The +4 margin (vs +2 in equivocation) gives the warp+sequencer-start + // path enough headroom to reach the build window for slotZero even if node creation jitters. + const { slot: currentSlot } = test.epochCache.getEpochAndSlotNow(); + const scanStart = currentSlot + 4; + const scanEnd = currentSlot + 60; + let slotOne: SlotNumber | undefined; + let proposerOne: EthAddress | undefined; + let proposerTwo: EthAddress | undefined; + let proposerThree: EthAddress | undefined; + for (let candidate = scanStart; candidate <= scanEnd; candidate++) { + const [p1, p2, p3] = await Promise.all([ + test.epochCache.getProposerAttesterAddressInSlot(SlotNumber(candidate)), + test.epochCache.getProposerAttesterAddressInSlot(SlotNumber(candidate + 1)), + test.epochCache.getProposerAttesterAddressInSlot(SlotNumber(candidate + 2)), + ]); + if (p1 && p2 && p3 && !p1.equals(p2) && !p1.equals(p3) && !p2.equals(p3)) { + slotOne = SlotNumber(candidate); + proposerOne = p1; + proposerTwo = p2; + proposerThree = p3; + break; + } + } + if (slotOne === undefined || !proposerOne || !proposerTwo || !proposerThree) { + throw new Error(`Could not find a slot in [${scanStart}, ${scanEnd}] with three distinct consecutive proposers`); + } + + const slotZero = SlotNumber(slotOne - 1); + const slotTwo = SlotNumber(slotOne + 1); + const slotThree = SlotNumber(slotOne + 2); + + const proposerOneNodeIndex = validators.findIndex(v => v.attester.equals(proposerOne!)); + if (proposerOneNodeIndex < 0) { + throw new Error(`No node holds the key for proposer ${proposerOne}`); + } + + logger.warn(`Selected target slotOne=${slotOne}`, { + slotOne, + slotZero, + slotTwo, + slotThree, + proposerOne: proposerOne.toString(), + proposerOneNodeIndex, + proposerTwo: proposerTwo.toString(), + proposerThree: proposerThree.toString(), + }); + + // Prevent the proposer for slotOne from publishing the checkpoint to L1 (build & gossip still happen). + await nodes[proposerOneNodeIndex].setConfig({ skipPublishingCheckpointsPercent: 100 }); + + // Subscribe to the prune event on every node before sequencers start, so we never miss it. + // We capture the L2 tips synchronously inside the handler — the archiver has already removed + // the pruned blocks at emit time, so this snapshot reflects the rolled-back state before any + // new pipelined block can be applied. + type PruneObservation = { slotNumber: SlotNumber; blocks: L2Block[]; tipsAtPrune: L2Tips }; + const prunePromises: Promise[] = nodes.map( + (node, idx) => + new Promise(resolve => { + const archiver = node.getBlockSource() as Archiver; + // eslint-disable-next-line @typescript-eslint/no-misused-promises + archiver.events.once(L2BlockSourceEvents.L2PruneUncheckpointed, async ev => { + const tipsAtPrune = await node.getL2Tips(); + logger.warn(`Node ${idx} pruned uncheckpointed blocks`, { + slotNumber: ev.slotNumber, + blocks: ev.blocks.map(b => ({ number: b.number, slot: b.header.globalVariables.slotNumber })), + tipsAtPrune, + }); + resolve({ slotNumber: ev.slotNumber, blocks: ev.blocks, tipsAtPrune }); + }); + }), + ); + + // Warp L1 to one L1 block before slotZero begins. Pipelining will then engage during slotZero. + const slotZeroStart = getTimestampForSlot(slotZero, test.constants); + const warpTo = slotZeroStart - BigInt(test.L1_BLOCK_TIME_IN_S); + logger.warn(`Warping L1 to timestamp ${warpTo} (one L1 block before slot ${slotZero})`); + await test.context.cheatCodes.eth.warp(Number(warpTo), { resetBlockInterval: true }); + + // Check that the chain is empty + const node = nodes[0]; + const blockNumber = await node.getBlockNumber(); + expect(blockNumber).toEqual(0); + + // Start all sequencers. + const sequencers = nodes.map(n => n.getSequencer()!); + const { failEvents } = test.watchSequencerEvents(sequencers, i => ({ validator: `V${i + 1}` })); + + // Subscribe to the proposerTwo pipelined-discard event — this is the most direct signal + // that the pipelined slotTwo work was correctly thrown away because parent slotOne did not land. + const proposerTwoNodeIndex = validators.findIndex(v => v.attester.equals(proposerTwo!)); + const pipelinedDiscardEvents: { slot: SlotNumber; checkpointNumber: number; reason: string }[] = []; + sequencers[proposerTwoNodeIndex].getSequencer().on('pipelined-checkpoint-discarded', args => { + pipelinedDiscardEvents.push({ slot: args.slot, checkpointNumber: args.checkpointNumber, reason: args.reason }); + logger.warn(`proposerTwo (node ${proposerTwoNodeIndex}) discarded pipelined work`, args); + }); + + await Promise.all(sequencers.map(s => s.start())); + logger.warn('All sequencers started'); + + const slotAdvanceTimeout = test.L2_SLOT_DURATION_IN_S * 3; + + // (1) During slotZero: the pipelined proposer for slotOne broadcasts. Every node sees a proposed block at slotOne. + logger.warn(`Waiting for proposed chain to reach slot ${slotOne} on all nodes (build during slotZero)`); + await Promise.all( + nodes.map((node, idx) => + retryUntil( + async () => { + const tips = await node.getL2Tips(); + if (tips.proposed.number === 0) { + return false; + } + const block = await node.getBlock(tips.proposed.number); + return !!block && block.header.globalVariables.slotNumber === slotOne; + }, + `node ${idx} proposed advanced to slot ${slotOne}`, + slotAdvanceTimeout, + 0.5, + ), + ), + ); + + // (2) During slotOne: the pipelined proposer for slotTwo broadcasts on top of slotOne → proposed reaches slotTwo. + logger.warn(`Waiting for proposed chain to reach slot ${slotTwo} on all nodes (build during slotOne)`); + await Promise.all( + nodes.map((node, idx) => + retryUntil( + async () => { + const tips = await node.getL2Tips(); + if (tips.proposed.number === 0) { + return false; + } + const block = await node.getBlock(tips.proposed.number); + return !!block && block.header.globalVariables.slotNumber === slotTwo; + }, + `node ${idx} proposed advanced to slot ${slotTwo}`, + slotAdvanceTimeout, + 0.5, + ), + ), + ); + + // (3) Wait until slotOne has fully ended on L1 — the archiver only prunes once slotAtNextL1Block > slotOne. + // The end-of-slotOne timestamp equals the start-of-slotTwo timestamp. + const slotOneEndTimestamp = getTimestampForSlot(slotTwo, test.constants); + logger.warn(`Waiting until L1 timestamp ${slotOneEndTimestamp} (end of slot ${slotOne})`); + await waitUntilL1Timestamp(test.l1Client, slotOneEndTimestamp, undefined, test.L2_SLOT_DURATION_IN_S * 3); + + // (4) After slotOne ends without a checkpoint, all nodes should prune. + // Verify rollback via the prune event itself: the pruned slot must equal slotOne, and the + // pruned blocks must include the broadcast blocks for slotOne (proposerOne) and slotTwo + // (pipelined proposerTwo, whose work is now invalid because parent slotOne did not land). + logger.warn('Waiting for L2PruneUncheckpointed on every node'); + const pruneTimeoutMs = test.L2_SLOT_DURATION_IN_S * 2 * 1000; + const pruneObservations = await Promise.all( + prunePromises.map((p, idx) => + Promise.race([p, timeoutPromise(pruneTimeoutMs, `Node ${idx} did not emit prune event in time`)]), + ), + ); + + logger.warn('Asserting prune event details on every node'); + for (const [idx, obs] of pruneObservations.entries()) { + expect({ idx, slotNumber: obs.slotNumber }).toEqual({ idx, slotNumber: slotOne }); + // proposerOne broadcasts during slotZero, so its block must always be in the pruned set. + // The pipelined slotTwo broadcast may or may not have arrived in time on every node, so + // we don't strictly require it here. + const prunedSlots = obs.blocks.map(b => b.header.globalVariables.slotNumber); + expect(prunedSlots).toContain(slotOne); + } + + // (5) Allow the formerly suppressed node to publish again so the chain can recover. + logger.warn(`Re-enabling checkpoint publishing on node ${proposerOneNodeIndex}`); + await nodes[proposerOneNodeIndex].setConfig({ skipPublishingCheckpointsPercent: 0 }); + + // (6) During slotTwo: the pipelined proposer for slotThree builds and broadcasts → proposed advances again. + // The chain must have rewound past slotOne and slotTwo and now build on whatever was + // checkpointed before slotZero — genesis, in this test, since no checkpoints have landed yet. + const postPruneProposedNumbers = pruneObservations.map(o => o.tipsAtPrune.proposed.number); + expect(postPruneProposedNumbers[0]).toBe(0); + + logger.warn(`Waiting for proposed chain to advance to slot ${slotThree} on all nodes (build during slotTwo)`); + await Promise.all( + nodes.map((node, idx) => + retryUntil( + async () => { + const tips = await node.getL2Tips(); + if (tips.proposed.number === 0) { + return false; + } + const block = await node.getBlock(tips.proposed.number); + return !!block && block.header.globalVariables.slotNumber >= slotThree; + }, + `node ${idx} proposed advanced to slot >= ${slotThree}`, + slotAdvanceTimeout, + 0.5, + ), + ), + ); + + // The first block in the chain after the prune must be the slotThree block — there should be + // nothing between genesis and the new pipelined work, since slotOne and slotTwo were pruned. + for (const node of nodes) { + const blocks = await node.getBlocks(BlockNumber(1), 50); + const firstSlotThreeIdx = blocks.findIndex(b => b.header.globalVariables.slotNumber === slotThree); + expect(firstSlotThreeIdx).toEqual(0); + } + + // (7) During slotThree: proposerThree publishes → checkpointed advances on every node. + logger.warn(`Waiting for checkpointed chain to reach slot >= ${slotThree} on all nodes`); + await Promise.all( + nodes.map((node, idx) => + retryUntil( + async () => { + const tips = await node.getL2Tips(); + if (tips.checkpointed.checkpoint.number === 0) { + return false; + } + const block = await node.getBlock(tips.checkpointed.block.number); + return ( + !!block && block.header.globalVariables.slotNumber >= slotThree && tips.checkpointed.block.number > 0 + ); + }, + `node ${idx} checkpointed advanced to slot >= ${slotThree}`, + slotAdvanceTimeout, + 0.5, + ), + ), + ); + + // Sanity: the only fail events we tolerate are the deliberate skip-publish on the suppressed + // node for slotOne, the pipelined-discard knock-on from proposerTwo (its parent slotOne + // never landed), and proposer-rollup-check noise that any non-proposer emits when the rollup + // contract rejects them. + const unexpectedFailEvents = failEvents.filter(e => { + if ( + e.type === 'checkpoint-publish-failed' && + e.sequencerIndex === proposerOneNodeIndex + 2 && + e.slot === slotOne + ) { + return false; + } + if ( + e.type === 'checkpoint-publish-failed' && + e.sequencerIndex === proposerTwoNodeIndex + 2 && + e.slot === slotTwo + ) { + return false; + } + if (e.type === 'proposer-rollup-check-failed') { + return false; + } + return true; + }); + if (unexpectedFailEvents.length > 0) { + logger.error('Unexpected fail events from sequencers', unexpectedFailEvents); + } + expect(unexpectedFailEvents).toEqual([]); + }); +}); From b23fb63bb083d2d4a1195659257af1bd28f2afa9 Mon Sep 17 00:00:00 2001 From: spypsy Date: Tue, 5 May 2026 10:07:46 +0000 Subject: [PATCH 04/15] chore(p2p): drop unused ENR xxhash versioning path USE_XX_HASH was never true in production; discv5 already validates via checkCompressedComponentVersion. Removes xxhash/toBufferBE from versioning.ts and tests the string format only. --- yarn-project/p2p/src/versioning.test.ts | 15 ++++++----- yarn-project/p2p/src/versioning.ts | 36 +++---------------------- 2 files changed, 12 insertions(+), 39 deletions(-) diff --git a/yarn-project/p2p/src/versioning.test.ts b/yarn-project/p2p/src/versioning.test.ts index 26e5c6a25eb6..72495d82e672 100644 --- a/yarn-project/p2p/src/versioning.test.ts +++ b/yarn-project/p2p/src/versioning.test.ts @@ -1,11 +1,12 @@ import { EthAddress } from '@aztec/foundation/eth-address'; import type { ChainConfig } from '@aztec/stdlib/config'; +import { checkCompressedComponentVersion, compressComponentVersions } from '@aztec/stdlib/versioning'; import type { SignableENR } from '@nethermindeth/enr'; import { type MockProxy, mock } from 'jest-mock-extended'; import { AZTEC_ENR_KEY } from './types/index.js'; -import { checkAztecEnrVersion, setAztecEnrKey } from './versioning.js'; +import { setAztecEnrKey } from './versioning.js'; describe('versioning', () => { let enr: MockProxy; @@ -29,14 +30,16 @@ describe('versioning', () => { }; }); - it.each([true, false])('sets and compares versions with xxhash=%s', (useXxHash: boolean) => { - const versions = setAztecEnrKey(enr, chainConfig, useXxHash); + it('sets and compares compressed versions on ENR', () => { + const versions = setAztecEnrKey(enr, chainConfig); expect(versions.l1ChainId).toEqual(1); expect(versions.rollupVersion).toEqual(3); expect(versions.l1RollupAddress).toEqual(chainConfig.l1Contracts.rollupAddress); - expect(versionSet).toHaveLength(useXxHash ? 8 : 33); + expect(Buffer.from(versionSet!).toString()).toEqual(compressComponentVersions(versions)); - checkAztecEnrVersion(versionSet, versions); - expect(() => checkAztecEnrVersion(versionSet, { ...versions, l1ChainId: 3 })).toThrow(); + checkCompressedComponentVersion(Buffer.from(versionSet!).toString(), versions); + expect(() => + checkCompressedComponentVersion(Buffer.from(versionSet!).toString(), { ...versions, l1ChainId: 3 }), + ).toThrow(); }); }); diff --git a/yarn-project/p2p/src/versioning.ts b/yarn-project/p2p/src/versioning.ts index 8fa00935c944..adbad28b59ae 100644 --- a/yarn-project/p2p/src/versioning.ts +++ b/yarn-project/p2p/src/versioning.ts @@ -1,33 +1,21 @@ -import { toBufferBE } from '@aztec/foundation/bigint-buffer'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; import { protocolContractsHash } from '@aztec/protocol-contracts'; import type { ChainConfig } from '@aztec/stdlib/config'; -import { - type ComponentsVersions, - checkCompressedComponentVersion, - compressComponentVersions, - getComponentsVersionsFromConfig, -} from '@aztec/stdlib/versioning'; +import { compressComponentVersions, getComponentsVersionsFromConfig } from '@aztec/stdlib/versioning'; import type { SignableENR } from '@nethermindeth/enr'; -import xxhashFactory from 'xxhash-wasm'; import { AZTEC_ENR_CLIENT_VERSION_KEY, AZTEC_ENR_KEY } from './types/index.js'; -const USE_XX_HASH = false; // Enable to reduce the size of the ENR record for production -const XX_HASH_LEN = 8; -const xxhash = await xxhashFactory(); - /** Returns the component versions based on config and this build. */ export function getVersions(config: ChainConfig) { return getComponentsVersionsFromConfig(config, protocolContractsHash, getVKTreeRoot()); } /** Sets the aztec key on the ENR record with versioning info. */ -export function setAztecEnrKey(enr: SignableENR, config: ChainConfig, useXxHash = USE_XX_HASH) { +export function setAztecEnrKey(enr: SignableENR, config: ChainConfig) { const versions = getVersions(config); - const value = versionsToEnrValue(versions, useXxHash); - enr.set(AZTEC_ENR_KEY, value); + enr.set(AZTEC_ENR_KEY, Buffer.from(compressComponentVersions(versions))); return versions; } @@ -37,21 +25,3 @@ export function setAztecClientVersionEnrKey(enr: SignableENR, clientVersion: str enr.set(AZTEC_ENR_CLIENT_VERSION_KEY, Buffer.from(clientVersion)); } } - -/** Checks the given value from an ENR record against the expected versions. */ -export function checkAztecEnrVersion(enrValue: Buffer, expectedVersions: ComponentsVersions) { - if (enrValue.length === XX_HASH_LEN) { - const expected = versionsToEnrValue(expectedVersions, true); - if (!Buffer.from(enrValue).equals(expected)) { - throw new Error(`Expected ENR version ${expected.toString('hex')} but received ${enrValue.toString('hex')}`); - } - } else { - const actual = Buffer.from(enrValue).toString(); - checkCompressedComponentVersion(actual, expectedVersions); - } -} - -function versionsToEnrValue(versions: ComponentsVersions, useXxHash: boolean) { - const compressed = compressComponentVersions(versions); - return useXxHash ? toBufferBE(xxhash.h64(compressed), XX_HASH_LEN) : Buffer.from(compressed); -} From c536816489455bc5b4da6d0cc0617d209642d746 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 5 May 2026 09:42:32 -0300 Subject: [PATCH 05/15] fix: dedup attestation pool by payload hash (#22871) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes a slashing-soundness gap in the checkpoint attestation pool: two different checkpoint proposals (or attestations) at the same slot with identical archive root were considered equal in the attestation pool, since the pool keyed on `archive`. So we'd never slash for `DUPLICATE_PROPOSAL` / `DUPLICATE_ATTESTATION`. There were two scenarios where two different proposals could have the same archiveroot: a malicious or buggy node that sent two proposals with the same root but different content (ie an archive root that doesn't follow from the payload, ie an invalid one), or a malicious or buggy node that sent two equal proposals but with different `feeAssetPriceModifier` which is not covered by the archive root. Fixes A-1013 ## Pool changes - **Dedup by payload hash, not by archive.** Each equivocation position has two stores: a main store that keeps the *first* full entry seen at that position, and a parallel multimap that tracks the *set of distinct signed-payload hashes* seen there. A second distinct hash arriving at the same position bumps the multimap count to 2, which trips `tryAdd*`'s `count` and lets libp2p fire its duplicate callback / `WANT_TO_SLASH_EVENT`. Bytes of the equivocating payload are not retained. - `attestationPerSlotAndSigner` (full) + `attestationHashesPerSlotAndSigner` (hashes), keyed by `(slot, signer)`. - `checkpointProposalPerSlot` (full) + `checkpointProposalHashesPerSlot` (hashes), keyed by slot. - `blockProposalPerSlotAndIndex` (full) + `blockProposalHashesPerSlotAndIndex` (hashes), keyed by `(slot, indexWithinCheckpoint)`. - Hash is `keccak256(getPayloadToSign())`, **never over the signature**, so non-deterministic ECDSA re-signs of the same payload do not look like equivocation. - `CheckpointProposal.getPayloadHash()` hashes through the `ConsensusPayload` form so a checkpoint proposal's hash matches the hashes of the attestations that signed it (proposers and attesters sign different byte layouts of the same logical content). - Secondary `blockProposalSlotAndIndexPerArchive` index keeps `getBlockProposalByArchive(archive)` (used by the block-txs req/resp protocol) resolving by archive root without a wire-protocol change. The lookup now validates that the stored proposal's archive matches the requested one and warns + returns `undefined` on mismatch. - On-disk kv-store map names are unchanged; only the in-memory field names and the *value format* (now `0x`-prefixed payload-hash hex) are new. ## Branded payload-hash types - `CheckpointProposalHash` and `BlockProposalHash` introduced as `Branded<0x${string}>` in `foundation/branded-types`, so the two cannot be confused at the TS type level. `CheckpointAttestation.getPayloadHash()` returns `CheckpointProposalHash` (the attestation and its proposal sign the same payload). - `generateP2PMessageIdentifier()` on `CheckpointProposal` / `CheckpointAttestation` / `BlockProposal` now derives from the **same bytes** as `getPayloadHash()` (returning `Buffer32` rather than the `0x`-string), so libp2p's gossip dedup identity and the attestation pool's dedup identity agree. A shared `getPayloadHashBuffer()` helper on `BlockProposal` avoids double-hashing. ## Handler cache (validator-client) - `ProposalHandler.lastCheckpointValidationResult` now keys by `CheckpointProposalHash` instead of `(archive, slot)`. Without this fix, two proposals at the same slot+archive with a differing `feeAssetPriceModifier` would have shared a cached validation result and the second proposal would have skipped re-validation. ## API renames - `AttestationPool.getCheckpointProposal(slot)` — was `getCheckpointProposal(archive)`. - `AttestationPool.getBlockProposalByArchive(archive)` — was `getBlockProposal(archive)`; now validates the resolved proposal's archive matches. - `AttestationPool.getCheckpointAttestationsForSlotAndProposal(slot, proposalPayloadHash)` — was `(slot, archive)`. - `P2PApi.getCheckpointAttestationsForSlot(slot, proposalPayloadHash?)`. - Sentinel stores `proposalPayloadHash` alongside archive; validator client passes `proposal.getPayloadHash()` to filter attestations. ## Tests - New pool-level test verifies same-archive-different-`feeAssetPriceModifier` is recognised as an equivocation. - New libp2p_service tests verify the equivocation surfaces all the way to the slash callback for checkpoint proposals, attestations (incl. negative test for two distinct signers), and block proposals. - New proposal_handler test verifies the cache is not shared across proposals that differ only on `feeAssetPriceModifier`. --- .../aztec-node/src/sentinel/sentinel.ts | 30 +- .../src/branded-types/buffer32_hash.ts | 60 ++- .../foundation/src/branded-types/index.ts | 2 +- yarn-project/p2p/src/client/p2p_client.ts | 13 +- .../p2p_client.integration_batch_txs.test.ts | 2 +- .../p2p_client.integration_block_txs.test.ts | 12 +- .../attestation_pool/attestation_pool.ts | 475 +++++++++--------- .../attestation_pool_test_suite.ts | 185 ++++--- .../fisherman_attestation_validator.test.ts | 6 +- .../fisherman_attestation_validator.ts | 7 +- .../services/libp2p/libp2p_service.test.ts | 213 +++++++- .../p2p/src/services/libp2p/libp2p_service.ts | 2 +- .../block_txs/block_txs_handler.test.ts | 16 +- .../protocols/block_txs/block_txs_handler.ts | 2 +- .../p2p/src/test-helpers/testbench-utils.ts | 7 +- .../stdlib/src/interfaces/p2p.test.ts | 14 +- yarn-project/stdlib/src/interfaces/p2p.ts | 19 +- yarn-project/stdlib/src/p2p/block_proposal.ts | 19 +- .../stdlib/src/p2p/checkpoint_attestation.ts | 17 +- .../stdlib/src/p2p/checkpoint_proposal.ts | 24 +- .../stdlib/src/p2p/consensus_payload.ts | 9 + .../txe/src/state_machine/dummy_p2p_client.ts | 7 +- .../src/proposal_handler.test.ts | 24 + .../validator-client/src/proposal_handler.ts | 29 +- .../validator-client/src/validator.test.ts | 28 +- .../validator-client/src/validator.ts | 24 +- 26 files changed, 804 insertions(+), 442 deletions(-) diff --git a/yarn-project/aztec-node/src/sentinel/sentinel.ts b/yarn-project/aztec-node/src/sentinel/sentinel.ts index a5e1c1f5e8fe..611a67cb36bf 100644 --- a/yarn-project/aztec-node/src/sentinel/sentinel.ts +++ b/yarn-project/aztec-node/src/sentinel/sentinel.ts @@ -1,5 +1,11 @@ import type { EpochCache } from '@aztec/epoch-cache'; -import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { + BlockNumber, + CheckpointNumber, + CheckpointProposalHash, + EpochNumber, + SlotNumber, +} from '@aztec/foundation/branded-types'; import { countWhile, filterAsync, fromEntries, getEntries, mapValues } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; import { createLogger } from '@aztec/foundation/log'; @@ -23,7 +29,7 @@ import { } from '@aztec/stdlib/block'; import type { ChainConfig } from '@aztec/stdlib/config'; import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers'; -import type { CoordinationSignatureContext } from '@aztec/stdlib/p2p'; +import { ConsensusPayload, type CoordinationSignatureContext } from '@aztec/stdlib/p2p'; import type { SingleValidatorStats, ValidatorStats, @@ -64,7 +70,13 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme protected lastProcessedSlot: SlotNumber | undefined; protected slotNumberToCheckpoint: Map< SlotNumber, - { checkpointNumber: CheckpointNumber; archive: string; attestors: EthAddress[] } + { + checkpointNumber: CheckpointNumber; + archive: string; + /** Hex keccak256 of the consensus payload bytes; used to fetch matching p2p attestations. */ + proposalPayloadHash: CheckpointProposalHash; + attestors: EthAddress[]; + } > = new Map(); constructor( @@ -124,11 +136,17 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme } const checkpoint = event.checkpoint; - // Store mapping from slot to archive, checkpoint number, and attestors + // Store mapping from slot to archive, checkpoint number, attestors, and the consensus payload + // hash (used to query matching p2p attestations regardless of feeAssetPriceModifier variants). + const signatureContext = this.getSignatureContext(); + const proposalPayloadHash = CheckpointProposalHash.fromBuffer( + ConsensusPayload.fromCheckpoint(checkpoint.checkpoint, signatureContext).getPayloadHash(), + ); this.slotNumberToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, { checkpointNumber: checkpoint.checkpoint.number, archive: checkpoint.checkpoint.archive.root.toString(), - attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint, this.getSignatureContext()) + proposalPayloadHash, + attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint, signatureContext) .filter(a => a.status === 'recovered-from-signature') .map(a => a.address!), }); @@ -372,7 +390,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme // We gather from both p2p (contains the ones seen on the p2p layer) and archiver // (contains the ones synced from mined checkpoints, which we may have missed from p2p). const checkpoint = this.slotNumberToCheckpoint.get(slot); - const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot, checkpoint?.archive); + const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot, checkpoint?.proposalPayloadHash); // Filter out attestations with invalid signatures const p2pAttestors = p2pAttested.map(a => a.getSender()).filter((s): s is EthAddress => s !== undefined); const attestors = new Set( diff --git a/yarn-project/foundation/src/branded-types/buffer32_hash.ts b/yarn-project/foundation/src/branded-types/buffer32_hash.ts index ca4baea97ab7..3de688a55ccb 100644 --- a/yarn-project/foundation/src/branded-types/buffer32_hash.ts +++ b/yarn-project/foundation/src/branded-types/buffer32_hash.ts @@ -1,42 +1,38 @@ -import type { BaseBuffer32 } from '../buffer/buffer32.js'; -import { Buffer32 } from '../buffer/buffer32.js'; import type { Branded } from './types.js'; -/** A branded Buffer32 representing a block proposal hash, used for p2p deduplication. */ -export type BlockProposalHash = Branded; - -/** Creates a BlockProposalHash from a BaseBuffer32. */ -export function BlockProposalHash(buf: BaseBuffer32): BlockProposalHash { - return buf as BlockProposalHash; +/** + * A branded `0x`-prefixed hex string representing a checkpoint proposal payload hash. + * + * A `CheckpointProposal` and the matching `CheckpointAttestation` sign the same + * `ConsensusPayload`, so they share this hash type. Used by the p2p attestation + * pool to dedup signed payloads and detect equivocations. + */ +export type CheckpointProposalHash = Branded<`0x${string}`, 'CheckpointProposalHash'>; + +/** Brands a `0x`-prefixed hex string as a CheckpointProposalHash. */ +export function CheckpointProposalHash(s: `0x${string}`): CheckpointProposalHash { + return s as CheckpointProposalHash; } -/** Creates a BlockProposalHash from a raw Buffer. */ -BlockProposalHash.fromBuffer = function (buf: Buffer): BlockProposalHash { - return new Buffer32(buf) as unknown as BlockProposalHash; -}; - -/** A branded Buffer32 representing a checkpoint proposal hash, used for p2p deduplication. */ -export type CheckpointProposalHash = Branded; - -/** Creates a CheckpointProposalHash from a BaseBuffer32. */ -export function CheckpointProposalHash(buf: BaseBuffer32): CheckpointProposalHash { - return buf as CheckpointProposalHash; -} - -/** Creates a CheckpointProposalHash from a raw Buffer. */ +/** Constructs a CheckpointProposalHash from a raw 32-byte hash buffer. */ CheckpointProposalHash.fromBuffer = function (buf: Buffer): CheckpointProposalHash { - return new Buffer32(buf) as unknown as CheckpointProposalHash; + return `0x${buf.toString('hex')}` as CheckpointProposalHash; }; -/** A branded Buffer32 representing a checkpoint attestation hash, used for p2p deduplication. */ -export type CheckpointAttestationHash = Branded; - -/** Creates a CheckpointAttestationHash from a BaseBuffer32. */ -export function CheckpointAttestationHash(buf: BaseBuffer32): CheckpointAttestationHash { - return buf as CheckpointAttestationHash; +/** + * A branded `0x`-prefixed hex string representing a block proposal payload hash. + * + * Used by the p2p attestation pool to dedup signed payloads at a given + * `(slot, indexWithinCheckpoint)` and detect equivocations. + */ +export type BlockProposalHash = Branded<`0x${string}`, 'BlockProposalHash'>; + +/** Brands a `0x`-prefixed hex string as a BlockProposalHash. */ +export function BlockProposalHash(s: `0x${string}`): BlockProposalHash { + return s as BlockProposalHash; } -/** Creates a CheckpointAttestationHash from a raw Buffer. */ -CheckpointAttestationHash.fromBuffer = function (buf: Buffer): CheckpointAttestationHash { - return new Buffer32(buf) as unknown as CheckpointAttestationHash; +/** Constructs a BlockProposalHash from a raw 32-byte hash buffer. */ +BlockProposalHash.fromBuffer = function (buf: Buffer): BlockProposalHash { + return `0x${buf.toString('hex')}` as BlockProposalHash; }; diff --git a/yarn-project/foundation/src/branded-types/index.ts b/yarn-project/foundation/src/branded-types/index.ts index 774680285f66..ab7ecd344073 100644 --- a/yarn-project/foundation/src/branded-types/index.ts +++ b/yarn-project/foundation/src/branded-types/index.ts @@ -1,5 +1,5 @@ export { BlockNumber, BlockNumberSchema, BlockNumberPositiveSchema } from './block_number.js'; -export { BlockProposalHash, CheckpointAttestationHash, CheckpointProposalHash } from './buffer32_hash.js'; +export { BlockProposalHash, CheckpointProposalHash } from './buffer32_hash.js'; export { CheckpointNumber, CheckpointNumberSchema, CheckpointNumberPositiveSchema } from './checkpoint_number.js'; export { EpochNumber, EpochNumberSchema } from './epoch.js'; export { IndexWithinCheckpoint, IndexWithinCheckpointSchema } from './index_within_checkpoint.js'; diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 65f661a4023e..914e08a6b08f 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -1,5 +1,10 @@ import type { EpochCacheInterface } from '@aztec/epoch-cache'; -import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { + BlockNumber, + CheckpointNumber, + type CheckpointProposalHash, + SlotNumber, +} from '@aztec/foundation/branded-types'; import { createLogger } from '@aztec/foundation/log'; import { RunningPromise } from '@aztec/foundation/promise'; import { DateProvider } from '@aztec/foundation/timer'; @@ -379,10 +384,10 @@ export class P2PClient extends WithTracer implements P2P { public async getCheckpointAttestationsForSlot( slot: SlotNumber, - proposalId?: string, + proposalPayloadHash?: CheckpointProposalHash, ): Promise { - return await (proposalId - ? this.attestationPool.getCheckpointAttestationsForSlotAndProposal(slot, proposalId) + return await (proposalPayloadHash + ? this.attestationPool.getCheckpointAttestationsForSlotAndProposal(slot, proposalPayloadHash) : this.attestationPool.getCheckpointAttestationsForSlot(slot)); } diff --git a/yarn-project/p2p/src/client/test/p2p_client.integration_batch_txs.test.ts b/yarn-project/p2p/src/client/test/p2p_client.integration_batch_txs.test.ts index f6e76dbef7c8..1858f94786ac 100644 --- a/yarn-project/p2p/src/client/test/p2p_client.integration_batch_txs.test.ts +++ b/yarn-project/p2p/src/client/test/p2p_client.integration_batch_txs.test.ts @@ -221,7 +221,7 @@ describe('p2p client integration batch txs', () => { const peerIds = clients.map(client => (client as any).p2pService.node.peerId); connectionSampler.getPeerListSortedByConnectionCountAsc.mockReturnValue(peerIds); - attestationPool.getBlockProposal.mockResolvedValue(blockProposal); + attestationPool.getBlockProposalByArchive.mockResolvedValue(blockProposal); // Client 0 is missing all transactions const missingTxHashes = txHashes; diff --git a/yarn-project/p2p/src/client/test/p2p_client.integration_block_txs.test.ts b/yarn-project/p2p/src/client/test/p2p_client.integration_block_txs.test.ts index 9f8464e4dd08..50123143334d 100644 --- a/yarn-project/p2p/src/client/test/p2p_client.integration_block_txs.test.ts +++ b/yarn-project/p2p/src/client/test/p2p_client.integration_block_txs.test.ts @@ -108,7 +108,7 @@ describe('p2p client integration block txs protocol ', () => { txs = await Promise.all(times(5, i => createMockTxWithMetadata(p2pBaseConfig, i))); txHashes = await Promise.all(txs.map(tx => tx.getTxHash())); blockProposal = await createBlockProposal(BlockNumber(blockNumber), archiveRoot, txHashes); - attestationPool.getBlockProposal.mockResolvedValue(blockProposal); + attestationPool.getBlockProposalByArchive.mockResolvedValue(blockProposal); }); afterEach(async () => { @@ -153,7 +153,7 @@ describe('p2p client integration block txs protocol ', () => { }; it('responds with NOT_FOUND when peer does not have the requested block proposal', async () => { - attestationPool.getBlockProposal.mockResolvedValue(undefined); + attestationPool.getBlockProposalByArchive.mockResolvedValue(undefined); const missing = new TxHashArray(...Array.from({ length: 4 }, () => TxHash.random())); const blockProposal = await createBlockProposal(blockNumber, Fr.random(), missing); @@ -326,7 +326,7 @@ describe('p2p client integration block txs protocol ', () => { it('responds with txs when peer does not have proposal but has txs (includeFullTxHashes=true)', async () => { // Peer doesn't have the block proposal - attestationPool.getBlockProposal.mockResolvedValue(undefined); + attestationPool.getBlockProposalByArchive.mockResolvedValue(undefined); // But peer has some of the requested txs in their pool const availableTxs = [txs[1], txs[3]]; @@ -355,7 +355,7 @@ describe('p2p client integration block txs protocol ', () => { it('responds with partial txs when peer does not have proposal (includeFullTxHashes=true)', async () => { // Peer doesn't have the block proposal - attestationPool.getBlockProposal.mockResolvedValue(undefined); + attestationPool.getBlockProposalByArchive.mockResolvedValue(undefined); // Peer has only one of the requested txs const availableTx = txs[2]; @@ -384,7 +384,7 @@ describe('p2p client integration block txs protocol ', () => { it('responds with empty txs when peer does not have proposal or txs (includeFullTxHashes=true)', async () => { // Peer doesn't have the block proposal - attestationPool.getBlockProposal.mockResolvedValue(undefined); + attestationPool.getBlockProposalByArchive.mockResolvedValue(undefined); // Peer also doesn't have any of the requested txs txPool.getTxsByHash.mockResolvedValue([]); @@ -403,7 +403,7 @@ describe('p2p client integration block txs protocol ', () => { it('still responds with NOT_FOUND when peer does not have proposal and includeFullTxHashes=false', async () => { // Peer doesn't have the block proposal - attestationPool.getBlockProposal.mockResolvedValue(undefined); + attestationPool.getBlockProposalByArchive.mockResolvedValue(undefined); // Even if peer has the txs in pool const hashToTx = new Map(txs.map((tx, i) => [txHashes[i].toString(), tx])); diff --git a/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts b/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts index 69c2e02f8e55..109e472aa35f 100644 --- a/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts +++ b/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool.ts @@ -1,5 +1,4 @@ -import { IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types'; -import { Fr } from '@aztec/foundation/curves/bn254'; +import type { BlockProposalHash, CheckpointProposalHash, SlotNumber } from '@aztec/foundation/branded-types'; import { toArray } from '@aztec/foundation/iterable'; import { createLogger } from '@aztec/foundation/log'; import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap } from '@aztec/kv-store'; @@ -15,14 +14,14 @@ import { PoolInstrumentation, PoolName, type PoolStatsCallback } from '../instru /** Result of trying to add an item (proposal or attestation) to the pool */ export type TryAddResult = { - /** Whether the item was added */ + /** Whether the item was added to a main store. False when the slot/position/(slot,signer) already had a stored entry, even if a new equivocation hash was tracked. */ added: boolean; - /** Whether the exact item already existed */ + /** Whether the exact signed payload (matched by payload hash) already existed in the pool. */ alreadyExists: boolean; - /** Count of items for the position. Meaning varies by method: - * - tryAddBlockProposal: proposals at (slot, indexWithinCheckpoint) - * - tryAddCheckpointProposal: proposals at slot - * - tryAddCheckpointAttestation: attestations by this signer for this slot */ + /** Number of distinct signed-payload hashes seen for the position. Meaning varies by method: + * - tryAddBlockProposal: distinct payload hashes at (slot, indexWithinCheckpoint) + * - tryAddCheckpointProposal: distinct payload hashes at slot + * - tryAddCheckpointAttestation: distinct payload hashes by this signer for this slot */ count: number; }; @@ -35,7 +34,7 @@ export const MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER = 2; export type AttestationPoolApi = Pick< AttestationPool, | 'tryAddBlockProposal' - | 'getBlockProposal' + | 'getBlockProposalByArchive' | 'tryAddCheckpointProposal' | 'getCheckpointProposal' | 'addOwnCheckpointAttestations' @@ -52,31 +51,46 @@ export type AttestationPoolApi = Pick< * * Attestations and proposals observed via the p2p network are stored for requests * from the validator to produce a block, or to serve to other peers. + * + * Equivocation detection: each main store holds at most one entry per equivocation + * position (one checkpoint proposal per slot, one block proposal per (slot, position), + * one attestation per (slot, signer)). Distinct *signed payload hashes* arriving at + * the same position are tracked in the matching index multimap so the equivocation + * count reaches 2 even when archive collides on `feeAssetPriceModifier` variants. */ export class AttestationPool { private metrics: PoolInstrumentation; - // Checkpoint attestations from attestation key (slot-proposalId-signer) to serialized CheckpointAttestation - // Keys are lexicographically sortable allowing range queries by slot or by (slot, proposalId) - private checkpointAttestations: AztecAsyncMap; + // Checkpoint attestations from `${paddedSlot}-${signer}` to serialized CheckpointAttestation. + // Stores the first attestation seen per (slot, signer); subsequent distinct payload + // hashes from the same signer are tracked only in `attestationHashesPerSlotAndSigner` + // for equivocation detection. + private attestationPerSlotAndSigner: AztecAsyncMap; + + // Distinct payload hashes seen per (slot, signer) for tracking attestation equivocations. + // Key: `${paddedSlot}-${signerAddress}`, Value: CheckpointProposalHash (`0x`-prefixed hex) + private attestationHashesPerSlotAndSigner: AztecAsyncMultiMap; - // Checkpoint proposals from proposal archive to serialized CheckpointProposal - private checkpointProposals: AztecAsyncMap; + // Checkpoint proposals from slot number to serialized CheckpointProposal. + // Stores the first proposal seen per slot. + private checkpointProposalPerSlot: AztecAsyncMap; - // Checkpoint proposals indexed by slot for querying all proposals in a slot - // Key: slot number, Value: proposal archive strings - private checkpointProposalsForSlot: AztecAsyncMultiMap; + // Distinct payload hashes seen per slot. Hash collision = duplicate. + // Hash count reaching 2 = equivocation. + // Key: slot number, Value: CheckpointProposalHash (`0x`-prefixed hex) + private checkpointProposalHashesPerSlot: AztecAsyncMultiMap; - // Block proposals from proposal archive to serialized BlockProposal - private blockProposals: AztecAsyncMap; + // Block proposals from positionKey to serialized BlockProposal. + // Stores the first proposal seen per (slot, indexWithinCheckpoint). + private blockProposalPerSlotAndIndex: AztecAsyncMap; - // Block proposals indexed by slot and index-within-checkpoint for duplicate detection - // Key: (slot << 10) | indexWithinCheckpoint, Value: archive string - private blockProposalsForSlotAndIndex: AztecAsyncMultiMap; + // Distinct payload hashes seen per (slot, indexWithinCheckpoint). + // Key: slot * (1 << INDEX_BITS) + indexWithinCheckpoint, Value: BlockProposalHash (`0x`-prefixed hex) + private blockProposalHashesPerSlotAndIndex: AztecAsyncMultiMap; - // Checkpoint attestations indexed by (slot, signer) for tracking attestations per (slot, signer) for duplicate detection - // Key: `${Fr(slot).toString()}-${signerAddress}` string (padded for lexicographic ordering), Value: `proposalId` strings - private checkpointAttestationsPerSlotAndSigner: AztecAsyncMultiMap; + // Secondary index from archive root to positionKey, so that the block-txs req/resp + // handler can still resolve a stored proposal by archive root. + private blockProposalSlotAndIndexPerArchive: AztecAsyncMap; constructor( private store: AztecAsyncKVStore, @@ -84,76 +98,65 @@ export class AttestationPool { private log = createLogger('aztec:attestation_pool'), ) { // Initialize block proposal storage - this.blockProposals = store.openMap('proposals'); - this.blockProposalsForSlotAndIndex = store.openMultiMap('block_proposals_for_slot_and_index'); + this.blockProposalPerSlotAndIndex = store.openMap('proposals'); + this.blockProposalHashesPerSlotAndIndex = store.openMultiMap('block_proposals_for_slot_and_index'); + this.blockProposalSlotAndIndexPerArchive = store.openMap('block_proposals_by_archive'); // Initialize checkpoint attestations storage - this.checkpointAttestations = store.openMap('checkpoint_attestations'); - this.checkpointAttestationsPerSlotAndSigner = store.openMultiMap('checkpoint_attestations_per_slot_and_signer'); + this.attestationPerSlotAndSigner = store.openMap('checkpoint_attestations'); + this.attestationHashesPerSlotAndSigner = store.openMultiMap('checkpoint_attestations_per_slot_and_signer'); // Initialize checkpoint proposal storage - this.checkpointProposals = store.openMap('checkpoint_proposals'); - this.checkpointProposalsForSlot = store.openMultiMap('checkpoint_proposals_for_slot'); + this.checkpointProposalPerSlot = store.openMap('checkpoint_proposals'); + this.checkpointProposalHashesPerSlot = store.openMultiMap('checkpoint_proposals_for_slot'); this.metrics = new PoolInstrumentation(telemetry, PoolName.ATTESTATION_POOL, this.poolStats); } private poolStats: PoolStatsCallback = async () => { return { - itemCount: await this.checkpointAttestations.sizeAsync(), + itemCount: await this.attestationPerSlotAndSigner.sizeAsync(), }; }; /** Returns whether the pool is empty. */ public async isEmpty(): Promise { - for await (const _ of this.checkpointAttestations.entriesAsync()) { + for await (const _ of this.attestationPerSlotAndSigner.entriesAsync()) { return false; } - for await (const _ of this.blockProposals.entriesAsync()) { + for await (const _ of this.blockProposalPerSlotAndIndex.entriesAsync()) { return false; } return true; } - private getProposalKey(slot: number | bigint | Fr | string, proposalId: Fr | string | Buffer): string { - const slotStr = typeof slot === 'string' ? slot : new Fr(slot).toString(); - const proposalIdStr = - typeof proposalId === 'string' - ? proposalId - : Buffer.isBuffer(proposalId) - ? Fr.fromBuffer(proposalId).toString() - : proposalId.toString(); + /** Number of bits reserved for indexWithinCheckpoint in position keys. */ + private static readonly INDEX_BITS = 10; + /** Maximum indexWithinCheckpoint value (2^10 - 1 = 1023). */ + private static readonly MAX_INDEX = (1 << AttestationPool.INDEX_BITS) - 1; + /** Decimal digits used to left-pad slot numbers in string keys. + * 10 digits ≈ 3500 years at 36 s/slot, leaving ample headroom. */ + private static readonly SLOT_PAD_DIGITS = 10; - return `${slotStr}-${proposalIdStr}`; + /** Fixed-width decimal slot string for use in composite string keys. */ + private slotPaddedKey(slot: SlotNumber | number): string { + return slot.toString().padStart(AttestationPool.SLOT_PAD_DIGITS, '0'); } - private getAttestationKey(slot: number | bigint | Fr | string, proposalId: Fr | string, address: string): string { - return `${this.getProposalKey(slot, proposalId)}-${address}`; + /** Key for the per-(slot, signer) attestation main store and equivocation index. */ + private getSlotSignerKey(slot: SlotNumber, signerAddress: string): string { + return `${this.slotPaddedKey(slot)}-${signerAddress}`; } - /** Returns range bounds for querying all attestations for a given slot. */ + /** + * Returns range bounds for querying all attestations for a given slot. + * Fixed-width padding ensures the slot prefix sorts cleanly, so using the next + * slot's prefix as the upper bound captures exactly the current slot's entries. + */ private getAttestationKeyRangeForSlot(slot: SlotNumber): { start: string; end: string } { - const slotStr = new Fr(slot).toString(); - return { start: `${slotStr}-`, end: `${slotStr}-Z` }; // 'Z' sorts after any hex character + return { start: `${this.slotPaddedKey(slot)}-`, end: `${this.slotPaddedKey(slot + 1)}-` }; } - /** Returns range bounds for querying all attestations for a given (slot, proposalId). */ - private getAttestationKeyRangeForProposal(slot: SlotNumber, proposalId: string): { start: string; end: string } { - const proposalKey = this.getProposalKey(slot, proposalId); - return { start: `${proposalKey}-`, end: `${proposalKey}-Z` }; - } - - /** Creates a key for the per-signer-per-slot attestation index. Uses padded slot for lexicographic ordering. */ - private getSlotSignerKey(slot: SlotNumber, signerAddress: string): string { - const slotStr = new Fr(slot).toString(); - return `${slotStr}-${signerAddress}`; - } - - /** Number of bits reserved for indexWithinCheckpoint in position keys. */ - private static readonly INDEX_BITS = 10; - /** Maximum indexWithinCheckpoint value (2^10 - 1 = 1023). */ - private static readonly MAX_INDEX = (1 << AttestationPool.INDEX_BITS) - 1; - /** Creates a position key for block proposals: slot * 1024 + indexWithinCheckpoint. * Uses multiplication instead of bit-shift to avoid 32-bit signed integer overflow * (bit-shift overflows after slot ~2^21, roughly 278 days of uptime). */ @@ -166,50 +169,64 @@ export class AttestationPool { return slot * (1 << AttestationPool.INDEX_BITS) + indexWithinCheckpoint; } + /** Returns true if the multimap already contains the given value for the given key. */ + private async multimapHasValue( + map: AztecAsyncMultiMap, + key: TKey, + value: TValue, + ): Promise { + const values = await toArray(map.getValuesAsync(key)); + return values.includes(value); + } + /** * Attempts to add a block proposal to the pool. * - * This method performs validation and addition in a single call: - * - Checks if the proposal already exists (returns alreadyExists: true if so) - * - Checks if the position has reached the proposal cap (returns added: false if so) - * - Adds the proposal if validation passes + * - Detects duplicates by signed-payload hash (not archive); a re-broadcast of the + * exact same signed payload returns `alreadyExists: true`. + * - Distinct payload hashes at the same `(slot, indexWithinCheckpoint)` are tracked + * in the equivocation index. The first hash also stores the proposal bytes; later + * distinct hashes only bump `count` so libp2p can fire its duplicate callback. * * @param blockProposal - The block proposal to add * @returns Result indicating whether the proposal was added and duplicate detection info */ public async tryAddBlockProposal(blockProposal: BlockProposal): Promise { return await this.store.transactionAsync(async () => { - const proposalId = blockProposal.archive.toString(); - - // Check if already exists - const alreadyExists = await this.blockProposals.hasAsync(proposalId); - if (alreadyExists) { - const count = await this.getBlockProposalCountForPosition( - blockProposal.slotNumber, - blockProposal.indexWithinCheckpoint, - ); + const positionKey = this.getBlockPositionKey(blockProposal.slotNumber, blockProposal.indexWithinCheckpoint); + const payloadHash = blockProposal.getPayloadHash(); + + // Hash already tracked => exact same signed payload was already received. + if (await this.multimapHasValue(this.blockProposalHashesPerSlotAndIndex, positionKey, payloadHash)) { + const count = await this.blockProposalHashesPerSlotAndIndex.getValueCountAsync(positionKey); return { added: false, alreadyExists: true, count }; } - // Get current count for position and check cap, do not add if exceeded - const count = await this.getBlockProposalCountForPosition( - blockProposal.slotNumber, - blockProposal.indexWithinCheckpoint, - ); - + // Cap reached for this position (no more new payload hashes accepted). + const count = await this.blockProposalHashesPerSlotAndIndex.getValueCountAsync(positionKey); if (count >= MAX_BLOCK_PROPOSALS_PER_POSITION) { return { added: false, alreadyExists: false, count }; } - // Add the proposal - await this.addBlockProposal(blockProposal); + // Track the new payload hash for equivocation detection. + await this.blockProposalHashesPerSlotAndIndex.set(positionKey, payloadHash); + + // Only the first distinct payload at this position is stored; later equivocations + // are detected via the multimap but their payload bytes are not retained. + const alreadyHasStored = await this.blockProposalPerSlotAndIndex.hasAsync(positionKey); + if (!alreadyHasStored) { + await this.blockProposalPerSlotAndIndex.set(positionKey, blockProposal.withoutSignedTxs().toBuffer()); + await this.blockProposalSlotAndIndexPerArchive.set(blockProposal.archive.toString(), positionKey); + } this.log.debug( `Added block proposal for slot ${blockProposal.slotNumber} and index ${blockProposal.indexWithinCheckpoint}`, { - proposalId, + archive: blockProposal.archive.toString(), + payloadHash, slotNumber: blockProposal.slotNumber, indexWithinCheckpoint: blockProposal.indexWithinCheckpoint, + stored: !alreadyHasStored, }, ); @@ -217,60 +234,60 @@ export class AttestationPool { }); } - /** Gets the count of block proposals for a given position (slot, indexWithinCheckpoint). */ - private getBlockProposalCountForPosition( - slot: SlotNumber, - indexWithinCheckpoint: IndexWithinCheckpoint, - ): Promise { - const positionKey = this.getBlockPositionKey(slot, indexWithinCheckpoint); - return this.blockProposalsForSlotAndIndex.getValueCountAsync(positionKey); - } - - /** Internal method - must be called within a transaction. */ - private async addBlockProposal(blockProposal: BlockProposal): Promise { - const proposalId = blockProposal.archive.toString(); - // Strip signedTxs before storing to avoid persisting full tx data - await this.blockProposals.set(proposalId, blockProposal.withoutSignedTxs().toBuffer()); - - // Index by slot and position for duplicate detection - const positionKey = this.getBlockPositionKey(blockProposal.slotNumber, blockProposal.indexWithinCheckpoint); - await this.blockProposalsForSlotAndIndex.set(positionKey, proposalId); - } - /** - * Get block proposal by its ID. + * Get block proposal by archive root. * - * @param id - The ID of the block proposal to retrieve. The ID is proposal.payload.archive + * Resolves the archive root to its `(slot, indexWithinCheckpoint)` via a secondary + * index, then fetches the stored proposal (if any). Returns the *first* proposal + * seen at that position, even if a later equivocating payload was tracked. + * Validates that the stored proposal's archive matches the requested one before + * returning, guarding against secondary-index corruption or position-key reuse. * - * @return The block proposal if it exists, otherwise undefined. + * @param archiveRoot - The archive root to look up + * @return The block proposal if it exists and its archive matches, otherwise undefined. */ - public async getBlockProposal(id: string): Promise { - const buffer = await this.blockProposals.getAsync(id); + public async getBlockProposalByArchive(archiveRoot: string): Promise { + const positionKey = await this.blockProposalSlotAndIndexPerArchive.getAsync(archiveRoot); + if (positionKey === undefined) { + return undefined; + } + const buffer = await this.blockProposalPerSlotAndIndex.getAsync(positionKey); + if (!buffer || buffer.length === 0) { + return undefined; + } + let proposal: BlockProposal; try { - if (buffer && buffer.length > 0) { - return BlockProposal.fromBuffer(buffer); - } + proposal = BlockProposal.fromBuffer(buffer); } catch { return undefined; } - - return undefined; + const storedArchive = proposal.archive.toString(); + if (storedArchive !== archiveRoot) { + this.log.warn(`Stored block proposal archive does not match requested archive root`, { + requestedArchive: archiveRoot, + storedArchive, + positionKey, + }); + return undefined; + } + return proposal; } /** Checks if any block proposals exist for a given slot (at index 0). */ public async hasBlockProposalsForSlot(slot: SlotNumber): Promise { const positionKey = this.getBlockPositionKey(slot, 0); - const count = await this.blockProposalsForSlotAndIndex.getValueCountAsync(positionKey); + const count = await this.blockProposalHashesPerSlotAndIndex.getValueCountAsync(positionKey); return count > 0; } /** * Attempts to add a checkpoint proposal to the pool. * - * This method performs validation and addition in a single call: - * - Checks if the proposal already exists (returns alreadyExists: true if so) - * - Checks if the slot has reached the proposal cap (returns added: false if so) - * - Adds the proposal if validation passes + * - Detects duplicates by signed-payload hash (not archive); a re-broadcast of the + * exact same signed payload returns `alreadyExists: true`. + * - Distinct payload hashes at the same slot are tracked in the equivocation index. + * Only the first distinct payload's bytes are stored; later distinct hashes bump + * `count` so libp2p can fire its duplicate callback. * * Note: This method only handles the CheckpointProposalCore. If the original * CheckpointProposal contains a lastBlock, the caller should extract it via @@ -280,56 +297,52 @@ export class AttestationPool { * @returns Result indicating whether the proposal was added and duplicate detection info */ public async tryAddCheckpointProposal(proposal: CheckpointProposalCore): Promise { - const result = await this.store.transactionAsync(async () => { - const proposalId = proposal.archive.toString(); + return await this.store.transactionAsync(async () => { + const slot = proposal.slotNumber; + const payloadHash = proposal.getPayloadHash(); - // Check if already exists - const alreadyExists = await this.checkpointProposals.hasAsync(proposalId); - if (alreadyExists) { - const count = await this.checkpointProposalsForSlot.getValueCountAsync(proposal.slotNumber); + if (await this.multimapHasValue(this.checkpointProposalHashesPerSlot, slot, payloadHash)) { + const count = await this.checkpointProposalHashesPerSlot.getValueCountAsync(slot); return { added: false, alreadyExists: true, count }; } - // Get current count for slot and check cap - const count = await this.checkpointProposalsForSlot.getValueCountAsync(proposal.slotNumber); + const count = await this.checkpointProposalHashesPerSlot.getValueCountAsync(slot); if (count >= MAX_CHECKPOINT_PROPOSALS_PER_SLOT) { return { added: false, alreadyExists: false, count }; } - // Add the proposal if cap not exceeded - await this.addCheckpointProposal(proposal); + // Track the new payload hash for equivocation detection. + await this.checkpointProposalHashesPerSlot.set(slot, payloadHash); + + // Only the first distinct payload at this slot is stored; later equivocations + // are detected via the multimap but their payload bytes are not retained. + const alreadyHasStored = await this.checkpointProposalPerSlot.hasAsync(slot); + if (!alreadyHasStored) { + await this.checkpointProposalPerSlot.set(slot, proposal.toBuffer()); + } - this.log.debug(`Added checkpoint proposal for slot ${proposal.slotNumber}`, { - proposalId, - slotNumber: proposal.slotNumber, + this.log.debug(`Added checkpoint proposal for slot ${slot}`, { + archive: proposal.archive.toString(), + payloadHash, + slotNumber: slot, + stored: !alreadyHasStored, }); return { added: true, alreadyExists: false, count: count + 1 }; }); - - return result; - } - - /** Internal method - must be called within a transaction. */ - private async addCheckpointProposal(proposal: CheckpointProposalCore): Promise { - const slotKey = proposal.slotNumber; - const proposalId = proposal.archive.toString(); - - await this.checkpointProposalsForSlot.set(slotKey, proposalId); - await this.checkpointProposals.set(proposalId, proposal.toBuffer()); } /** - * Get checkpoint proposal by its ID. + * Get the (first) checkpoint proposal stored for the given slot. * * Returns a CheckpointProposalCore (without lastBlock info) since the lastBlock * is extracted and stored separately as a BlockProposal when added. * - * @param id - The ID of the checkpoint proposal to retrieve (proposal.archive) - * @return The checkpoint proposal core if it exists, otherwise undefined. + * @param slot - The slot to look up + * @return The checkpoint proposal core if one is stored, otherwise undefined. */ - public async getCheckpointProposal(id: string): Promise { - const buffer = await this.checkpointProposals.getAsync(id); + public async getCheckpointProposal(slot: SlotNumber): Promise { + const buffer = await this.checkpointProposalPerSlot.getAsync(slot); try { if (buffer && buffer.length > 0) { return CheckpointProposal.fromBuffer(buffer); @@ -343,13 +356,13 @@ export class AttestationPool { /** * Adds own checkpoint attestations to the pool. - * Skips validations on number of checkpoint attestations stored for the given slot. + * Skips per-signer cap and equivocation tracking; the caller is trusted. + * Each (slot, signer) gets a single stored attestation; later additions overwrite. */ public async addOwnCheckpointAttestations(attestations: CheckpointAttestation[]): Promise { await this.store.transactionAsync(async () => { for (const attestation of attestations) { const slotNumber = attestation.payload.header.slotNumber; - const proposalId = attestation.archive.toString(); const sender = attestation.getSender(); // Skip attestations with invalid signatures @@ -357,22 +370,30 @@ export class AttestationPool { this.log.warn(`Skipping own checkpoint attestation with invalid signature for slot ${slotNumber}`, { signature: attestation.signature.toString(), slotNumber, - proposalId, + archive: attestation.archive.toString(), }); continue; } const address = sender.toString(); - const ownKey = this.getAttestationKey(slotNumber, proposalId, address); + const ownKey = this.getSlotSignerKey(slotNumber, address); + const payloadHash = attestation.getPayloadHash(); - await this.checkpointAttestations.set(ownKey, attestation.toBuffer()); + await this.attestationPerSlotAndSigner.set(ownKey, attestation.toBuffer()); this.metrics.trackMempoolItemAdded(ownKey); + // Track our own payload hash so that an equivocating attestation from another + // peer at the same (slot, signer) is detected as a duplicate. + if (!(await this.multimapHasValue(this.attestationHashesPerSlotAndSigner, ownKey, payloadHash))) { + await this.attestationHashesPerSlotAndSigner.set(ownKey, payloadHash); + } + this.log.debug(`Added own checkpoint attestation for slot ${slotNumber} from ${address}`, { signature: attestation.signature.toString(), slotNumber, address, - proposalId, + archive: attestation.archive.toString(), + payloadHash, }); } }); @@ -381,6 +402,10 @@ export class AttestationPool { /** * Get all checkpoint attestations for a given slot. * + * Returns one attestation per (slot, signer) — the first seen for each signer. + * Later equivocating attestations from the same signer are tracked in the index + * but their bytes are not retained. + * * @param slot - The slot to query * @return CheckpointAttestations */ @@ -388,7 +413,7 @@ export class AttestationPool { const range = this.getAttestationKeyRangeForSlot(slot); const attestations: CheckpointAttestation[] = []; - for await (const [_, buf] of this.checkpointAttestations.entriesAsync(range)) { + for await (const [_, buf] of this.attestationPerSlotAndSigner.entriesAsync(range)) { attestations.push(CheckpointAttestation.fromBuffer(buf)); } @@ -396,24 +421,19 @@ export class AttestationPool { } /** - * Get checkpoint attestations for slot and given proposal. + * Get checkpoint attestations for a slot whose signed payload matches the given + * proposal payload hash. * * @param slot - The slot to query - * @param proposalId - The proposal to query - * @return CheckpointAttestations + * @param proposalPayloadHash - Hex-encoded keccak256 of the target proposal's signed payload + * @return CheckpointAttestations whose `getPayloadHash()` matches `proposalPayloadHash` */ public async getCheckpointAttestationsForSlotAndProposal( slot: SlotNumber, - proposalId: string, + proposalPayloadHash: CheckpointProposalHash, ): Promise { - const range = this.getAttestationKeyRangeForProposal(slot, proposalId); - const attestations: CheckpointAttestation[] = []; - - for await (const [_, buf] of this.checkpointAttestations.entriesAsync(range)) { - attestations.push(CheckpointAttestation.fromBuffer(buf)); - } - - return attestations; + const all = await this.getCheckpointAttestationsForSlot(slot); + return all.filter(att => att.getPayloadHash() === proposalPayloadHash); } /** @@ -427,43 +447,46 @@ export class AttestationPool { let numberOfBlockProposals = 0; await this.store.transactionAsync(async () => { - // Delete checkpoint attestations with slot < oldestSlot - // Attestation keys start with Fr(slot).toString(), so we use end bound of Fr(oldestSlot).toString() - const attestationEndKey = new Fr(oldestSlot).toString(); - for await (const key of this.checkpointAttestations.keysAsync({ end: attestationEndKey })) { - await this.checkpointAttestations.delete(key); + const oldestSlotPadded = this.slotPaddedKey(oldestSlot); + + // Delete checkpoint attestations whose key < `${oldestSlotPadded}-`. Fixed-width + // decimal padding means the slot prefix sorts strictly before any key at that slot. + for await (const key of this.attestationPerSlotAndSigner.keysAsync({ end: `${oldestSlotPadded}-` })) { + await this.attestationPerSlotAndSigner.delete(key); this.metrics.trackMempoolItemRemoved(key); numberOfAttestations++; } - // Clean up per-signer-per-slot index. Keys are formatted as `${Fr(slot).toString()}-${signerAddress}`. - // Since Fr pads to fixed-width hex, Fr(oldestSlot) is lexicographically greater than any key with - // a smaller slot (even with the signer suffix), so using it as the exclusive end bound is correct. - const slotSignerEndKey = new Fr(oldestSlot).toString(); - for await (const key of this.checkpointAttestationsPerSlotAndSigner.keysAsync({ end: slotSignerEndKey })) { - await this.checkpointAttestationsPerSlotAndSigner.delete(key); + // Clean up per-signer-per-slot index using the same end bound. + for await (const key of this.attestationHashesPerSlotAndSigner.keysAsync({ end: `${oldestSlotPadded}-` })) { + await this.attestationHashesPerSlotAndSigner.delete(key); } - // Delete checkpoint proposals for slots < oldestSlot, using checkpointProposalsForSlot as index - for await (const slot of this.checkpointProposalsForSlot.keysAsync({ end: oldestSlot })) { - const proposalIds = await toArray(this.checkpointProposalsForSlot.getValuesAsync(slot)); - for (const proposalId of proposalIds) { - await this.checkpointProposals.delete(proposalId); + // Delete checkpoint proposals for slots < oldestSlot. + for await (const slot of this.checkpointProposalHashesPerSlot.keysAsync({ end: oldestSlot })) { + await this.checkpointProposalHashesPerSlot.delete(slot); + if (await this.checkpointProposalPerSlot.hasAsync(slot)) { + await this.checkpointProposalPerSlot.delete(slot); numberOfCheckpointProposals++; } - await this.checkpointProposalsForSlot.delete(slot); } - // Delete block proposals for slots < oldestSlot, using blockProposalsForSlotAndIndex as index - // Key format: (slot << INDEX_BITS) | indexWithinCheckpoint + // Delete block proposals for slots < oldestSlot, using blockProposalHashesPerSlotAndIndex as index. + // Key format: slot * (1 << INDEX_BITS) + indexWithinCheckpoint const blockPositionEndKey = oldestSlot * (1 << AttestationPool.INDEX_BITS); - for await (const positionKey of this.blockProposalsForSlotAndIndex.keysAsync({ end: blockPositionEndKey })) { - const proposalIds = await toArray(this.blockProposalsForSlotAndIndex.getValuesAsync(positionKey)); - for (const proposalId of proposalIds) { - await this.blockProposals.delete(proposalId); + for await (const positionKey of this.blockProposalHashesPerSlotAndIndex.keysAsync({ end: blockPositionEndKey })) { + await this.blockProposalHashesPerSlotAndIndex.delete(positionKey); + const stored = await this.blockProposalPerSlotAndIndex.getAsync(positionKey); + if (stored) { + try { + const proposal = BlockProposal.fromBuffer(stored); + await this.blockProposalSlotAndIndexPerArchive.delete(proposal.archive.toString()); + } catch { + // ignore decode errors when cleaning up + } + await this.blockProposalPerSlotAndIndex.delete(positionKey); numberOfBlockProposals++; } - await this.blockProposalsForSlotAndIndex.delete(positionKey); } }); @@ -478,18 +501,19 @@ export class AttestationPool { /** * Attempts to add a checkpoint attestation to the pool. * - * This method performs validation and addition in a single call: - * - Checks if the attestation already exists (returns alreadyExists: true if so) - * - Checks if this signer has reached the per-signer attestation cap for this slot - * - Adds the attestation if validation passes + * - Detects duplicates by signed-payload hash (not archive); a re-broadcast of the + * exact same signed payload from the same signer returns `alreadyExists: true`. + * - Distinct payload hashes from the same (slot, signer) are tracked in the + * equivocation index. The first one's bytes are stored; later distinct hashes + * bump `count` so libp2p can fire its duplicate callback. * * @param attestation - The checkpoint attestation to add - * @returns Result indicating whether the attestation was added, existence info, and count of - * attestations by this signer for this slot (for equivocation detection) + * @returns Result indicating whether the attestation was added, existence info, + * and number of distinct payload hashes by this signer for this slot + * (for equivocation detection). */ public async tryAddCheckpointAttestation(attestation: CheckpointAttestation): Promise { const slotNumber = attestation.payload.header.slotNumber; - const proposalId = attestation.archive.toString(); const sender = attestation.getSender(); if (!sender) { @@ -497,28 +521,23 @@ export class AttestationPool { } const signerAddress = sender.toString(); + const slotSignerKey = this.getSlotSignerKey(slotNumber, signerAddress); + const payloadHash = attestation.getPayloadHash(); return await this.store.transactionAsync(async () => { - const key = this.getAttestationKey(slotNumber, proposalId, signerAddress); - const alreadyExists = await this.checkpointAttestations.hasAsync(key); - - // Get count of attestations by this signer for this slot (for duplicate detection) - const signerAttestationCount = await this.getSignerAttestationCountForSlot(slotNumber, signerAddress); - - if (alreadyExists) { - return { - added: false, - alreadyExists: true, - count: signerAttestationCount, - }; + if (await this.multimapHasValue(this.attestationHashesPerSlotAndSigner, slotSignerKey, payloadHash)) { + const count = await this.attestationHashesPerSlotAndSigner.getValueCountAsync(slotSignerKey); + return { added: false, alreadyExists: true, count }; } - // Check if this signer has exceeded the per-signer cap for this slot + const signerAttestationCount = await this.attestationHashesPerSlotAndSigner.getValueCountAsync(slotSignerKey); + if (signerAttestationCount >= MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER) { this.log.debug(`Rejecting attestation: signer ${signerAddress} exceeded per-slot cap for slot ${slotNumber}`, { slotNumber, signerAddress, - proposalId, + archive: attestation.archive.toString(), + payloadHash, signerAttestationCount, }); return { @@ -528,22 +547,26 @@ export class AttestationPool { }; } - // Add the attestation - await this.checkpointAttestations.set(key, attestation.toBuffer()); - this.metrics.trackMempoolItemAdded(key); + // Track the new payload hash for equivocation detection. + await this.attestationHashesPerSlotAndSigner.set(slotSignerKey, payloadHash); - // Track this attestation in the per-signer-per-slot index for duplicate detection - const slotSignerKey = this.getSlotSignerKey(slotNumber, signerAddress); - await this.checkpointAttestationsPerSlotAndSigner.set(slotSignerKey, proposalId); + // Only the first distinct payload at (slot, signer) is stored; later + // equivocations are detected via the multimap but their bytes are not retained. + const alreadyHasStored = await this.attestationPerSlotAndSigner.hasAsync(slotSignerKey); + if (!alreadyHasStored) { + await this.attestationPerSlotAndSigner.set(slotSignerKey, attestation.toBuffer()); + this.metrics.trackMempoolItemAdded(slotSignerKey); + } this.log.debug(`Added checkpoint attestation for slot ${slotNumber} from ${signerAddress}`, { signature: attestation.signature.toString(), slotNumber, address: signerAddress, - proposalId, + archive: attestation.archive.toString(), + payloadHash, + stored: !alreadyHasStored, }); - // Return the new count return { added: true, alreadyExists: false, @@ -551,12 +574,6 @@ export class AttestationPool { }; }); } - - /** Gets the count of attestations by a specific signer for a given slot. */ - private async getSignerAttestationCountForSlot(slot: SlotNumber, signerAddress: string): Promise { - const slotSignerKey = this.getSlotSignerKey(slot, signerAddress); - return await this.checkpointAttestationsPerSlotAndSigner.getValueCountAsync(slotSignerKey); - } } /** Creates an AttestationPool backed by a temporary store for testing. */ diff --git a/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts b/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts index 02b1f1357f27..7265d2e52a42 100644 --- a/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +++ b/yarn-project/p2p/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts @@ -28,9 +28,16 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo signers = Array.from({ length: NUMBER_OF_SIGNERS_PER_TEST }, () => Secp256k1Signer.random()); }); + /** + * Build attestations from each signer over the *same* signed payload (same header, + * archive, feeAssetPriceModifier). Required by the new pool, which deduplicates by + * payload hash and treats attestations from different signers as distinct entries + * only when the payload itself matches. + */ const createCheckpointAttestationsForSlot = (slotNumber: number, archive?: Fr) => { const archiveToUse = archive ?? Fr.random(); - return signers.map(signer => mockCheckpointAttestation(signer, slotNumber, archiveToUse)); + const sharedHeader = CheckpointHeader.random({ slotNumber: SlotNumber(slotNumber) }); + return signers.map(signer => mockCheckpointAttestation(signer, slotNumber, archiveToUse, sharedHeader)); }; const mockBlockProposalForPool = ( @@ -59,13 +66,17 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo it('should add attestations to pool', async () => { const slotNumber = 420; const archive = Fr.random(); - const attestations = signers.slice(0, -1).map(signer => mockCheckpointAttestation(signer, slotNumber, archive)); + const sharedHeader = CheckpointHeader.random({ slotNumber: SlotNumber(slotNumber) }); + const attestations = signers + .slice(0, -1) + .map(signer => mockCheckpointAttestation(signer, slotNumber, archive, sharedHeader)); + const payloadHash = attestations[0].getPayloadHash(); await ap.addOwnCheckpointAttestations(attestations); const retrievedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal( SlotNumber(slotNumber), - archive.toString(), + payloadHash, ); expect(retrievedAttestations.length).toBe(attestations.length); compareCheckpointAttestations(retrievedAttestations, attestations); @@ -75,11 +86,16 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo compareCheckpointAttestations(retrievedAttestationsForSlot, attestations); // Add another one - const newAttestation = mockCheckpointAttestation(signers[NUMBER_OF_SIGNERS_PER_TEST - 1], slotNumber, archive); + const newAttestation = mockCheckpointAttestation( + signers[NUMBER_OF_SIGNERS_PER_TEST - 1], + slotNumber, + archive, + sharedHeader, + ); await ap.addOwnCheckpointAttestations([newAttestation]); const retrievedAttestationsAfterAdd = await ap.getCheckpointAttestationsForSlotAndProposal( SlotNumber(slotNumber), - archive.toString(), + payloadHash, ); expect(retrievedAttestationsAfterAdd.length).toBe(attestations.length + 1); compareCheckpointAttestations(retrievedAttestationsAfterAdd, [...attestations, newAttestation]); @@ -92,7 +108,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo const retreivedAttestationsAfterDelete = await ap.getCheckpointAttestationsForSlotAndProposal( SlotNumber(slotNumber), - archive.toString(), + payloadHash, ); expect(retreivedAttestationsAfterDelete.length).toBe(0); }); @@ -108,13 +124,14 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo for (let i = 0; i < NUMBER_OF_SIGNERS_PER_TEST; i++) { attestations.push(mockCheckpointAttestation(signer, slotNumber, archive, header)); } + const payloadHash = attestations[0].getPayloadHash(); // Add them to store and check we end up with only one await ap.addOwnCheckpointAttestations(attestations); const retreivedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal( SlotNumber(slotNumber), - archive.toString(), + payloadHash, ); expect(retreivedAttestations.length).toBe(1); expect(retreivedAttestations[0].toBuffer()).toEqual(attestations[0].toBuffer()); @@ -122,9 +139,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo // Try adding them on another operation and check they are still not duplicated await ap.addOwnCheckpointAttestations([attestations[0]]); - expect( - await ap.getCheckpointAttestationsForSlotAndProposal(SlotNumber(slotNumber), archive.toString()), - ).toHaveLength(1); + expect(await ap.getCheckpointAttestationsForSlotAndProposal(SlotNumber(slotNumber), payloadHash)).toHaveLength(1); }); it('should store attestations by differing slot', async () => { @@ -135,9 +150,9 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo for (const attestation of attestations) { const slot = attestation.payload.header.slotNumber; - const archive = attestation.archive.toString(); + const payloadHash = attestation.getPayloadHash(); - const retreivedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(slot, archive); + const retreivedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(slot, payloadHash); expect(retreivedAttestations.length).toBe(1); expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer()); expect(retreivedAttestations[0].payload.header.slotNumber).toEqual(slot); @@ -153,9 +168,9 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo for (const attestation of attestations) { const slot = attestation.payload.header.slotNumber; - const proposalId = attestation.archive.toString(); + const payloadHash = attestation.getPayloadHash(); - const retreivedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(slot, proposalId); + const retreivedAttestations = await ap.getCheckpointAttestationsForSlotAndProposal(slot, payloadHash); expect(retreivedAttestations.length).toBe(1); expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer()); expect(retreivedAttestations[0].payload.header.slotNumber).toEqual(slot); @@ -164,21 +179,25 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo it('should delete attestations older than a given slot', async () => { const slotNumbers = [1, 2, 3, 69, 72, 74, 88, 420]; - const attestations = ( - await Promise.all(slotNumbers.map(slotNumber => createCheckpointAttestationsForSlot(slotNumber))) - ).flat(); - const proposalId = attestations[0].archive.toString(); + const attestationsPerSlot = await Promise.all( + slotNumbers.map(slotNumber => createCheckpointAttestationsForSlot(slotNumber)), + ); + const attestations = attestationsPerSlot.flat(); + const payloadHashForSlot1 = attestationsPerSlot[0][0].getPayloadHash(); await ap.addOwnCheckpointAttestations(attestations); - const attestationsForSlot1 = await ap.getCheckpointAttestationsForSlotAndProposal(SlotNumber(1), proposalId); + const attestationsForSlot1 = await ap.getCheckpointAttestationsForSlotAndProposal( + SlotNumber(1), + payloadHashForSlot1, + ); expect(attestationsForSlot1.length).toBe(signers.length); await ap.deleteOlderThan(SlotNumber(73)); const attestationsForSlot1AfterDelete = await ap.getCheckpointAttestationsForSlotAndProposal( SlotNumber(1), - proposalId, + payloadHashForSlot1, ); expect(attestationsForSlot1AfterDelete.length).toBe(0); }); @@ -189,7 +208,6 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo const slotNumber = 420; const archive = Fr.random(); const proposal = await mockBlockProposalForPool(signers[0], slotNumber, archive); - const proposalId = proposal.archive.toString(); const result = await ap.tryAddBlockProposal(proposal); @@ -197,7 +215,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo expect(result.alreadyExists).toBe(false); expect(result.count).toBe(1); - const retrievedProposal = await ap.getBlockProposal(proposalId); + const retrievedProposal = await ap.getBlockProposalByArchive(proposal.archive.toString()); expect(retrievedProposal).toBeDefined(); expect(retrievedProposal!).toEqual(proposal); @@ -205,31 +223,27 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo it('should return undefined for non-existent block proposal', async () => { const nonExistentId = Fr.random().toString(); - const retrievedProposal = await ap.getBlockProposal(nonExistentId); + const retrievedProposal = await ap.getBlockProposalByArchive(nonExistentId); expect(retrievedProposal).toBeUndefined(); }); - it('should return alreadyExists when adding proposal with same id', async () => { + it('should return alreadyExists when re-adding the same signed payload', async () => { const slotNumber = 420; const archive = Fr.random(); - const proposal1 = await mockBlockProposalForPool(signers[0], slotNumber, archive); - const proposalId = proposal1.archive.toString(); + const proposal = await mockBlockProposalForPool(signers[0], slotNumber, archive); - const result1 = await ap.tryAddBlockProposal(proposal1); + const result1 = await ap.tryAddBlockProposal(proposal); expect(result1.added).toBe(true); expect(result1.alreadyExists).toBe(false); - // Create a new proposal with same archive but different signer - const proposal2 = await mockBlockProposalForPool(signers[1], slotNumber, archive); - - const result2 = await ap.tryAddBlockProposal(proposal2); + // Re-broadcasting the exact same proposal yields alreadyExists. + const result2 = await ap.tryAddBlockProposal(proposal); expect(result2.added).toBe(false); expect(result2.alreadyExists).toBe(true); - // Should still have the first proposal - const retrievedProposal = await ap.getBlockProposal(proposalId); + const retrievedProposal = await ap.getBlockProposalByArchive(proposal.archive.toString()); expect(retrievedProposal).toBeDefined(); - expect(retrievedProposal!.toBuffer()).toEqual(proposal1.toBuffer()); + expect(retrievedProposal!.toBuffer()).toEqual(proposal.toBuffer()); expect(retrievedProposal!.getSender()?.toString()).toBe(signers[0].address.toString()); }); }); @@ -239,12 +253,13 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo signer: Secp256k1Signer, slotNumber: number, archive: Fr = Fr.random(), + checkpointHeader?: CheckpointHeader, ): Promise => { - const checkpointHeader = makeCheckpointHeader(1, { slotNumber: SlotNumber(slotNumber) }); + const headerToUse = checkpointHeader ?? makeCheckpointHeader(1, { slotNumber: SlotNumber(slotNumber) }); const blockHeader = makeBlockHeader(1); const proposal = await makeCheckpointProposal({ signer, - checkpointHeader, + checkpointHeader: headerToUse, archiveRoot: archive, lastBlock: { blockHeader }, }); @@ -256,7 +271,6 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo const slotNumber = 420; const archive = Fr.random(); const proposal = await mockCheckpointProposalForPool(signers[0], slotNumber, archive); - const proposalId = proposal.archive.toString(); const result = await ap.tryAddCheckpointProposal(proposal); @@ -264,7 +278,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo expect(result.alreadyExists).toBe(false); expect(result.count).toBe(1); - const retrievedProposal = await ap.getCheckpointProposal(proposalId); + const retrievedProposal = await ap.getCheckpointProposal(SlotNumber(slotNumber)); expect(retrievedProposal).toBeDefined(); expect(retrievedProposal!.toBuffer()).toEqual(proposal.toBuffer()); @@ -281,54 +295,100 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo archiveRoot: archive, // No lastBlock }); - const proposalId = proposal.archive.toString(); // Add the checkpoint core - block extraction is now caller responsibility await ap.tryAddCheckpointProposal(proposal.toCore()); // The checkpoint proposal should be stored - const retrievedCheckpointProposal = await ap.getCheckpointProposal(proposalId); + const retrievedCheckpointProposal = await ap.getCheckpointProposal(SlotNumber(slotNumber)); expect(retrievedCheckpointProposal).toBeDefined(); // No block proposal was extracted (it had none anyway) - const retrievedBlockProposal = await ap.getBlockProposal(proposalId); + const retrievedBlockProposal = await ap.getBlockProposalByArchive(proposal.archive.toString()); expect(retrievedBlockProposal).toBeUndefined(); }); it('should return undefined for non-existent checkpoint proposal', async () => { - const nonExistentId = Fr.random().toString(); - const retrievedProposal = await ap.getCheckpointProposal(nonExistentId); + const retrievedProposal = await ap.getCheckpointProposal(SlotNumber(99999)); expect(retrievedProposal).toBeUndefined(); }); - it('should return alreadyExists when adding proposal with same id', async () => { + it('should return alreadyExists when re-adding the same signed payload', async () => { const slotNumber = 420; const archive = Fr.random(); - const proposal1 = await mockCheckpointProposalForPool(signers[0], slotNumber, archive); - const proposalId = proposal1.archive.toString(); + const proposal = await mockCheckpointProposalForPool(signers[0], slotNumber, archive); - const result1 = await ap.tryAddCheckpointProposal(proposal1); + const result1 = await ap.tryAddCheckpointProposal(proposal); expect(result1.added).toBe(true); expect(result1.alreadyExists).toBe(false); - // Create a new proposal with same archive but different signer - const proposal2 = await mockCheckpointProposalForPool(signers[1], slotNumber, archive); - - const result2 = await ap.tryAddCheckpointProposal(proposal2); + // Re-broadcasting the exact same signed payload yields alreadyExists. + const result2 = await ap.tryAddCheckpointProposal(proposal); expect(result2.added).toBe(false); expect(result2.alreadyExists).toBe(true); - // Should still have the first proposal - const retrievedProposal = await ap.getCheckpointProposal(proposalId); + // Should still have the first proposal stored at the slot + const retrievedProposal = await ap.getCheckpointProposal(SlotNumber(slotNumber)); expect(retrievedProposal).toBeDefined(); - expect(retrievedProposal!.toBuffer()).toEqual(proposal1.toBuffer()); + expect(retrievedProposal!.toBuffer()).toEqual(proposal.toBuffer()); expect(retrievedProposal!.getSender()?.toString()).toBe(signers[0].address.toString()); }); + it('should treat distinct payloads at the same slot as equivocations (count = 2)', async () => { + const slotNumber = 420; + // Two proposals at the same slot but with different headers (distinct payloads). + const proposal1 = await mockCheckpointProposalForPool(signers[0], slotNumber, Fr.random()); + const proposal2 = await mockCheckpointProposalForPool(signers[0], slotNumber, Fr.random()); + + const result1 = await ap.tryAddCheckpointProposal(proposal1); + expect(result1.added).toBe(true); + expect(result1.count).toBe(1); + + const result2 = await ap.tryAddCheckpointProposal(proposal2); + // The second distinct payload is tracked as an equivocation, count goes to 2, + // but its bytes are not retained — the first proposal stays in the main store. + expect(result2.added).toBe(true); + expect(result2.alreadyExists).toBe(false); + expect(result2.count).toBe(2); + + const retrievedProposal = await ap.getCheckpointProposal(SlotNumber(slotNumber)); + expect(retrievedProposal!.toBuffer()).toEqual(proposal1.toBuffer()); + }); + + it('should detect equivocation when only feeAssetPriceModifier differs', async () => { + const slotNumber = 420; + const archive = Fr.random(); + // Same checkpoint header + archive, but two different feeAssetPriceModifier values. + // This is the audit-finding scenario: archive collides but the signed payload differs. + const sharedHeader = makeCheckpointHeader(1, { slotNumber: SlotNumber(slotNumber) }); + const proposalA = await makeCheckpointProposal({ + signer: signers[0], + checkpointHeader: sharedHeader, + archiveRoot: archive, + feeAssetPriceModifier: 50n, + }); + const proposalB = await makeCheckpointProposal({ + signer: signers[0], + checkpointHeader: sharedHeader, + archiveRoot: archive, + feeAssetPriceModifier: -50n, + }); + + const result1 = await ap.tryAddCheckpointProposal(proposalA.toCore()); + expect(result1.count).toBe(1); + + const result2 = await ap.tryAddCheckpointProposal(proposalB.toCore()); + // The fix: archive collision no longer hides the equivocation; payload-hash dedup + // sees the distinct feeMod and bumps `count` to 2 so libp2p can fire the slash callback. + expect(result2.added).toBe(true); + expect(result2.alreadyExists).toBe(false); + expect(result2.count).toBe(2); + }); + it('should return added=false when exceeding capacity', async () => { const slotNumber = 420; - // Add MAX_CHECKPOINT_PROPOSALS_PER_SLOT proposals + // Add MAX_CHECKPOINT_PROPOSALS_PER_SLOT distinct proposals. for (let i = 0; i < MAX_CHECKPOINT_PROPOSALS_PER_SLOT; i++) { const proposal = await mockCheckpointProposalForPool(signers[i % NUMBER_OF_SIGNERS_PER_TEST], slotNumber); const result = await ap.tryAddCheckpointProposal(proposal); @@ -336,7 +396,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo expect(result.count).toBe(i + 1); } - // The next proposal should not be added + // The next proposal should not be added. const extraProposal = await mockCheckpointProposalForPool(signers[0], slotNumber); const result = await ap.tryAddCheckpointProposal(extraProposal); expect(result.added).toBe(false); @@ -571,17 +631,17 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo await ap.tryAddBlockProposal(newProposal); // Verify both proposals exist - expect(await ap.getBlockProposal(oldProposal.archive.toString())).toBeDefined(); - expect(await ap.getBlockProposal(newProposal.archive.toString())).toBeDefined(); + expect(await ap.getBlockProposalByArchive(oldProposal.archive.toString())).toBeDefined(); + expect(await ap.getBlockProposalByArchive(newProposal.archive.toString())).toBeDefined(); // Delete slots older than newSlot (should delete oldSlot) await ap.deleteOlderThan(SlotNumber(newSlot)); // Old proposal should be deleted from storage - expect(await ap.getBlockProposal(oldProposal.archive.toString())).toBeUndefined(); + expect(await ap.getBlockProposalByArchive(oldProposal.archive.toString())).toBeUndefined(); // New proposal should still exist - expect(await ap.getBlockProposal(newProposal.archive.toString())).toBeDefined(); + expect(await ap.getBlockProposalByArchive(newProposal.archive.toString())).toBeDefined(); }); }); @@ -590,12 +650,13 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo signer: Secp256k1Signer, slotNumber: number, archive: Fr = Fr.random(), + checkpointHeader?: CheckpointHeader, ): Promise => { - const checkpointHeader = makeCheckpointHeader(1, { slotNumber: SlotNumber(slotNumber) }); + const headerToUse = checkpointHeader ?? makeCheckpointHeader(1, { slotNumber: SlotNumber(slotNumber) }); const blockHeader = makeBlockHeader(1); const proposal = await makeCheckpointProposal({ signer, - checkpointHeader, + checkpointHeader: headerToUse, archiveRoot: archive, lastBlock: { blockHeader }, }); diff --git a/yarn-project/p2p/src/msg_validators/attestation_validator/fisherman_attestation_validator.test.ts b/yarn-project/p2p/src/msg_validators/attestation_validator/fisherman_attestation_validator.test.ts index dc1d39576a16..4cf3c3acab58 100644 --- a/yarn-project/p2p/src/msg_validators/attestation_validator/fisherman_attestation_validator.test.ts +++ b/yarn-project/p2p/src/msg_validators/attestation_validator/fisherman_attestation_validator.test.ts @@ -148,7 +148,7 @@ describe('FishermanAttestationValidator', () => { const result = await validator.validate(mockAttestation); expect(result).toEqual({ result: 'accept' }); - expect(attestationPool.getCheckpointProposal).toHaveBeenCalledWith(mockAttestation.archive.toString()); + expect(attestationPool.getCheckpointProposal).toHaveBeenCalledWith(mockAttestation.payload.header.slotNumber); }); it('returns low tolerance error if attestation payload does not match proposal payload', async () => { @@ -173,7 +173,7 @@ describe('FishermanAttestationValidator', () => { const result = await validator.validate(mockAttestation); expect(result).toEqual({ result: 'reject', severity: PeerErrorSeverity.LowToleranceError }); - expect(attestationPool.getCheckpointProposal).toHaveBeenCalledWith(mockAttestation.archive.toString()); + expect(attestationPool.getCheckpointProposal).toHaveBeenCalledWith(mockAttestation.payload.header.slotNumber); }); it('returns accept if proposal is not found yet (attestation arrived before proposal)', async () => { @@ -189,7 +189,7 @@ describe('FishermanAttestationValidator', () => { const result = await validator.validate(mockAttestation); expect(result).toEqual({ result: 'accept' }); - expect(attestationPool.getCheckpointProposal).toHaveBeenCalledWith(mockAttestation.archive.toString()); + expect(attestationPool.getCheckpointProposal).toHaveBeenCalledWith(mockAttestation.payload.header.slotNumber); }); it('detects payload mismatch with different archive roots', async () => { diff --git a/yarn-project/p2p/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts b/yarn-project/p2p/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts index 3b83d030d9ea..5639c7e45a69 100644 --- a/yarn-project/p2p/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +++ b/yarn-project/p2p/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts @@ -64,8 +64,7 @@ export class FishermanAttestationValidator extends CheckpointAttestationValidato return { result: 'accept' }; } - const proposalId = message.archive.toString(); - const proposal = await this.attestationPool.getCheckpointProposal(proposalId); + const proposal = await this.attestationPool.getCheckpointProposal(message.payload.header.slotNumber); if (proposal) { // Compare the attestation payload with the proposal payload @@ -94,9 +93,7 @@ export class FishermanAttestationValidator extends CheckpointAttestationValidato } } else { // We might receive attestations before proposals in some cases - this.logger.debug( - `Received attestation for slot ${slotNumberBigInt} but proposal not found yet. ` + `Proposal ID: ${proposalId}`, - ); + this.logger.debug(`Received attestation for slot ${slotNumberBigInt} but proposal not found yet.`); } return { result: 'accept' }; diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.test.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.test.ts index be2ce9f19994..2dab754fe0d4 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.test.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.test.ts @@ -9,11 +9,12 @@ import type { L2BlockSource } from '@aztec/stdlib/block'; import type { ContractDataSource } from '@aztec/stdlib/contract'; import { GasFees } from '@aztec/stdlib/gas'; import type { ClientProtocolCircuitVerifier } from '@aztec/stdlib/interfaces/server'; -import { BlockProposal, PeerErrorSeverity } from '@aztec/stdlib/p2p'; +import { BlockProposal, type CheckpointAttestation, PeerErrorSeverity } from '@aztec/stdlib/p2p'; import { TEST_COORDINATION_SIGNATURE_CONTEXT, makeBlockHeader, makeBlockProposal, + makeCheckpointAttestation, makeCheckpointHeader, makeCheckpointProposal, mockTx, @@ -327,10 +328,10 @@ describe('LibP2PService', () => { /** Sets up the mempools with a mock attestation pool that returns a proposal with given tx hashes. */ function setProposalTxHashes(svc: TestLibP2PService, txHashes: string[]): void { - // Create a partial mock of the attestation pool that only implements getBlockProposal. + // Create a partial mock of the attestation pool that only implements getBlockProposalByArchive. // The validation code only accesses `txHashes` from the returned proposal. const mockAttestationPool: MockAttestationPoolForTests = { - getBlockProposal: (_: string) => + getBlockProposalByArchive: (_: string) => Promise.resolve({ txHashes: txHashes.map(s => ({ toString: () => s })), }), @@ -491,7 +492,7 @@ describe('LibP2PService', () => { // No proposal available - mock attestationPool to return undefined const mockAttestationPool: MockAttestationPoolForTests = { - getBlockProposal: (_: string) => Promise.resolve(undefined), + getBlockProposalByArchive: (_: string) => Promise.resolve(undefined), }; service.setAttestationPool(mockAttestationPool); @@ -557,7 +558,7 @@ describe('LibP2PService', () => { expect(reportMessageValidationResultSpy).toHaveBeenCalledWith('msg-1', MOCK_PEER_ID, TopicValidatorResult.Accept); // Verify block was stored in attestation pool - const stored = await attestationPool.getBlockProposal(proposal.archive.toString()); + const stored = await attestationPool.getBlockProposalByArchive(proposal.archive.toString()); expect(stored).toBeDefined(); }); @@ -732,6 +733,46 @@ describe('LibP2PService', () => { // Verify message was rejected expect(reportMessageValidationResultSpy).toHaveBeenCalledWith('msg-1', MOCK_PEER_ID, TopicValidatorResult.Reject); }); + + // Regression for A-1013: payloads sharing (slot, position, archive) but differing on another + // signed field (e.g. inHash) used to dedup by archive only and silently drop the second one. + // The pool now dedups by signed-payload hash, so the equivocation surfaces. + it('same archive but different signed payload triggers slash callback', async () => { + const blockHeader = makeBlockHeader(1, { slotNumber: targetSlot }); + const indexWithinCheckpoint = IndexWithinCheckpoint(0); + const sharedArchive = Fr.random(); + + const proposal1 = await makeBlockProposal({ + signer, + blockHeader, + indexWithinCheckpoint, + inHash: Fr.fromString('0x1'), + archiveRoot: sharedArchive, + }); + await service.processBlockFromPeer(proposal1.toBuffer(), 'msg-1', mockPeerId); + expect(duplicateProposalCallback).not.toHaveBeenCalled(); + + const proposal2 = await makeBlockProposal({ + signer, + blockHeader, + indexWithinCheckpoint, + inHash: Fr.fromString('0x2'), + archiveRoot: sharedArchive, + }); + expect(proposal2.archive.toString()).toBe(proposal1.archive.toString()); + expect(proposal2.getPayloadHash()).not.toEqual(proposal1.getPayloadHash()); + + await service.processBlockFromPeer(proposal2.toBuffer(), 'msg-2', mockPeerId); + + expect(reportMessageValidationResultSpy).toHaveBeenCalledWith('msg-2', MOCK_PEER_ID, TopicValidatorResult.Accept); + expect(blockReceivedCallback).toHaveBeenCalledTimes(1); // only the first one + expect(duplicateProposalCallback).toHaveBeenCalledTimes(1); + expect(duplicateProposalCallback).toHaveBeenCalledWith({ + slot: targetSlot, + proposer: signer.address, + type: 'block', + }); + }); }); describe('handleGossipedCheckpointProposal', () => { @@ -796,7 +837,7 @@ describe('LibP2PService', () => { expect(reportMessageValidationResultSpy).toHaveBeenCalledWith('msg-1', MOCK_PEER_ID, TopicValidatorResult.Accept); // Verify checkpoint was stored in attestation pool - const stored = await attestationPool.getCheckpointProposal(proposal.archive.toString()); + const stored = await attestationPool.getCheckpointProposal(proposal.slotNumber); expect(stored).toBeDefined(); }); @@ -861,10 +902,12 @@ describe('LibP2PService', () => { expect(mockTxPool.protectTxs).toHaveBeenCalledTimes(1); // Verify both were stored in attestation pool - const storedCheckpoint = await attestationPool.getCheckpointProposal(proposal.archive.toString()); + const storedCheckpoint = await attestationPool.getCheckpointProposal(proposal.slotNumber); expect(storedCheckpoint).toBeDefined(); - const storedBlock = await attestationPool.getBlockProposal(proposal.getBlockProposal()!.archive.toString()); + const storedBlock = await attestationPool.getBlockProposalByArchive( + proposal.getBlockProposal()!.archive.toString(), + ); expect(storedBlock).toBeDefined(); }); @@ -921,7 +964,9 @@ describe('LibP2PService', () => { expect(receivedBlock.archive.toString()).toBe(extraProposal.getBlockProposal()!.archive.toString()); // The lastBlock is stored in the attestation pool - const storedBlock = await attestationPool.getBlockProposal(extraProposal.getBlockProposal()!.archive.toString()); + const storedBlock = await attestationPool.getBlockProposalByArchive( + extraProposal.getBlockProposal()!.archive.toString(), + ); expect(storedBlock).toBeDefined(); // Txs were marked as non-evictable since the block was processed @@ -991,6 +1036,147 @@ describe('LibP2PService', () => { expect(allNodesCheckpointReceivedCallback).toHaveBeenCalledTimes(1); expect(allNodesCheckpointReceivedCallback).toHaveBeenCalledWith(expect.any(Object), expect.anything()); }); + + // Regression for A-1013: payloads sharing (slot, archive) but differing on feeAssetPriceModifier + // used to dedup by archive only and silently drop the second one. The pool now dedups by + // signed-payload hash, so the equivocation surfaces. + it('same archive but different feeAssetPriceModifier triggers slash callback', async () => { + const sharedHeader = makeCheckpointHeader(1, { slotNumber: targetSlot }); + const sharedArchive = Fr.random(); + + const checkpoint1 = await makeCheckpointProposal({ + signer, + checkpointHeader: sharedHeader, + archiveRoot: sharedArchive, + feeAssetPriceModifier: 50n, + }); + await service.handleGossipedCheckpointProposal(checkpoint1.toBuffer(), 'msg-1', mockPeerId); + expect(duplicateProposalCallback).not.toHaveBeenCalled(); + + const checkpoint2 = await makeCheckpointProposal({ + signer, + checkpointHeader: sharedHeader, + archiveRoot: sharedArchive, + feeAssetPriceModifier: -50n, + }); + expect(checkpoint2.archive.toString()).toBe(checkpoint1.archive.toString()); + expect(checkpoint2.getPayloadHash()).not.toEqual(checkpoint1.getPayloadHash()); + + await service.handleGossipedCheckpointProposal(checkpoint2.toBuffer(), 'msg-2', mockPeerId); + + expect(reportMessageValidationResultSpy).toHaveBeenCalledWith('msg-2', MOCK_PEER_ID, TopicValidatorResult.Accept); + expect(allNodesCheckpointReceivedCallback).toHaveBeenCalledTimes(1); // only the first one + expect(validatorCheckpointReceivedCallback).toHaveBeenCalledTimes(1); + expect(duplicateProposalCallback).toHaveBeenCalledTimes(1); + expect(duplicateProposalCallback).toHaveBeenCalledWith({ + slot: targetSlot, + proposer: signer.address, + type: 'checkpoint', + }); + }); + }); + + // Regression for A-1013 + describe('validateAndStoreCheckpointAttestation', () => { + let attestationPool: AttestationPool; + let mockEpochCache: MockProxy; + let proposerSigner: Secp256k1Signer; + let duplicateAttestationCallback: jest.Mock; + + const targetSlot = SlotNumber(100); + const nextSlot = SlotNumber(101); + + beforeEach(() => { + proposerSigner = Secp256k1Signer.random(); + attestationPool = new AttestationPool(openTmpStore(true)); + const mockTxPool = mock(); + mockTxPool.protectTxs.mockResolvedValue([]); + + mockEpochCache = mock(); + mockEpochCache.getProposerAttesterAddressInSlot.mockResolvedValue(proposerSigner.address); + mockEpochCache.getTargetAndNextSlot.mockReturnValue({ targetSlot, nextSlot }); + mockEpochCache.getTargetSlot.mockReturnValue(targetSlot); + mockEpochCache.isInCommittee.mockResolvedValue(true); + + mockPeerManager = mock(); + reportMessageValidationResultSpy = jest.fn(); + + service = createTestLibP2PServiceWithPools( + mockPeerManager, + reportMessageValidationResultSpy, + attestationPool, + mockTxPool, + mockEpochCache, + ); + + duplicateAttestationCallback = jest.fn(); + service.registerDuplicateAttestationCallback(duplicateAttestationCallback); + }); + + // Regression for A-1013: attestations sharing (slot, signer, archive) but differing on + // feeAssetPriceModifier used to dedup by archive only. The pool now dedups by signed-payload + // hash, so the equivocation surfaces. + it('same signer + same archive + different feeAssetPriceModifier triggers slash callback', async () => { + const attesterSigner = Secp256k1Signer.random(); + const sharedHeader = makeCheckpointHeader(1, { slotNumber: targetSlot }); + const sharedArchive = Fr.random(); + + const attestation1 = makeCheckpointAttestation({ + header: sharedHeader, + archive: sharedArchive, + feeAssetPriceModifier: 50n, + attesterSigner, + proposerSigner, + }); + await service.validateAndStoreCheckpointAttestation(mockPeerId, attestation1); + expect(duplicateAttestationCallback).not.toHaveBeenCalled(); + + const attestation2 = makeCheckpointAttestation({ + header: sharedHeader, + archive: sharedArchive, + feeAssetPriceModifier: -50n, + attesterSigner, + proposerSigner, + }); + expect(attestation2.archive.toString()).toBe(attestation1.archive.toString()); + expect(attestation2.getPayloadHash()).not.toEqual(attestation1.getPayloadHash()); + + await service.validateAndStoreCheckpointAttestation(mockPeerId, attestation2); + + expect(duplicateAttestationCallback).toHaveBeenCalledTimes(1); + expect(duplicateAttestationCallback).toHaveBeenCalledWith({ + slot: targetSlot, + attester: attesterSigner.address, + }); + }); + + it('different signers are not equivocations and do not trigger slash callback', async () => { + const attesterA = Secp256k1Signer.random(); + const attesterB = Secp256k1Signer.random(); + const sharedHeader = makeCheckpointHeader(1, { slotNumber: targetSlot }); + const sharedArchive = Fr.random(); + + const attestationA = makeCheckpointAttestation({ + header: sharedHeader, + archive: sharedArchive, + feeAssetPriceModifier: 50n, + attesterSigner: attesterA, + proposerSigner, + }); + await service.validateAndStoreCheckpointAttestation(mockPeerId, attestationA); + + const attestationB = makeCheckpointAttestation({ + header: sharedHeader, + archive: sharedArchive, + feeAssetPriceModifier: -50n, + attesterSigner: attesterB, + proposerSigner, + }); + await service.validateAndStoreCheckpointAttestation(mockPeerId, attestationB); + + // Two distinct signers are not an equivocation; the pool tracks per-(slot, signer). + expect(duplicateAttestationCallback).not.toHaveBeenCalled(); + }); }); }); @@ -1000,11 +1186,11 @@ interface MockTx { } /** - * Minimal attestation pool interface for tests that only need getBlockProposal. + * Minimal attestation pool interface for tests that only need getBlockProposalByArchive. * This allows creating partial mocks without implementing the full AttestationPool interface. */ interface MockAttestationPoolForTests { - getBlockProposal(id: string): Promise<{ txHashes: { toString(): string }[] } | undefined>; + getBlockProposalByArchive(id: string): Promise<{ txHashes: { toString(): string }[] } | undefined>; } /** Options for creating a test LibP2PService instance. */ @@ -1150,6 +1336,11 @@ class TestLibP2PService extends LibP2PService { return super.handleGossipedCheckpointProposal(payloadData, msgId, source); } + /** Exposes the protected validateAndStoreCheckpointAttestation for testing. */ + public override validateAndStoreCheckpointAttestation(peerId: PeerId, attestation: CheckpointAttestation) { + return super.validateAndStoreCheckpointAttestation(peerId, attestation); + } + /** Override to use the mock. */ protected override async validateRequestedTx( tx: Tx, diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index 79b5e9d69578..980d632a6d28 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -1535,7 +1535,7 @@ export class LibP2PService extends WithTracer implements P2PService { } // Given proposal (should have locally), ensure returned txs are valid subset and match request indices - const proposal = await this.mempools.attestationPool.getBlockProposal(request.archiveRoot.toString()); + const proposal = await this.mempools.attestationPool.getBlockProposalByArchive(request.archiveRoot.toString()); if (proposal) { // Build intersected indices const intersectIdx = request.txIndices.getTrueIndices().filter(i => response.txIndices.isSet(i)); diff --git a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.test.ts b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.test.ts index e68e5113b5a8..b6cea69ac2c2 100644 --- a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.test.ts +++ b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.test.ts @@ -45,7 +45,7 @@ describe('reqRespBlockTxsHandler', () => { txPool = mock(); peerId = mock(); - attestationPool.getBlockProposal.mockResolvedValue(undefined); + attestationPool.getBlockProposalByArchive.mockResolvedValue(undefined); archiver.getBlock.mockResolvedValue(undefined); txPool.getTxsByHash.mockResolvedValue([]); txPool.hasTxs.mockResolvedValue([]); @@ -91,7 +91,7 @@ describe('reqRespBlockTxsHandler', () => { const proposal = await createBlockProposal(txHashes); const txs = txHashes.map(h => makeTx(h)); - attestationPool.getBlockProposal.mockResolvedValue(proposal); + attestationPool.getBlockProposalByArchive.mockResolvedValue(proposal); txPool.hasTxs.mockResolvedValue([true, true, true]); txPool.getTxsByHash.mockResolvedValue(txs); @@ -107,7 +107,7 @@ describe('reqRespBlockTxsHandler', () => { const txHashes = [TxHash.random(), TxHash.random(), TxHash.random()]; const proposal = await createBlockProposal(txHashes); - attestationPool.getBlockProposal.mockResolvedValue(proposal); + attestationPool.getBlockProposalByArchive.mockResolvedValue(proposal); txPool.hasTxs.mockResolvedValue([true, false, true]); txPool.getTxsByHash.mockResolvedValue([makeTx(txHashes[0]), undefined, makeTx(txHashes[2])]); @@ -122,7 +122,7 @@ describe('reqRespBlockTxsHandler', () => { const txHashes = [TxHash.random(), TxHash.random()]; const proposal = await createBlockProposal(txHashes); - attestationPool.getBlockProposal.mockResolvedValue(proposal); + attestationPool.getBlockProposalByArchive.mockResolvedValue(proposal); txPool.hasTxs.mockResolvedValue([true, false]); txPool.getTxsByHash.mockResolvedValue([makeTx(txHashes[0]), undefined]); @@ -150,7 +150,7 @@ describe('reqRespBlockTxsHandler', () => { expect(response.txs.length).toBe(3); expect(response.txIndices.getTrueIndices()).toEqual([0, 1, 2]); - expect(attestationPool.getBlockProposal).toHaveBeenCalledWith(block.archive.root.toString()); + expect(attestationPool.getBlockProposalByArchive).toHaveBeenCalledWith(block.archive.root.toString()); expect(archiver.getBlock).toHaveBeenCalledWith({ archive: block.archive.root }); }); @@ -168,7 +168,7 @@ describe('reqRespBlockTxsHandler', () => { expect(response.txs.length).toBe(2); expect(response.txIndices.getTrueIndices()).toEqual([0, 2]); - expect(attestationPool.getBlockProposal).toHaveBeenCalledWith(block.archive.root.toString()); + expect(attestationPool.getBlockProposalByArchive).toHaveBeenCalledWith(block.archive.root.toString()); expect(archiver.getBlock).toHaveBeenCalledWith({ archive: block.archive.root }); }); @@ -177,14 +177,14 @@ describe('reqRespBlockTxsHandler', () => { const proposal = await createBlockProposal(txHashes); const txs = txHashes.map(h => makeTx(h)); - attestationPool.getBlockProposal.mockResolvedValue(proposal); + attestationPool.getBlockProposalByArchive.mockResolvedValue(proposal); txPool.hasTxs.mockResolvedValue([true, true]); txPool.getTxsByHash.mockResolvedValue(txs); const request = new BlockTxsRequest(proposal.archive, new TxHashArray(), BitVector.init(2, [0, 1])); await callHandler(request); - expect(attestationPool.getBlockProposal).toHaveBeenCalledWith(proposal.archive.toString()); + expect(attestationPool.getBlockProposalByArchive).toHaveBeenCalledWith(proposal.archive.toString()); expect(archiver.getBlock).not.toHaveBeenCalled(); }); }); diff --git a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.ts b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.ts index d1a4a81876d9..19cb7f29a478 100644 --- a/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +++ b/yarn-project/p2p/src/services/reqresp/protocols/block_txs/block_txs_handler.ts @@ -37,7 +37,7 @@ export function reqRespBlockTxsHandler( throw new ReqRespStatusError(ReqRespStatus.BADLY_FORMED_REQUEST, { cause: err }); } // First try attestation pool, then fall back to archiver - let txHashes = (await attestationPool.getBlockProposal(request.archiveRoot.toString()))?.txHashes; + let txHashes = (await attestationPool.getBlockProposalByArchive(request.archiveRoot.toString()))?.txHashes; if (!txHashes) { txHashes = (await archiver.getBlock({ archive: request.archiveRoot }))?.body.txEffects.map( effect => effect.txHash, diff --git a/yarn-project/p2p/src/test-helpers/testbench-utils.ts b/yarn-project/p2p/src/test-helpers/testbench-utils.ts index c8d142136278..e71a879d7659 100644 --- a/yarn-project/p2p/src/test-helpers/testbench-utils.ts +++ b/yarn-project/p2p/src/test-helpers/testbench-utils.ts @@ -1,4 +1,5 @@ import type { EpochCacheInterface } from '@aztec/epoch-cache'; +import type { CheckpointProposalHash } from '@aztec/foundation/branded-types'; import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types'; import type { Logger } from '@aztec/foundation/log'; import type { L2Block, L2BlockId } from '@aztec/stdlib/block'; @@ -225,7 +226,7 @@ export class InMemoryAttestationPool { return Promise.resolve({ added: true, alreadyExists: false, count: 1 }); } - getBlockProposal(id: string): Promise { + getBlockProposalByArchive(id: string): Promise { return Promise.resolve(this.proposals.get(id)); } @@ -233,7 +234,7 @@ export class InMemoryAttestationPool { return Promise.resolve({ added: true, alreadyExists: false, count: 1 }); } - getCheckpointProposal(_id: string): Promise { + getCheckpointProposal(_slot: SlotNumber): Promise { return Promise.resolve(undefined); } @@ -247,7 +248,7 @@ export class InMemoryAttestationPool { getCheckpointAttestationsForSlotAndProposal( _slot: SlotNumber, - _proposalId: string, + _proposalPayloadHash: CheckpointProposalHash, ): Promise { return Promise.resolve([]); } diff --git a/yarn-project/stdlib/src/interfaces/p2p.test.ts b/yarn-project/stdlib/src/interfaces/p2p.test.ts index 3b03eb9158a9..37c64471ed15 100644 --- a/yarn-project/stdlib/src/interfaces/p2p.test.ts +++ b/yarn-project/stdlib/src/interfaces/p2p.test.ts @@ -1,4 +1,4 @@ -import { SlotNumber } from '@aztec/foundation/branded-types'; +import { CheckpointProposalHash, SlotNumber } from '@aztec/foundation/branded-types'; import { type JsonRpcTestContext, createJsonRpcTestSetup } from '@aztec/foundation/json-rpc/test'; import { CheckpointAttestation } from '../p2p/checkpoint_attestation.js'; @@ -27,7 +27,10 @@ describe('P2PApiSchema', () => { }); it('getCheckpointAttestationsForSlot', async () => { - const attestations = await context.client.getCheckpointAttestationsForSlot(SlotNumber(1), 'proposalId'); + const attestations = await context.client.getCheckpointAttestationsForSlot( + SlotNumber(1), + CheckpointProposalHash('0xdeadbeef'), + ); expect(attestations).toEqual([CheckpointAttestation.empty()]); expect(attestations[0]).toBeInstanceOf(CheckpointAttestation); }); @@ -65,9 +68,12 @@ const peers: PeerInfo[] = [ ]; class MockP2P implements P2PApi { - getCheckpointAttestationsForSlot(slot: SlotNumber, proposalId?: string): Promise { + getCheckpointAttestationsForSlot( + slot: SlotNumber, + proposalId?: CheckpointProposalHash, + ): Promise { expect(slot).toEqual(SlotNumber(1)); - expect(proposalId).toEqual('proposalId'); + expect(proposalId).toEqual(CheckpointProposalHash('0xdeadbeef')); return Promise.resolve([CheckpointAttestation.empty()]); } diff --git a/yarn-project/stdlib/src/interfaces/p2p.ts b/yarn-project/stdlib/src/interfaces/p2p.ts index 301cbb4f9fee..a729ecf22938 100644 --- a/yarn-project/stdlib/src/interfaces/p2p.ts +++ b/yarn-project/stdlib/src/interfaces/p2p.ts @@ -1,4 +1,4 @@ -import type { SlotNumber } from '@aztec/foundation/branded-types'; +import type { CheckpointProposalHash, SlotNumber } from '@aztec/foundation/branded-types'; import { z } from 'zod'; @@ -49,13 +49,19 @@ export interface P2PApi { getPeers(includePending?: boolean): Promise; /** - * Queries the Attestation pool for checkpoint attestations for the given slot + * Queries the Attestation pool for checkpoint attestations for the given slot. * * @param slot - the slot to query - * @param proposalId - the proposal id to query, or undefined to query all proposals for the slot + * @param proposalPayloadHash - hex-encoded keccak256 of the target proposal's signed + * payload (`CheckpointProposal.getPayloadHash()`). When provided, only + * attestations whose own signed payload hashes to the same value are returned. + * When omitted, all attestations for the slot are returned. * @returns CheckpointAttestations */ - getCheckpointAttestationsForSlot(slot: SlotNumber, proposalId?: string): Promise; + getCheckpointAttestationsForSlot( + slot: SlotNumber, + proposalPayloadHash?: CheckpointProposalHash, + ): Promise; } export interface P2PClient extends P2PApi { @@ -66,7 +72,10 @@ export interface P2PClient extends P2PApi { export const P2PApiSchema: ApiSchemaFor = { getCheckpointAttestationsForSlot: z .function() - .args(schemas.SlotNumber, optional(z.string())) + .args( + schemas.SlotNumber, + optional(z.string().regex(/^0x[0-9a-fA-F]+$/) as unknown as z.ZodType), + ) .returns(z.array(CheckpointAttestation.schema)), getPendingTxs: z .function() diff --git a/yarn-project/stdlib/src/p2p/block_proposal.ts b/yarn-project/stdlib/src/p2p/block_proposal.ts index de5a36e2ca64..1d29fa05ff99 100644 --- a/yarn-project/stdlib/src/p2p/block_proposal.ts +++ b/yarn-project/stdlib/src/p2p/block_proposal.ts @@ -5,7 +5,7 @@ import { IndexWithinCheckpoint, SlotNumber, } from '@aztec/foundation/branded-types'; -import type { BaseBuffer32 } from '@aztec/foundation/buffer'; +import { type BaseBuffer32, Buffer32 } from '@aztec/foundation/buffer'; import { keccak256 } from '@aztec/foundation/crypto/keccak'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { EthAddress } from '@aztec/foundation/eth-address'; @@ -92,7 +92,7 @@ export class BlockProposal extends Gossipable implements Signable { } override generateP2PMessageIdentifier(): Promise { - return Promise.resolve(BlockProposalHash.fromBuffer(keccak256(this.signature.toBuffer()))); + return Promise.resolve(new Buffer32(this.getPayloadHashBuffer())); } get archive(): Fr { @@ -137,6 +137,21 @@ export class BlockProposal extends Gossipable implements Signable { ]); } + private getPayloadHashBuffer(): Buffer { + return keccak256(this.getPayloadToSign()); + } + + /** + * Returns a keccak256 hash of the signed payload. + * Used by the attestation pool to dedup distinct signed payloads at the same + * (slot, indexWithinCheckpoint) regardless of archive collisions. + * The hash deliberately excludes the signature so non-deterministic ECDSA + * re-signs of the same payload do not look like equivocation. + */ + getPayloadHash(): BlockProposalHash { + return BlockProposalHash.fromBuffer(this.getPayloadHashBuffer()); + } + static async createProposalFromSigner( blockHeader: BlockHeader, checkpointNumber: CheckpointNumber, diff --git a/yarn-project/stdlib/src/p2p/checkpoint_attestation.ts b/yarn-project/stdlib/src/p2p/checkpoint_attestation.ts index f3550d638e20..c054a6c5dc40 100644 --- a/yarn-project/stdlib/src/p2p/checkpoint_attestation.ts +++ b/yarn-project/stdlib/src/p2p/checkpoint_attestation.ts @@ -1,6 +1,5 @@ -import { CheckpointAttestationHash, SlotNumber } from '@aztec/foundation/branded-types'; -import type { BaseBuffer32 } from '@aztec/foundation/buffer'; -import { keccak256 } from '@aztec/foundation/crypto/keccak'; +import { CheckpointProposalHash, type SlotNumber } from '@aztec/foundation/branded-types'; +import { type BaseBuffer32, Buffer32 } from '@aztec/foundation/buffer'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { EthAddress } from '@aztec/foundation/eth-address'; import { Signature } from '@aztec/foundation/eth-signature'; @@ -15,8 +14,6 @@ import { Gossipable } from './gossipable.js'; import { type CoordinationSignatureContext, recoverCoordinationSigner } from './signature_utils.js'; import { TopicType } from './topic_type.js'; -export type { CheckpointAttestationHash } from '@aztec/foundation/branded-types'; - /** * CheckpointAttestation * @@ -53,7 +50,7 @@ export class CheckpointAttestation extends Gossipable { } override generateP2PMessageIdentifier(): Promise { - return Promise.resolve(CheckpointAttestationHash.fromBuffer(keccak256(this.signature.toBuffer()))); + return Promise.resolve(new Buffer32(this.payload.getPayloadHash())); } get archive(): Fr { @@ -104,6 +101,14 @@ export class CheckpointAttestation extends Gossipable { return this.payload.getPayloadToSign(); } + /** + * Returns a keccak256 hash of the signed consensus payload. + * Used to dedup distinct signed payloads. Returns same hash than the corresponding proposal. + */ + getPayloadHash(): CheckpointProposalHash { + return CheckpointProposalHash.fromBuffer(this.payload.getPayloadHash()); + } + toBuffer(): Buffer { return serializeToBuffer([this.payload, this.signature, this.proposerSignature]); } diff --git a/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts b/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts index dc832cda5138..2595068e7f6d 100644 --- a/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts +++ b/yarn-project/stdlib/src/p2p/checkpoint_proposal.ts @@ -4,8 +4,7 @@ import { IndexWithinCheckpoint, SlotNumber, } from '@aztec/foundation/branded-types'; -import type { BaseBuffer32 } from '@aztec/foundation/buffer'; -import { keccak256 } from '@aztec/foundation/crypto/keccak'; +import { type BaseBuffer32, Buffer32 } from '@aztec/foundation/buffer'; import { Fr } from '@aztec/foundation/curves/bn254'; import type { EthAddress } from '@aztec/foundation/eth-address'; import { Signature } from '@aztec/foundation/eth-signature'; @@ -21,6 +20,7 @@ import { BlockHeader } from '../tx/block_header.js'; import { TxHash } from '../tx/index.js'; import type { Tx } from '../tx/tx.js'; import { BlockProposal } from './block_proposal.js'; +import { ConsensusPayload } from './consensus_payload.js'; import { Gossipable } from './gossipable.js'; import { type CoordinationSignatureContext, @@ -105,7 +105,7 @@ export class CheckpointProposal extends Gossipable implements Signable { } override generateP2PMessageIdentifier(): Promise { - return Promise.resolve(CheckpointProposalHash.fromBuffer(keccak256(this.signature.toBuffer()))); + return Promise.resolve(new Buffer32(this.toConsensusPayload().getPayloadHash())); } get slotNumber(): SlotNumber { @@ -164,6 +164,24 @@ export class CheckpointProposal extends Gossipable implements Signable { return serializeToBuffer([this.checkpointHeader, this.archive, serializeSignedBigInt(this.feeAssetPriceModifier)]); } + /** + * Returns a content-addressed keccak256 hash of the consensus payload + * (header + archive + feeAssetPriceModifier + signatureContext). + * + * Used by the attestation pool to dedup distinct signed payloads at the same slot + * regardless of archive/header collisions on `feeAssetPriceModifier` variants. + * The hash deliberately excludes the signature so non-deterministic ECDSA + * re-signs of the same payload do not look like equivocation. + */ + getPayloadHash(): CheckpointProposalHash { + return CheckpointProposalHash.fromBuffer(this.toConsensusPayload().getPayloadHash()); + } + + /** Returns the ConsensusPayload that an attester would sign for this proposal. */ + toConsensusPayload(): ConsensusPayload { + return new ConsensusPayload(this.checkpointHeader, this.archive, this.feeAssetPriceModifier, this.signatureContext); + } + static async createProposalFromSigner( checkpointHeader: CheckpointHeader, archiveRoot: Fr, diff --git a/yarn-project/stdlib/src/p2p/consensus_payload.ts b/yarn-project/stdlib/src/p2p/consensus_payload.ts index 3054f02d5724..17b3267f2194 100644 --- a/yarn-project/stdlib/src/p2p/consensus_payload.ts +++ b/yarn-project/stdlib/src/p2p/consensus_payload.ts @@ -1,3 +1,4 @@ +import { keccak256 } from '@aztec/foundation/crypto/keccak'; import { Fr } from '@aztec/foundation/curves/bn254'; import { schemas } from '@aztec/foundation/schemas'; import { BufferReader, serializeSignedBigInt, serializeToBuffer } from '@aztec/foundation/serialize'; @@ -69,6 +70,14 @@ export class ConsensusPayload implements Signable { return hexToBuffer(encodedData); } + /** + * Returns a keccak256 hash of the signed payload (header + archive + feeAssetPriceModifier). + * Used by the attestation pool to dedup distinct signed payloads. + */ + getPayloadHash(): Buffer { + return keccak256(this.getPayloadToSign()); + } + toBuffer(): Buffer { return serializeToBuffer([ this.header, diff --git a/yarn-project/txe/src/state_machine/dummy_p2p_client.ts b/yarn-project/txe/src/state_machine/dummy_p2p_client.ts index 28e9564f1a79..3990a17d12d3 100644 --- a/yarn-project/txe/src/state_machine/dummy_p2p_client.ts +++ b/yarn-project/txe/src/state_machine/dummy_p2p_client.ts @@ -1,4 +1,4 @@ -import type { SlotNumber } from '@aztec/foundation/branded-types'; +import type { CheckpointProposalHash, SlotNumber } from '@aztec/foundation/branded-types'; import type { AuthRequest, ENR, @@ -147,7 +147,10 @@ export class DummyP2P implements P2P { throw new Error('DummyP2P does not implement "getTxsByHash"'); } - public getCheckpointAttestationsForSlot(_slot: SlotNumber, _proposalId?: string): Promise { + public getCheckpointAttestationsForSlot( + _slot: SlotNumber, + _proposalPayloadHash?: CheckpointProposalHash, + ): Promise { throw new Error('DummyP2P does not implement "getCheckpointAttestationsForSlot"'); } diff --git a/yarn-project/validator-client/src/proposal_handler.test.ts b/yarn-project/validator-client/src/proposal_handler.test.ts index 5023c7db4861..44a28f3d0cb4 100644 --- a/yarn-project/validator-client/src/proposal_handler.test.ts +++ b/yarn-project/validator-client/src/proposal_handler.test.ts @@ -211,6 +211,30 @@ describe('ProposalHandler checkpoint validation', () => { expect(blockSource.syncImmediate).toHaveBeenCalled(); }); + // Regression for A-1013: cache used to key by (archive, slot) which let two proposals at the + // same slot+archive but with a different feeAssetPriceModifier share the same cache entry. + it('does not cache across proposals that share archive and slot but differ in feeAssetPriceModifier', async () => { + blockSource.getBlockData.mockResolvedValue(undefined); + const sharedHeader = makeCheckpointHeader(0, { slotNumber: SlotNumber(1) }); + const sharedArchive = Fr.random(); + + const proposalA = await makeProposal({ + checkpointHeader: sharedHeader, + archiveRoot: sharedArchive, + feeAssetPriceModifier: 50n, + }); + await handler.handleCheckpointProposal(proposalA, proposalInfo); + blockSource.syncImmediate.mockClear(); + + const proposalB = await makeProposal({ + checkpointHeader: sharedHeader, + archiveRoot: sharedArchive, + feeAssetPriceModifier: -50n, + }); + await handler.handleCheckpointProposal(proposalB, proposalInfo); + expect(blockSource.syncImmediate).toHaveBeenCalled(); + }); + it('returns block_fetch_error when getBlockData throws', async () => { blockSource.getBlockData.mockRejectedValue(new Error('db connection failed')); diff --git a/yarn-project/validator-client/src/proposal_handler.ts b/yarn-project/validator-client/src/proposal_handler.ts index 6bc78992c4b3..cdf951a68b34 100644 --- a/yarn-project/validator-client/src/proposal_handler.ts +++ b/yarn-project/validator-client/src/proposal_handler.ts @@ -4,7 +4,12 @@ import { type Blob, encodeCheckpointBlobDataFromBlocks, getBlobsPerL1Block } fro import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; import type { EpochCache } from '@aztec/epoch-cache'; import { validateFeeAssetPriceModifier } from '@aztec/ethereum/contracts'; -import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types'; +import { + BlockNumber, + CheckpointNumber, + type CheckpointProposalHash, + SlotNumber, +} from '@aztec/foundation/branded-types'; import { pick } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; import { TimeoutError } from '@aztec/foundation/error'; @@ -88,10 +93,11 @@ type CheckpointComputationResult = export class ProposalHandler { public readonly tracer: Tracer; - /** Cached last checkpoint validation result to avoid double-validation on validator nodes. */ + /** Cached last checkpoint validation result to avoid double-validation on validator nodes. + * Keyed by signed-payload hash so two proposals at the same (slot, archive) but with a + * different `feeAssetPriceModifier` (or any other signed field) are validated independently. */ private lastCheckpointValidationResult?: { - archive: Fr; - slotNumber: SlotNumber; + payloadHash: CheckpointProposalHash; result: CheckpointProposalValidationResult; }; @@ -735,13 +741,10 @@ export class ProposalHandler { proposalInfo: LogData, ): Promise { const slot = proposal.slotNumber; + const payloadHash = proposal.getPayloadHash(); - // Check cache: same archive+slot means we already validated this proposal - if ( - this.lastCheckpointValidationResult && - this.lastCheckpointValidationResult.archive.equals(proposal.archive) && - this.lastCheckpointValidationResult.slotNumber === slot - ) { + // Check cache: same signed-payload hash means we already validated this exact proposal. + if (this.lastCheckpointValidationResult && this.lastCheckpointValidationResult.payloadHash === payloadHash) { this.log.debug(`Returning cached validation result for checkpoint proposal at slot ${slot}`, proposalInfo); return this.lastCheckpointValidationResult.result; } @@ -750,7 +753,7 @@ export class ProposalHandler { if (!proposer) { this.log.warn(`Received checkpoint proposal with invalid signature for slot ${proposal.slotNumber}`); const result: CheckpointProposalValidationResult = { isValid: false, reason: 'invalid_signature' }; - this.lastCheckpointValidationResult = { archive: proposal.archive, slotNumber: slot, result }; + this.lastCheckpointValidationResult = { payloadHash, result }; return result; } @@ -759,12 +762,12 @@ export class ProposalHandler { `Received checkpoint proposal with invalid feeAssetPriceModifier ${proposal.feeAssetPriceModifier} for slot ${proposal.slotNumber}`, ); const result: CheckpointProposalValidationResult = { isValid: false, reason: 'invalid_fee_asset_price_modifier' }; - this.lastCheckpointValidationResult = { archive: proposal.archive, slotNumber: slot, result }; + this.lastCheckpointValidationResult = { payloadHash, result }; return result; } const result = await this.validateCheckpointProposal(proposal, proposalInfo); - this.lastCheckpointValidationResult = { archive: proposal.archive, slotNumber: slot, result }; + this.lastCheckpointValidationResult = { payloadHash, result }; // Upload blobs to filestore if validation passed (fire and forget) if (result.isValid) { diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index bf17e92b2dec..d1286c4d086e 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -261,8 +261,9 @@ describe('ValidatorClient', () => { makeCheckpointAttestation({ signer: attestor1, archive, header: proposal.checkpointHeader }), makeCheckpointAttestation({ signer: attestor2, archive, header: proposal.checkpointHeader }), ]; - p2pClient.getCheckpointAttestationsForSlot.mockImplementation((slot, proposalId) => { - if (proposal.slotNumber === slot && proposalId === proposal.archive.toString()) { + const expectedPayloadHash = proposal.getPayloadHash(); + p2pClient.getCheckpointAttestationsForSlot.mockImplementation((slot, proposalPayloadHash) => { + if (proposal.slotNumber === slot && proposalPayloadHash === expectedPayloadHash) { return Promise.resolve(expectedAttestations); } return Promise.resolve([]); @@ -294,41 +295,36 @@ describe('ValidatorClient', () => { expect(addCheckpointAttestationsSpy.mock.calls[0][0]).toHaveLength(2); }); - it('should filter out attestations with mismatched payload', async () => { + it('forwards the proposal payload hash to the pool so mismatched attestations are filtered out', async () => { const signer = Secp256k1Signer.random(); const attestor1 = Secp256k1Signer.random(); - const attestor2 = Secp256k1Signer.random(); const archive = Fr.random(); const txHashes = [0, 1, 2, 3, 4, 5].map(() => TxHash.random()); const proposal = await makeCheckpointProposal({ signer, archiveRoot: archive, lastBlock: { txHashes } }); - // Create attestations - one with matching payload, one with mismatched + // The pool is responsible for filtering by payload hash; the validator just forwards it. + // We mock the pool to return the matching attestation only when queried with the right hash. const validAttestation = makeCheckpointAttestation({ signer: attestor1, archive, header: proposal.checkpointHeader, }); - const invalidAttestation = makeCheckpointAttestation({ - signer: attestor2, - archive: Fr.random(), - header: proposal.checkpointHeader, - }); - p2pClient.getCheckpointAttestationsForSlot.mockImplementation((slot, proposalId) => - proposal.slotNumber === slot && proposalId === proposal.archive.toString() - ? Promise.resolve([validAttestation, invalidAttestation]) + const expectedPayloadHash = proposal.getPayloadHash(); + p2pClient.getCheckpointAttestationsForSlot.mockImplementation((slot, proposalPayloadHash) => + proposal.slotNumber === slot && proposalPayloadHash === expectedPayloadHash + ? Promise.resolve([validAttestation]) : Promise.resolve([]), ); - // Perform the query - should timeout but we're testing the filtering behavior + // Only one matching attestation is returned, but the validator needs 2 -> times out. await expect( validatorClient.collectAttestations(proposal, 2, new Date(dateProvider.now() + 1000), CheckpointNumber(1)), ).rejects.toThrow(AttestationTimeoutError); - // Verify that getCheckpointAttestationsForSlot was called (meaning the loop ran) - expect(p2pClient.getCheckpointAttestationsForSlot).toHaveBeenCalled(); + expect(p2pClient.getCheckpointAttestationsForSlot).toHaveBeenCalledWith(proposal.slotNumber, expectedPayloadHash); }); }); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 98a60a50a08c..d92a51717233 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -881,33 +881,21 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter) await this.collectOwnAttestations(proposal, checkpointNumber); - const proposalId = proposal.archive.toString(); + const proposalPayloadHash = proposal.getPayloadHash(); const myAddresses = this.getValidatorAddresses(); let attestations: CheckpointAttestation[] = []; while (true) { - // Filter out attestations with a mismatching archive. This should NOT happen since we have verified - // the proposer signature (ie our own) before accepting the attestation into the pool via the p2p client. - const collectedAttestations = (await this.p2pClient.getCheckpointAttestationsForSlot(slot, proposalId)).filter( - attestation => { - if (!attestation.archive.equals(proposal.archive)) { - this.log.warn( - `Received attestation for slot ${slot} with mismatched archive from ${attestation - .getSender() - ?.toString()}`, - { attestationArchive: attestation.archive.toString(), proposalArchive: proposal.archive.toString() }, - ); - return false; - } - return true; - }, - ); + // The pool already filters by proposal payload hash; if any attestation slips through with a + // mismatched payload hash, drop it defensively. Equivocations are emitted as separate slash + // events from libp2p_service. + const collectedAttestations = await this.p2pClient.getCheckpointAttestationsForSlot(slot, proposalPayloadHash); // Log new attestations we collected const oldSenders = attestations.map(attestation => attestation.getSender()); for (const collected of collectedAttestations) { const collectedSender = collected.getSender(); - // Skip attestations with invalid signatures + // Skip attestations with invalid signatures. Should not happen as we don't add invalid attestations to our pool. if (!collectedSender) { this.log.warn(`Skipping attestation with invalid signature for slot ${slot}`); continue; From a9c6ad7e047f7cbc2b00cb722d1bf62ae43d7cd5 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Tue, 5 May 2026 13:46:22 +0100 Subject: [PATCH 06/15] chore: notify slack users directly (#22944) Notify slack users directly in Grafan alerts --- .github/workflows/metrics-deploy.yml | 10 ++++++++++ spartan/metrics/grafana/alerts/contactpoints.yaml | 8 ++++++++ spartan/terraform/deploy-metrics/main.tf | 3 ++- spartan/terraform/deploy-metrics/variables.tf | 6 ++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/metrics-deploy.yml b/.github/workflows/metrics-deploy.yml index 9ccc2ad2a555..b8774a1a7e96 100644 --- a/.github/workflows/metrics-deploy.yml +++ b/.github/workflows/metrics-deploy.yml @@ -37,6 +37,11 @@ on: required: true type: string default: "grafana-dashboard-password" + slack_alert_mention_user_ids: + description: Optional Terraform list of Slack user IDs to mention on Grafana alert notifications + required: false + type: string + default: '["U0AHB6VR8N5"]' secrets: GCP_SA_KEY: required: true @@ -70,6 +75,10 @@ on: description: The name of the secret which holds the Grafana dashboard password required: true default: "grafana-dashboard-password" + slack_alert_mention_user_ids: + description: Optional Terraform list of Slack user IDs to mention on Grafana alert notifications + required: false + default: '["U0AHB6VR8N5"]' jobs: metrics_deployment: @@ -96,6 +105,7 @@ jobs: SLACK_WEBHOOK_NEXT_NET_SECRET_NAME: slack-webhook-next-net-url SLACK_WEBHOOK_TESTNET_SECRET_NAME: slack-webhook-testnet-url SLACK_WEBHOOK_MAINNET_SECRET_NAME: slack-webhook-mainnet-url + TF_VAR_SLACK_ALERT_MENTION_USER_IDS: ${{ inputs.slack_alert_mention_user_ids }} steps: - name: Checkout code diff --git a/spartan/metrics/grafana/alerts/contactpoints.yaml b/spartan/metrics/grafana/alerts/contactpoints.yaml index 03b883cb5c6c..956d94cce523 100644 --- a/spartan/metrics/grafana/alerts/contactpoints.yaml +++ b/spartan/metrics/grafana/alerts/contactpoints.yaml @@ -7,6 +7,7 @@ contactPoints: type: slack settings: url: $SLACK_WEBHOOK_URL + mentionUsers: $SLACK_ALERT_MENTION_USER_IDS text: |- {{ template "aztec.slack.by_namespace" . }} disableResolveMessage: false @@ -18,6 +19,7 @@ contactPoints: type: slack settings: url: $SLACK_WEBHOOK_NEXT_SCENARIO_URL + mentionUsers: $SLACK_ALERT_MENTION_USER_IDS text: |- {{ template "aztec.slack.by_namespace" . }} disableResolveMessage: false @@ -29,6 +31,7 @@ contactPoints: type: slack settings: url: $SLACK_WEBHOOK_NEXT_NET_URL + mentionUsers: $SLACK_ALERT_MENTION_USER_IDS text: |- {{ template "aztec.slack.by_namespace" . }} disableResolveMessage: false @@ -40,6 +43,7 @@ contactPoints: type: slack settings: url: $SLACK_WEBHOOK_TESTNET_URL + mentionUsers: $SLACK_ALERT_MENTION_USER_IDS text: |- {{ template "aztec.slack.by_namespace" . }} disableResolveMessage: false @@ -51,6 +55,7 @@ contactPoints: type: slack settings: url: $SLACK_WEBHOOK_MAINNET_URL + mentionUsers: $SLACK_ALERT_MENTION_USER_IDS text: |- {{ template "aztec.slack.by_namespace" . }} disableResolveMessage: false @@ -62,6 +67,7 @@ contactPoints: type: slack settings: url: $SLACK_WEBHOOK_TESTNET_URL + mentionUsers: $SLACK_ALERT_MENTION_USER_IDS text: |- {{ template "aztec.slack.by_network" . }} disableResolveMessage: false @@ -73,6 +79,7 @@ contactPoints: type: slack settings: url: $SLACK_WEBHOOK_MAINNET_URL + mentionUsers: $SLACK_ALERT_MENTION_USER_IDS text: |- {{ template "aztec.slack.by_network" . }} disableResolveMessage: false @@ -84,6 +91,7 @@ contactPoints: type: slack settings: url: $SLACK_WEBHOOK_DEVNET_URL + mentionUsers: $SLACK_ALERT_MENTION_USER_IDS text: |- {{ template "aztec.slack.by_namespace" . }} disableResolveMessage: false diff --git a/spartan/terraform/deploy-metrics/main.tf b/spartan/terraform/deploy-metrics/main.tf index 47a5c27cc1a0..57467ee59855 100644 --- a/spartan/terraform/deploy-metrics/main.tf +++ b/spartan/terraform/deploy-metrics/main.tf @@ -125,7 +125,8 @@ resource "helm_release" "aztec-gke-cluster" { env = { # we have to set an admin username through env vars otherwise the chart expects to find an 'admin-user' key in the admin secret - GF_SECURITY_ADMIN_USER = "admin" + GF_SECURITY_ADMIN_USER = "admin" + SLACK_ALERT_MENTION_USER_IDS = join(",", var.SLACK_ALERT_MENTION_USER_IDS) } sidecar = { diff --git a/spartan/terraform/deploy-metrics/variables.tf b/spartan/terraform/deploy-metrics/variables.tf index 88ca6e974f13..f7327e84877e 100644 --- a/spartan/terraform/deploy-metrics/variables.tf +++ b/spartan/terraform/deploy-metrics/variables.tf @@ -70,6 +70,12 @@ variable "SLACK_WEBHOOK_MAINNET_SECRET_NAME" { default = "slack-webhook-mainnet-url" } +variable "SLACK_ALERT_MENTION_USER_IDS" { + description = "Optional Slack user IDs to mention on Grafana alert notifications." + type = list(string) + default = ["U0AHB6VR8N5"] +} + variable "project" { default = "testnet-440309" type = string From b7113e6d81aa20ed79ef9491e31fcd74359d2f7c Mon Sep 17 00:00:00 2001 From: ledwards2225 <98505400+ledwards2225@users.noreply.github.com> Date: Tue, 5 May 2026 11:58:11 -0700 Subject: [PATCH 07/15] chore: skip zero-init and reserve copy_cycle vectors (#22963) Two small mem optimizations targeting alloc-heavy slices of `Chonk::accumulate`: **Skip zero-init for fully-overwritten polynomials** - `PartiallyEvaluatedMultivariates`: `partially_evaluate` writes every cell in `[0, desired_size)` before any read; the virtual tail past `desired_size` is served by `SharedShiftedVirtualZeroesArray`'s implicit zeros. - ProverInstance sigma/id: `compute_honk_style_permutation_lagrange_polynomials_from_mapping` writes every cell in the active range; the virtual tail outside it remains implicitly zero. Adds an overload `Polynomial::shiftable(size, virtual_size, DontZeroMemory)` that mirrors the existing `shiftable()` factory but leaves the backing memory uninitialized. **Reserve copy_cycle vectors** Adds a counting pre-pass over all blocks that tallies copy-cycle sizes per real-variable index, then `reserve()`s each `copy_cycles[i]` once before the serial concat in phase 1.5. Eliminates the amortized realloc cost across the concat. ## Perf (3-run native median, transfer_1+sponsored_fpc, 16 threads) | Operation | Before (ms) | After (ms) | Delta | |-------------------------------------------------|------------:|-----------:|---------:| | fill_copy_cycles | 62.66 | 13.76 | -78.05% | | allocate_permutation_argument_polynomials | 13.96 | 1.81 | -87.06% | | Polynomial::zero_init (system-wide) | 549.71 | 213.84 | -61.10% | | Polynomial::Polynomial(size_t,size_t,size_t) | 163.46 | 81.26 | -50.29% | | Chonk::accumulate (warm aggregate) | 2881.23 | 2835.98 | -1.57% | | Chonk::prove | 2119.53 | 2088.02 | -1.49% | | ChonkAPI::prove (total wall) | 5834.66 | 5757.96 | -1.31% | --- .../partially_evaluated_multivariates.hpp | 3 ++- .../barretenberg/polynomials/polynomial.hpp | 9 +++++++ .../trace_to_polynomials.cpp | 24 ++++++++++++++++++- .../ultra_honk/prover_instance.cpp | 8 ++++--- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/flavor/partially_evaluated_multivariates.hpp b/barretenberg/cpp/src/barretenberg/flavor/partially_evaluated_multivariates.hpp index c6017731c72d..0f67ddf9b713 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/partially_evaluated_multivariates.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/partially_evaluated_multivariates.hpp @@ -33,7 +33,8 @@ class PartiallyEvaluatedMultivariatesBase : public AllEntitiesBase { for (auto [poly, full_poly] : zip_view(this->get_all(), full_polynomials.get_all())) { // After the initial sumcheck round, the new size is CEIL(size/2). size_t desired_size = (full_poly.end_index() / 2) + (full_poly.end_index() % 2); - poly = Polynomial(desired_size, circuit_size / 2); + // partially_evaluate writes to [0, desired_size) before any read; backing memory can be left uninitialized. + poly = Polynomial(desired_size, circuit_size / 2, 0, Polynomial::DontZeroMemory::FLAG); } } }; diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp index 0e30e936b4ce..7ac33b963303 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp @@ -126,6 +126,15 @@ template class Polynomial { } return p; } + /** + * @brief Overload of `shiftable` that leaves the backing memory uninitialized. + * @details Use only when the caller writes every cell in [NUM_ZERO_ROWS, NUM_ZERO_ROWS + size) + * before any read. + */ + static Polynomial shiftable(size_t size, size_t virtual_size, DontZeroMemory flag) + { + return Polynomial(/*actual size*/ size - NUM_ZERO_ROWS, virtual_size, /*shiftable offset*/ NUM_ZERO_ROWS, flag); + } // Allow polynomials to be entirely reset/dormant Polynomial() = default; diff --git a/barretenberg/cpp/src/barretenberg/trace_to_polynomials/trace_to_polynomials.cpp b/barretenberg/cpp/src/barretenberg/trace_to_polynomials/trace_to_polynomials.cpp index a8af8bd79885..98e86abc86b1 100644 --- a/barretenberg/cpp/src/barretenberg/trace_to_polynomials/trace_to_polynomials.cpp +++ b/barretenberg/cpp/src/barretenberg/trace_to_polynomials/trace_to_polynomials.cpp @@ -58,6 +58,28 @@ std::vector TraceToPolynomials::populate_wires_and_se auto blocks_array = builder.blocks.get(); const size_t num_blocks = blocks_array.size(); + // Pre-pass: count copy-cycle sizes per real-variable index so each copy_cycles[i] can be + // reserve()d once before the serial concat in phase 1.5, avoiding repeated reallocations. + { + BB_BENCH_NAME("counting copy_cycles"); + std::vector cycle_counts(builder.real_variable_index.size(), 0); + for (auto& block : blocks_array) { + const uint32_t block_size = static_cast(block.size()); + for (uint32_t block_row_idx = 0; block_row_idx < block_size; ++block_row_idx) { + for (uint32_t wire_idx = 0; wire_idx < NUM_WIRES; ++wire_idx) { + uint32_t var_idx = block.wires[wire_idx][block_row_idx]; + // var_idx may be untrusted (e.g. from ACIR) so use .at() to catch OOB. This validates real_var_idx + // as an in-range index for both cycle_counts and copy_cycles (same size), which is why phase 1.5 + // below can index copy_cycles[real_var_idx] without .at(). + ++cycle_counts.at(builder.real_variable_index.at(var_idx)); + } + } + } + for (size_t i = 0; i < copy_cycles.size(); ++i) { + copy_cycles[i].reserve(cycle_counts[i]); + } + } + // Phase 1: per-block parallel pass over wires and emit copy-cycle nodes. std::vector>> per_block_nodes(num_blocks); { @@ -89,7 +111,7 @@ std::vector TraceToPolynomials::populate_wires_and_se BB_BENCH_NAME("fill_copy_cycles"); for (const auto& block_nodes : per_block_nodes) { for (const auto& [real_var_idx, node] : block_nodes) { - copy_cycles.at(real_var_idx).emplace_back(node); + copy_cycles[real_var_idx].emplace_back(node); } } } diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp index fe42a5e54451..625bb028d904 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp @@ -154,12 +154,14 @@ template void ProverInstance_::allocate_permutation_ar { BB_BENCH_NAME("allocate_permutation_argument_polynomials"); - // Sigma and ID polynomials are zero outside the active trace range + // Sigma and ID polynomials are zero outside the active trace range. Inside the active range, + // compute_honk_style_permutation_lagrange_polynomials_from_mapping writes every cell, so the + // backing memory can be left uninitialized. for (auto& sigma : polynomials.get_sigmas()) { - sigma = Polynomial::shiftable(trace_active_range_size(), dyadic_size()); + sigma = Polynomial::shiftable(trace_active_range_size(), dyadic_size(), Polynomial::DontZeroMemory::FLAG); } for (auto& id : polynomials.get_ids()) { - id = Polynomial::shiftable(trace_active_range_size(), dyadic_size()); + id = Polynomial::shiftable(trace_active_range_size(), dyadic_size(), Polynomial::DontZeroMemory::FLAG); } polynomials.z_perm = Polynomial::shiftable(trace_active_range_size(), dyadic_size(), Flavor::HasZK); From 90af7407cd8ad7e0fbc652825fa10fafacd696ed Mon Sep 17 00:00:00 2001 From: rkarabut Date: Wed, 6 May 2026 10:46:30 +0000 Subject: [PATCH 08/15] feat(protocol-fuzzer): local setup, configurable artifacts, and bridge rename - Add setup-local.sh for running against a locally-built sandbox (no Docker) - Make artifact directory configurable via --artifacts-dir CLI arg - Rename bridge.mjs -> wallet-bridge.mjs to avoid ambiguity - Wait for port release after kill in setup-local.sh - Document local vs Docker artifact path distinction in README Co-Authored-By: Claude Opus 4.6 Co-Authored-By: Claude Opus 4.7 (1M context) --- noir-projects/protocol-fuzzer/.gitignore | 2 + noir-projects/protocol-fuzzer/README.md | 41 ++- .../protocol-fuzzer/SANDBOX_INSTRUCTIONS.md | 18 +- noir-projects/protocol-fuzzer/setup-local.sh | 302 ++++++++++++++++++ .../protocol-fuzzer/setup-nightly-sandbox.sh | 8 +- noir-projects/protocol-fuzzer/src/main.rs | 11 + .../src/side_effect/machine.rs | 6 +- .../protocol-fuzzer/src/side_effect/system.rs | 18 +- .../{bridge.mjs => wallet-bridge.mjs} | 31 +- 9 files changed, 399 insertions(+), 38 deletions(-) create mode 100755 noir-projects/protocol-fuzzer/setup-local.sh rename noir-projects/protocol-fuzzer/{bridge.mjs => wallet-bridge.mjs} (76%) diff --git a/noir-projects/protocol-fuzzer/.gitignore b/noir-projects/protocol-fuzzer/.gitignore index ecd9d7472433..4d9a90ba887b 100644 --- a/noir-projects/protocol-fuzzer/.gitignore +++ b/noir-projects/protocol-fuzzer/.gitignore @@ -1,3 +1,5 @@ /target contracts/target/ *.sw? +# Symlink to yarn-project/node_modules created by setup-local.sh. +/node_modules diff --git a/noir-projects/protocol-fuzzer/README.md b/noir-projects/protocol-fuzzer/README.md index 6116998ff2d9..a3f6103ef6c5 100644 --- a/noir-projects/protocol-fuzzer/README.md +++ b/noir-projects/protocol-fuzzer/README.md @@ -1,5 +1,5 @@ A state-machine fuzzer for Aztec contract interactions. It talks to a running -sandbox via a persistent Node.js HTTP bridge (`bridge.mjs`), compares the +sandbox via a persistent Node.js HTTP bridge (`wallet-bridge.mjs`), compares the sandbox's behavior to an in-memory model, and asserts on any divergence. Two machines are available: @@ -19,16 +19,22 @@ it uses the standard `Token` contract that ships with the wallet CLI: cargo run -- token --max-steps 100 ``` -The **side-effect** machine requires the **nightly** sandbox because it deploys custom -contracts compiled against the nightly's aztec-nr. Use `setup-nightly-sandbox.sh` to -automate the full setup (defaults to the last tested nightly tag; pass `--latest` to -try the newest one). See `SANDBOX_INSTRUCTIONS.md` for manual steps. +The **side-effect** machine requires custom contracts. There are two ways to set it up: +**Local setup** (no Docker, uses your repo build): +``` +bash setup-local.sh # compiles contracts, starts anvil + node + bridge +cargo run -- side-effect --artifacts-dir contracts/target --max-steps 100 ``` -bash setup-nightly-sandbox.sh +**Nightly Docker setup** (defaults to the last tested nightly tag; pass `--latest` for newest): +``` +bash setup-nightly-sandbox.sh cargo run -- side-effect --max-steps 100 ``` +The nightly script places artifacts at `/tmp/` inside the container (the default `--artifacts-dir`). + +See `SANDBOX_INSTRUCTIONS.md` for manual nightly steps and troubleshooting. To replay a specific failure seed: @@ -44,8 +50,15 @@ cargo run -- side-effect --max-steps 100000 --seed 0x5a7211231dcd6500 --seed 0xHEX Replay a specific seed --max-steps N Max fuzzing steps (default: 400) --max-batch-size N Max parallel sends per batch (default: 8) +--artifacts-dir DIR Contract artifact directory (side-effect only, default: /tmp) ``` +> **Note:** `--artifacts-dir` is resolved on the host and sent as-is to the bridge. +> For **local** setup the bridge runs on the host, so use the real path (e.g. +> `contracts/target`). For **nightly Docker** the bridge runs inside the container, +> so the path must be valid inside it — the default `/tmp` works because the nightly +> script places artifacts at `/tmp/*.json` inside the container. + ### Parallel batching Consecutive non-conflicting state-changing commands are batched and fired concurrently, @@ -64,12 +77,16 @@ Conflict rules (conservative -- false positives only reduce batch size): To verify that the sandbox is running correctly, run the integration smoke tests: ``` -cargo test -- --ignored --nocapture +ARTIFACTS_DIR=contracts/target cargo test -- --ignored --nocapture ``` These are `#[ignore]`d by default because they require a running sandbox. With bridge + fast slots, a full suite run takes ~1-2 minutes (~5-13s per transaction). +Environment variables for tests: +- `ARTIFACTS_DIR` -- contract artifact directory (default: `/tmp`) +- `BRIDGE_URL` -- bridge server URL (default: `http://localhost:8089`) + ## Contracts Contract sources live in `contracts/` within this crate, not in `noir-contracts/`. They @@ -82,9 +99,9 @@ with oracle calls (like `utilityLog`) that the nightly PXE doesn't support. - **Parent** (`contracts/parent_contract/`) -- forwards calls to SideEffect for cross-contract call testing -Artifacts are built by `setup-nightly-sandbox.sh` inside the nightly container and -placed in `contracts/target/` (not checked into git). +Artifacts are built by `setup-local.sh` or `setup-nightly-sandbox.sh` and placed in +`contracts/target/`. Pre-built artifacts are checked into git for convenience. -The setup script auto-detects the nightly commit by matching the container's nargo -hash against `origin/next`. See `SANDBOX_INSTRUCTIONS.md` for the full build pipeline, -version matrix, and troubleshooting. +The nightly setup script auto-detects the nightly commit by matching the container's +nargo hash against `origin/next`. See `SANDBOX_INSTRUCTIONS.md` for the full build +pipeline, version matrix, and troubleshooting. diff --git a/noir-projects/protocol-fuzzer/SANDBOX_INSTRUCTIONS.md b/noir-projects/protocol-fuzzer/SANDBOX_INSTRUCTIONS.md index 760425a0bcae..36438de6aa8f 100644 --- a/noir-projects/protocol-fuzzer/SANDBOX_INSTRUCTIONS.md +++ b/noir-projects/protocol-fuzzer/SANDBOX_INSTRUCTIONS.md @@ -1,5 +1,9 @@ # Protocol Fuzzer: Running with Nightly Docker Sandbox +> **For local development** (no Docker), use `setup-local.sh` instead. It starts +> anvil, the Aztec node, compiles contracts, and launches the bridge — all on the +> host. See `README.md` for quick-start instructions. + ## Overview The protocol fuzzer has two state machines: @@ -9,7 +13,7 @@ The protocol fuzzer has two state machines: - **side-effect**: Fuzzes note lifecycle, nullifier emission, and cross-contract calls via custom `SideEffect` and `Parent` contracts. **Requires the nightly sandbox.** -Both machines talk to the sandbox via a persistent Node.js HTTP bridge (`bridge.mjs`) +Both machines talk to the sandbox via a persistent Node.js HTTP bridge (`wallet-bridge.mjs`) that keeps a single CLIWallet instance alive across requests. ## Why the nightly sandbox? @@ -69,7 +73,7 @@ for the next block. Four things bring per-transaction time from ~35s down to ~4- 1. **Fast slots.** The setup script starts the sandbox with 5-second L1/L2 slot durations (default 36s/12s) and disables sequencer timetable enforcement. -2. **Persistent bridge.** `bridge.mjs` keeps a single Node.js wallet instance alive inside +2. **Persistent bridge.** `wallet-bridge.mjs` keeps a single Node.js wallet instance alive inside the container. Without it, each operation would shell out to the CLI wallet, paying a ~1.5s Node.js cold-start every time. 3. **Parallel batching.** The fuzzer buffers consecutive non-conflicting sends and fires @@ -216,14 +220,14 @@ done ### 5. Start the bridge server -The bridge server (`bridge.mjs`) runs inside the container and provides a persistent +The bridge server (`wallet-bridge.mjs`) runs inside the container and provides a persistent HTTP API that the fuzzer calls: ```bash -docker cp bridge.mjs aztec-sandbox-nightly:/usr/src/yarn-project/bridge.mjs +docker cp wallet-bridge.mjs aztec-sandbox-nightly:/usr/src/yarn-project/wallet-bridge.mjs docker exec -d aztec-sandbox-nightly \ - bash -c 'cd /usr/src/yarn-project && exec node --no-warnings bridge.mjs > /tmp/bridge.log 2>&1' + bash -c 'cd /usr/src/yarn-project && exec node --no-warnings wallet-bridge.mjs > /tmp/bridge.log 2>&1' # Wait for it to start curl -s http://localhost:8089/health # {"ok":true} @@ -317,7 +321,7 @@ on every operation. ### How the bridge works -`bridge.mjs` runs inside the container and lazily initializes a `CLIWallet` instance +`wallet-bridge.mjs` runs inside the container and lazily initializes a `CLIWallet` instance on the first request. The Rust fuzzer resolves aliases (`accounts:test0`, `contracts:test0`) to hex addresses before sending them to the bridge via HTTP POST. @@ -344,7 +348,7 @@ on the first request. The Rust fuzzer resolves aliases (`accounts:test0`, | wallet CLI | `node --no-warnings /usr/src/yarn-project/cli-wallet/dest/bin/index.js` | | sandbox CLI | `node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js` | | anvil | `/opt/foundry/bin/anvil` | -| bridge server | `/usr/src/yarn-project/bridge.mjs` | +| bridge server | `/usr/src/yarn-project/wallet-bridge.mjs` | | bridge log | `/tmp/bridge.log` | ## Stopping diff --git a/noir-projects/protocol-fuzzer/setup-local.sh b/noir-projects/protocol-fuzzer/setup-local.sh new file mode 100755 index 000000000000..8d169bf2079a --- /dev/null +++ b/noir-projects/protocol-fuzzer/setup-local.sh @@ -0,0 +1,302 @@ +#!/usr/bin/env bash +# +# Sets up a local Aztec sandbox for use with the protocol fuzzer. +# No Docker required — everything runs on the host. +# +# Prerequisites: +# cd $REPO_ROOT && ./bootstrap.sh build yarn-project +# +# What this script does: +# 1. Checks prerequisites (nargo, bb, anvil, node, jq, yarn-project build) +# 2. Kills any existing processes on ports 8545/8080/8089 +# 3. Starts anvil (L1) and the Aztec node + PXE +# 4. Compiles contracts (nargo + transpile + VK generation) +# 5. Starts the bridge server +# +# Options: +# --skip-compile Skip contract compilation (reuse existing artifacts) +# +set -euo pipefail + +SKIP_COMPILE=false +for arg in "$@"; do + case "$arg" in + --skip-compile) SKIP_COMPILE=true ;; + *) echo "Unknown option: $arg" >&2; exit 1 ;; + esac +done + +REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CONTRACTS_DIR="${SCRIPT_DIR}/contracts" + +# Binaries (overridable via env vars) +NARGO="${NARGO:-${REPO_ROOT}/noir/noir-repo/target/release/nargo}" +BB="${BB:-${REPO_ROOT}/barretenberg/cpp/build/bin/bb}" +ANVIL="${ANVIL:-/opt/foundry/bin/anvil}" +TRANSPILER="${TRANSPILER:-${REPO_ROOT}/avm-transpiler/target/release/avm-transpiler}" + +# --------------------------------------------------------------------------- # +# Helpers +# --------------------------------------------------------------------------- # + +log() { echo "==> $*"; } +die() { echo "ERROR: $*" >&2; exit 1; } + +# PIDs of background processes we start — killed if the script fails partway through. +BG_PIDS=() +SETUP_COMPLETE=false +cleanup() { + if [ "$SETUP_COMPLETE" = true ]; then + return + fi + if [ ${#BG_PIDS[@]} -gt 0 ]; then + log "Setup failed — stopping background processes: ${BG_PIDS[*]}" + kill "${BG_PIDS[@]}" 2>/dev/null || true + wait "${BG_PIDS[@]}" 2>/dev/null || true + fi +} +trap cleanup EXIT + +# kill_on_port PORT — kill any process listening on this TCP port +kill_on_port() { + local port=$1 pids + pids=$(lsof -ti "tcp:${port}" 2>/dev/null || true) + if [ -n "$pids" ]; then + log "Killing process(es) on port ${port}: ${pids}" + echo "$pids" | xargs kill -9 2>/dev/null || true + # Wait for the port to be fully released (up to 10s). + local elapsed=0 + while lsof -ti "tcp:${port}" >/dev/null 2>&1 && [ $elapsed -lt 10 ]; do + sleep 1 + elapsed=$((elapsed + 1)) + done + fi +} + +# wait_for_url PID URL MAX_SECONDS LOG_FILE +# Polls URL every 2s. Bails early if PID dies, printing last 30 lines of LOG_FILE. +wait_for_url() { + local pid=$1 url=$2 max=$3 logfile=$4 elapsed=0 + while [ $elapsed -lt "$max" ]; do + if ! kill -0 "$pid" 2>/dev/null; then + echo "--- last 30 lines of ${logfile} ---" + tail -30 "$logfile" + die "Process ${pid} exited unexpectedly" + fi + local code + code=$(curl -so /dev/null -w '%{http_code}' "$url" 2>/dev/null || true) + [ -n "$code" ] && [ "$code" != "000" ] && return 0 + sleep 2 + elapsed=$((elapsed + 2)) + done + die "Timed out after ${max}s waiting for ${url}. Check ${logfile}" +} + +# --------------------------------------------------------------------------- # +# 1. Check prerequisites +# --------------------------------------------------------------------------- # + +log "Checking prerequisites..." + +missing=() +[ -x "$NARGO" ] || missing+=("nargo (expected at ${NARGO})") +[ -x "$BB" ] || missing+=("bb (expected at ${BB})") +[ -x "$ANVIL" ] || missing+=("anvil (expected at ${ANVIL})") +command -v node >/dev/null || missing+=("node") +command -v jq >/dev/null || missing+=("jq") +command -v curl >/dev/null || missing+=("curl") + +if [ ${#missing[@]} -gt 0 ]; then + die "Missing prerequisites: ${missing[*]} + Run the full bootstrap to build everything: + cd ${REPO_ROOT} && ./bootstrap.sh build yarn-project" +fi + +# Determine which tool to use for artifact processing (transpilation + prefix stripping). +# bb aztec_process (new) does it all in one step; avm-transpiler (old) needs separate steps. +USE_BB_AZTEC_PROCESS=false +if "$BB" aztec_process 2>&1 | grep -q "contract artifact"; then + USE_BB_AZTEC_PROCESS=true + log "Using: bb aztec_process" +elif [ -x "$TRANSPILER" ]; then + log "Using: avm-transpiler + jq prefix strip + bb write_vk" +else + die "No artifact processor found. + Option A: Rebuild bb with AVM support + Option B: Build the avm-transpiler: + cd ${REPO_ROOT}/avm-transpiler && cargo build --release" +fi + +# Verify yarn-project is properly built. A partial build (swc-only, no generate steps) +# will have dest/ dirs but missing generated files, causing runtime crashes. +YP="${REPO_ROOT}/yarn-project" +missing_build=() +[ -d "${YP}/cli-wallet/dest" ] || missing_build+=("cli-wallet/dest") +[ -f "${YP}/cli/dest/config/generated/networks.js" ] || missing_build+=("cli/generated/networks.js") +[ -f "${YP}/ethereum/dest/generated/l1-contracts-defaults.js" ] || missing_build+=("ethereum/generated/l1-contracts-defaults.js") +[ -f "${YP}/noir-protocol-circuits-types/dest/vk_tree.js" ] || missing_build+=("noir-protocol-circuits-types/vk_tree.js") +if [ ${#missing_build[@]} -gt 0 ]; then + die "yarn-project is not fully built (missing: ${missing_build[*]}). + Run the full bootstrap first: + cd ${REPO_ROOT} && ./bootstrap.sh build yarn-project" +fi + +log "All prerequisites OK" + +# --------------------------------------------------------------------------- # +# 2. Kill existing processes on our ports +# --------------------------------------------------------------------------- # + +log "Checking for port conflicts..." +kill_on_port 8545 +kill_on_port 8080 +kill_on_port 8089 + +# --------------------------------------------------------------------------- # +# 3. Start anvil +# --------------------------------------------------------------------------- # + +log "Starting anvil on port 8545..." +"$ANVIL" --host 0.0.0.0 --port 8545 > /tmp/anvil-local.log 2>&1 & +ANVIL_PID=$! +BG_PIDS+=("$ANVIL_PID") +sleep 1 +if ! kill -0 "$ANVIL_PID" 2>/dev/null; then + die "Anvil failed to start. Check /tmp/anvil-local.log" +fi +log "Anvil running (PID ${ANVIL_PID})" + +# --------------------------------------------------------------------------- # +# 4. Start Aztec node and wait for PXE +# --------------------------------------------------------------------------- # + +log "Starting Aztec node on port 8080..." +( + cd "${REPO_ROOT}/yarn-project" + ETHEREUM_SLOT_DURATION=5 \ + AZTEC_SLOT_DURATION=5 \ + AZTEC_EPOCH_DURATION=4 \ + SEQ_ENFORCE_TIME_TABLE=false \ + LOG_LEVEL=info \ + node --no-warnings ./aztec/dest/bin/index.js start \ + --local-network \ + --l1-rpc-urls http://127.0.0.1:8545 \ + > /tmp/aztec-node-local.log 2>&1 +) & +NODE_PID=$! +BG_PIDS+=("$NODE_PID") + +log "Waiting for PXE on port 8080 (up to 300s)..." +wait_for_url "$NODE_PID" http://localhost:8080 300 /tmp/aztec-node-local.log +log "PXE is ready" + +# --------------------------------------------------------------------------- # +# 5. Compile contracts +# --------------------------------------------------------------------------- # + +# Map package names to artifact base names (nargo uses the contract name, not the package name) +declare -A ARTIFACT_NAMES=( + [side_effect_contract]="side_effect_contract-SideEffect" + [parent_contract]="parent_contract-Parent" +) + +if [ "$SKIP_COMPILE" = true ]; then + log "Skipping contract compilation (--skip-compile)" + for contract_pkg in side_effect_contract parent_contract; do + artifact="${ARTIFACT_NAMES[$contract_pkg]}" + json_path="${CONTRACTS_DIR}/target/${artifact}.json" + [ -f "$json_path" ] || die "Artifact not found: ${json_path} (cannot --skip-compile without prior build)" + done +else + mkdir -p "${CONTRACTS_DIR}/target" + + for contract_pkg in side_effect_contract parent_contract; do + artifact="${ARTIFACT_NAMES[$contract_pkg]}" + json_path="${CONTRACTS_DIR}/target/${artifact}.json" + + log "Compiling ${contract_pkg}..." + (cd "$CONTRACTS_DIR" && "$NARGO" compile --silence-warnings --inliner-aggressiveness 0 --package "$contract_pkg") + + if [ "$USE_BB_AZTEC_PROCESS" = true ]; then + log "Processing ${artifact} with bb aztec_process..." + "$BB" aztec_process -i "$json_path" + else + log "Transpiling ${artifact}..." + "$TRANSPILER" "$json_path" "$json_path" + + log "Stripping __aztec_nr_internals__ prefix..." + jq '.functions |= map(.name |= sub("^__aztec_nr_internals__"; ""))' "$json_path" > "${json_path}.tmp" + mv "${json_path}.tmp" "$json_path" + + # Generate verification keys for private functions. + log "Generating VKs for private functions..." + vk_tmp_dir=$(mktemp -d) + func_count=$(jq '.functions | length' "$json_path") + for (( i=0; i/dev/null || true) + if [ "$make_vk" = "true" ]; then + fname=$(jq -r ".functions[$i].name" "$json_path") + log " VK: ${fname}" + jq -r ".functions[$i].bytecode" "$json_path" \ + | base64 -d | gunzip \ + | "$BB" write_vk --scheme chonk -b - -o "$vk_tmp_dir" -v 2>/dev/null + vk_b64=$(base64 -w 0 < "$vk_tmp_dir/vk") + jq --arg vk "$vk_b64" --argjson idx "$i" \ + '.functions[$idx].verification_key = $vk' "$json_path" > "${json_path}.tmp" + mv "${json_path}.tmp" "$json_path" + fi + done + rm -rf "$vk_tmp_dir" + fi + + log "Built ${artifact}" + done +fi + +# --------------------------------------------------------------------------- # +# 6. Start bridge +# --------------------------------------------------------------------------- # + +# Verify node is still alive after compilation +if ! kill -0 "$NODE_PID" 2>/dev/null; then + die "Aztec node died during contract compilation. Check /tmp/aztec-node-local.log" +fi + +# Clear stale wallet state from previous runs (avoids "block hash not found" errors). +rm -rf "${HOME}/.aztec/wallet" + +# Symlink yarn-project/node_modules next to wallet-bridge.mjs so its @aztec/* imports +# resolve. Keeping the symlink under noir-projects/ avoids polluting yarn-project/. +ln -sfn "${REPO_ROOT}/yarn-project/node_modules" "${SCRIPT_DIR}/node_modules" + +log "Starting bridge server on port 8089..." +(cd "${SCRIPT_DIR}" && exec node --no-warnings wallet-bridge.mjs > /tmp/bridge-local.log 2>&1) & +BRIDGE_PID=$! +BG_PIDS+=("$BRIDGE_PID") + +wait_for_url "$BRIDGE_PID" http://localhost:8089/health 60 /tmp/bridge-local.log +log "Bridge is ready (PID ${BRIDGE_PID})" + +# --------------------------------------------------------------------------- # +# Done +# --------------------------------------------------------------------------- # + +echo "" +log "Local sandbox is ready!" +echo "" +echo " Anvil: http://localhost:8545 (PID ${ANVIL_PID}, log: /tmp/anvil-local.log)" +echo " Aztec node: http://localhost:8080 (PID ${NODE_PID}, log: /tmp/aztec-node-local.log)" +echo " Bridge: http://localhost:8089 (PID ${BRIDGE_PID}, log: /tmp/bridge-local.log)" +echo " Slot time: 5s" +echo "" +echo "Run the fuzzer:" +echo "" +echo " cd ${SCRIPT_DIR}" +echo " RUST_LOG=debug cargo run -- side-effect --artifacts-dir ${CONTRACTS_DIR}/target --max-steps 5" +echo "" +echo "To stop all services:" +echo " kill ${ANVIL_PID} ${NODE_PID} ${BRIDGE_PID}" +echo "" + +SETUP_COMPLETE=true diff --git a/noir-projects/protocol-fuzzer/setup-nightly-sandbox.sh b/noir-projects/protocol-fuzzer/setup-nightly-sandbox.sh index 207566171bcf..cda546d8951c 100755 --- a/noir-projects/protocol-fuzzer/setup-nightly-sandbox.sh +++ b/noir-projects/protocol-fuzzer/setup-nightly-sandbox.sh @@ -16,7 +16,7 @@ set -euo pipefail CONTAINER_NAME="aztec-sandbox-nightly" # Last nightly tag verified to work with the current contract source code. -KNOWN_GOOD_TAG="5.0.0-nightly.20260224" +KNOWN_GOOD_TAG="5.0.0-nightly.20260402" WRAPPER_DIR="${HOME}/.local/bin" WRAPPER_PATH="${WRAPPER_DIR}/aztec-wallet" REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" @@ -223,15 +223,15 @@ done log "Waiting for Aztec Server HTTP endpoint to be ready..." wait_for_http http://localhost:8080 120 || die "PXE HTTP endpoint did not recover" -BRIDGE_SRC="${REPO_ROOT}/noir-projects/protocol-fuzzer/bridge.mjs" +BRIDGE_SRC="${REPO_ROOT}/noir-projects/protocol-fuzzer/wallet-bridge.mjs" if [ ! -f "$BRIDGE_SRC" ]; then die "Bridge source not found: ${BRIDGE_SRC}" fi log "Starting bridge server..." -docker cp "$BRIDGE_SRC" "${CONTAINER_NAME}:/usr/src/yarn-project/bridge.mjs" +docker cp "$BRIDGE_SRC" "${CONTAINER_NAME}:/usr/src/yarn-project/wallet-bridge.mjs" docker exec -d "$CONTAINER_NAME" \ - bash -c 'cd /usr/src/yarn-project && exec node --no-warnings bridge.mjs > /tmp/bridge.log 2>&1' + bash -c 'cd /usr/src/yarn-project && exec node --no-warnings wallet-bridge.mjs > /tmp/bridge.log 2>&1' if wait_for_http http://localhost:8089/health 60 2; then log "Bridge is ready on port 8089" diff --git a/noir-projects/protocol-fuzzer/src/main.rs b/noir-projects/protocol-fuzzer/src/main.rs index 2daa74e96c57..c9427275899c 100644 --- a/noir-projects/protocol-fuzzer/src/main.rs +++ b/noir-projects/protocol-fuzzer/src/main.rs @@ -60,6 +60,9 @@ struct SideEffectArgs { common: CommonArgs, #[arg(long, default_value_t = 5)] storage_slots: usize, + /// Directory containing compiled contract artifacts. + #[arg(long, default_value = "/tmp")] + artifacts_dir: String, } fn parse_hex_u64(s: &str) -> Result { @@ -123,6 +126,7 @@ fn main() { let mut machine = side_effect::SideEffectMachine { storage_slots: se_args.storage_slots, bridge: Some(&bridge), + artifacts_dir: se_args.artifacts_dir.clone(), }; log::debug!( "Starting side-effect machine with parameters: {:?}", @@ -164,6 +168,10 @@ mod integration_tests { &BRIDGE } + fn artifacts_dir() -> String { + std::env::var("ARTIFACTS_DIR").unwrap_or_else(|_| "/tmp".to_string()) + } + /// Verifies the sandbox is reachable and test accounts can be imported. /// Run this first to diagnose setup issues before running heavier tests. /// Prefixed with `_0` so it sorts first alphabetically (`_` < `a` in @@ -205,6 +213,7 @@ mod integration_tests { let mut machine = side_effect::SideEffectMachine { storage_slots: 2, bridge: Some(bridge), + artifacts_dir: artifacts_dir(), }; smt::fixed_size_builder(1024).run(|u| smt::run(u, &mut machine, 5)) } @@ -271,6 +280,7 @@ mod integration_tests { let mut machine = side_effect::SideEffectMachine { storage_slots: 1, bridge: Some(bridge), + artifacts_dir: artifacts_dir(), }; let state = side_effect::machine::SideEffectState { accounts: vec![0, 1, 2], @@ -327,6 +337,7 @@ mod integration_tests { let mut machine = side_effect::SideEffectMachine { storage_slots: 3, bridge: None, + artifacts_dir: artifacts_dir(), }; let mut state = machine.gen_state(&mut u).unwrap(); let mut commands = Vec::new(); diff --git a/noir-projects/protocol-fuzzer/src/side_effect/machine.rs b/noir-projects/protocol-fuzzer/src/side_effect/machine.rs index c1b236825ece..f5b74ec0b78c 100644 --- a/noir-projects/protocol-fuzzer/src/side_effect/machine.rs +++ b/noir-projects/protocol-fuzzer/src/side_effect/machine.rs @@ -21,6 +21,9 @@ pub struct SideEffectMachine<'a> { /// Required for `new_system()` (deploy + import). `None` is fine for /// model-only tests that never call `new_system`. pub bridge: Option<&'a Bridge>, + /// Directory containing compiled contract JSON artifacts (resolved to + /// an absolute path in `SideEffectSystem::new`). + pub artifacts_dir: String, } #[derive(Debug, Clone)] @@ -466,7 +469,7 @@ impl<'a> smt::StateMachine for SideEffectMachine<'a> { bridge .import_test_accounts() .expect("could not import test accounts"); - let system = SideEffectSystem::new(bridge); + let system = SideEffectSystem::new(bridge, &self.artifacts_dir); system .deploy_side_effect_contract(0) .expect("side-effect contract could not be deployed"); @@ -804,6 +807,7 @@ mod tests { SideEffectMachine { storage_slots: 5, bridge: None, + artifacts_dir: "/tmp".into(), } } diff --git a/noir-projects/protocol-fuzzer/src/side_effect/system.rs b/noir-projects/protocol-fuzzer/src/side_effect/system.rs index aa60b2b36268..b53cdad84865 100644 --- a/noir-projects/protocol-fuzzer/src/side_effect/system.rs +++ b/noir-projects/protocol-fuzzer/src/side_effect/system.rs @@ -2,8 +2,8 @@ use super::machine::SideEffectCommand; use crate::wallet::{AccountId, Bridge, WalletCommand}; pub struct SideEffectSystem<'a> { - side_effect_artifact: &'static str, - parent_artifact: &'static str, + side_effect_artifact: String, + parent_artifact: String, bridge: &'a Bridge, } @@ -144,7 +144,7 @@ impl<'a> SideEffectSystem<'a> { pub(crate) fn deploy_side_effect_contract(&self, account: AccountId) -> anyhow::Result { self.bridge.deploy( - self.side_effect_artifact, + &self.side_effect_artifact, &format!("accounts:test{account}"), "test0", Some("initialize"), @@ -154,7 +154,7 @@ impl<'a> SideEffectSystem<'a> { pub(crate) fn deploy_parent_contract(&self, account: AccountId) -> anyhow::Result { self.bridge.deploy( - self.parent_artifact, + &self.parent_artifact, &format!("accounts:test{account}"), "parent0", Some("initialize"), @@ -162,10 +162,14 @@ impl<'a> SideEffectSystem<'a> { ) } - pub(crate) fn new(bridge: &'a Bridge) -> Self { + pub(crate) fn new(bridge: &'a Bridge, artifacts_dir: &str) -> Self { + let dir = std::path::Path::new(artifacts_dir) + .canonicalize() + .unwrap_or_else(|e| panic!("cannot resolve artifacts dir {artifacts_dir:?}: {e}")); + let dir = dir.display(); Self { - side_effect_artifact: "/tmp/side_effect_contract-SideEffect.json", - parent_artifact: "/tmp/parent_contract-Parent.json", + side_effect_artifact: format!("{dir}/side_effect_contract-SideEffect.json"), + parent_artifact: format!("{dir}/parent_contract-Parent.json"), bridge, } } diff --git a/noir-projects/protocol-fuzzer/bridge.mjs b/noir-projects/protocol-fuzzer/wallet-bridge.mjs similarity index 76% rename from noir-projects/protocol-fuzzer/bridge.mjs rename to noir-projects/protocol-fuzzer/wallet-bridge.mjs index 112d4239466a..b37cc1cb6ea4 100644 --- a/noir-projects/protocol-fuzzer/bridge.mjs +++ b/noir-projects/protocol-fuzzer/wallet-bridge.mjs @@ -1,14 +1,20 @@ #!/usr/bin/env node -// bridge.mjs -- Persistent HTTP bridge for the Aztec protocol fuzzer. +// wallet-bridge.mjs -- Persistent HTTP bridge for the Aztec protocol fuzzer. // -// Runs inside the nightly sandbox container. Reuses CLIWallet to avoid -// the ~1.5s cold-start of spawning a new Node process per call. +// Works both inside the nightly sandbox container and on the host against a +// locally-built yarn-project. The CLI path is auto-detected: if the container +// path exists we use it, otherwise we resolve relative to the repo root. +// For @aztec/* imports to resolve, either run from yarn-project/ (Docker path) +// or have a node_modules symlink alongside this file pointing to +// yarn-project/node_modules (local setup -- see setup-local.sh). // All addresses arrive as raw 0x hex strings (resolved by the Rust fuzzer). import { createServer } from 'node:http'; -import { join } from 'node:path'; +import { join, resolve, dirname } from 'node:path'; +import { existsSync } from 'node:fs'; import { homedir } from 'node:os'; import { format } from 'node:util'; +import { fileURLToPath } from 'node:url'; const PORT = parseInt(process.env.BRIDGE_PORT || '8089', 10); const NODE_URL = process.env.AZTEC_NODE_URL || 'http://localhost:8080'; @@ -17,7 +23,16 @@ const DATA_DIR = process.env.WALLET_DATA_DIRECTORY || join(homedir(), '.aztec/wa const { createAztecNodeClient } = await import('@aztec/aztec.js/node'); const { AztecAddress } = await import('@aztec/aztec.js/addresses'); const { openStoreAt } = await import('@aztec/kv-store/lmdb-v2'); -const CLI = '/usr/src/yarn-project/cli-wallet/dest'; + +// Auto-detect CLI path: try container path, then common local locations. +const __dirname = dirname(fileURLToPath(import.meta.url)); +const candidates = [ + '/usr/src/yarn-project/cli-wallet/dest', // nightly container + resolve(__dirname, 'cli-wallet/dest'), // symlinked into yarn-project/ + resolve(__dirname, '../../yarn-project/cli-wallet/dest'), // original location in protocol-fuzzer/ +]; +const CLI = candidates.find(p => existsSync(p)); +if (!CLI) throw new Error('Cannot find cli-wallet/dest in any known location'); const { CLIWallet } = await import(`${CLI}/utils/wallet.js`); const { WalletDB } = await import(`${CLI}/storage/wallet_db.js`); const { importTestAccounts } = await import(`${CLI}/cmds/import_test_accounts.js`); @@ -80,7 +95,9 @@ const handlers = { false, /* skipClassPublication */ false, /* skipInitialization */ true, /* wait */ - feeOpts, false, 120, /* fee, verbose, timeout */ + feeOpts, /* fee */ + 'mined', /* waitForStatus */ + false, 120, /* verbose, timeout */ { debug: noop, error: noop }, log, /* debugLogger, log */ ), ); @@ -94,7 +111,7 @@ const handlers = { const callArgs = args || []; const { stdout } = await capturing(log => verb === 'send' - ? send(w, node, sender, method, callArgs, artifact, target, true, false, feeOpts, [], false, log) + ? send(w, node, sender, method, callArgs, artifact, target, true, false, feeOpts, [], 'mined', false, log) : simulate(w, node, sender, method, callArgs, artifact, target, feeOpts, [], false, log), ); return { ok: true, stdout }; From 36d6ec82ca9681c2d5cddc5b3be60a98f5663f8d Mon Sep 17 00:00:00 2001 From: federicobarbacovi <171914500+federicobarbacovi@users.noreply.github.com> Date: Wed, 6 May 2026 13:02:29 +0100 Subject: [PATCH 09/15] feat: Multi app per kernel (#22640) Implementation of the required infrastructure to support multiple apps per kernel. This PR generalises databus-related infrastructure to support multiple apps per kernel. It also generalised mocking IVC state, mock circuits producers, and public inputs structure so that we can easily transition to kernels processing multiple apps. --------- Co-authored-by: ledwards2225 --- .../batched_honk_translator.test.cpp | 18 +- .../cpp/src/barretenberg/chonk/chonk.cpp | 34 ++- .../cpp/src/barretenberg/chonk/chonk.test.cpp | 229 +++++++++--------- .../chonk/mock_circuit_producer.hpp | 48 ++-- .../barretenberg/chonk/test_bench_shared.hpp | 49 +++- .../mega_circuit_builder.test.cpp | 8 +- .../cpp/src/barretenberg/constants.hpp | 3 + .../acir_format/acir_to_constraint_buf.cpp | 4 +- .../dsl/acir_format/block_constraint.cpp | 20 +- .../dsl/acir_format/block_constraint.hpp | 13 +- .../dsl/acir_format/block_constraint.test.cpp | 12 +- .../hypernova_recursion_constraint.cpp | 45 ++-- .../hypernova_recursion_constraint.test.cpp | 7 +- .../acir_format/opcode_gate_count.test.cpp | 8 +- .../dsl/acir_format/test_class.hpp | 2 +- .../src/barretenberg/flavor/mega_flavor.hpp | 18 +- .../honk/types/public_inputs_type.hpp | 16 +- .../hypernova/hypernova_verifier.test.cpp | 8 +- ...tabus_lookup_relation_consistency.test.cpp | 6 +- .../stdlib/primitives/databus/databus.hpp | 77 ++++-- .../primitives/databus/databus.test.cpp | 38 +-- .../special_public_inputs.hpp | 36 ++- .../special_public_inputs.test.cpp | 13 +- .../special_public_inputs_test_serde.hpp | 23 +- .../stdlib_circuit_builders/databus.hpp | 10 +- .../mega_circuit_builder.hpp | 36 +-- .../barretenberg/ultra_honk/databus.test.cpp | 69 ++---- .../ultra_honk/honk_transcript.test.cpp | 18 +- .../ultra_honk/relation_correctness.test.cpp | 8 +- 29 files changed, 477 insertions(+), 399 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator.test.cpp b/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator.test.cpp index ee1459f8514e..3aa4e7082961 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator.test.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator.test.cpp @@ -140,12 +140,12 @@ class BatchedHonkTranslatorTests : public ::testing::Test { m.add_entry(round, "ECC_OP_WIRE_" + std::to_string(i), G); } // DataBus entities: - for (const auto& label : { "CALLDATA", - "CALLDATA_READ_COUNTS", - "SECONDARY_CALLDATA", - "SECONDARY_CALLDATA_READ_COUNTS", - "RETURN_DATA", - "RETURN_DATA_READ_COUNTS" }) { + for (const auto& label : { "KERNEL_CALLDATA", + "KERNEL_CALLDATA_READ_COUNTS", + "APP_CALLDATA", + "APP_CALLDATA_READ_COUNTS", + "RETURNDATA", + "RETURNDATA_READ_COUNTS" }) { m.add_entry(round, label, G); } m.add_challenge(round, "eta"); @@ -160,9 +160,9 @@ class BatchedHonkTranslatorTests : public ::testing::Test { // ── Round 2: MegaZK logderiv inverses + Z_PERM + translator Oink ───────── m.add_entry(round, "LOOKUP_INVERSES", G); - m.add_entry(round, "CALLDATA_INVERSES", G); - m.add_entry(round, "SECONDARY_CALLDATA_INVERSES", G); - m.add_entry(round, "RETURN_DATA_INVERSES", G); + m.add_entry(round, "KERNEL_CALLDATA_INVERSES", G); + m.add_entry(round, "APP_CALLDATA_INVERSES", G); + m.add_entry(round, "RETURNDATA_INVERSES", G); m.add_entry(round, "Z_PERM", G); // Translator Oink: vk_hash, masking commitment, 10 wire commitments m.add_entry(round, "vk_hash", Fr); diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp b/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp index 4ad80fd17211..7b987c637108 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp @@ -171,14 +171,19 @@ Chonk::PublicInputsResult Chonk::process_public_inputs_and_consistency_checks( << witness_commitments.calldata.get_value()); kernel_input.kernel_return_data.incomplete_assert_equal(witness_commitments.calldata); - // App return data - bool app_return_data_match = - kernel_input.app_return_data.get_value() == witness_commitments.secondary_calldata.get_value(); - BB_ASSERT_DEBUG(app_return_data_match, - "app_return_data mismatch: proof contains " - << kernel_input.app_return_data.get_value() << " but secondary_calldata commitment is " - << witness_commitments.secondary_calldata.get_value()); - kernel_input.app_return_data.incomplete_assert_equal(witness_commitments.secondary_calldata); + // App return data. Mega currently exposes a single app calldata witness commitment + // (`secondary_calldata`), so MAX_APPS_PER_KERNEL remains 1. + static_assert(MAX_APPS_PER_KERNEL == 1, "Multiple app calldata witness columns are not wired yet"); + for (size_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) { + bool app_return_data_match = + kernel_input.app_return_data[idx].get_value() == witness_commitments.secondary_calldata.get_value(); + BB_ASSERT_DEBUG(app_return_data_match, + "app_return_data mismatch: proof contains " + << kernel_input.app_return_data[idx].get_value() + << " but secondary_calldata commitment is " + << witness_commitments.secondary_calldata.get_value()); + kernel_input.app_return_data[idx].incomplete_assert_equal(witness_commitments.secondary_calldata); + } // ============= Perform accumulator hash consistency check ========================= @@ -202,7 +207,7 @@ Chonk::PublicInputsResult Chonk::process_public_inputs_and_consistency_checks( AppIO app_input; // pairing points app_input.reconstruct_from_public(public_inputs); - // Set the app return data commitment to be propagated via the public inputs + // Set the app return data commitment to be propagated via the public inputs. The depot owns slot allocation. bus_depot.set_app_return_data_commitment(witness_commitments.return_data); return { std::move(app_input.pairing_inputs), std::nullopt }; @@ -318,6 +323,9 @@ void Chonk::complete_kernel_circuit_logic(ClientCircuit& circuit) // Step 2: VERIFICATION LOOP - Recursively verify each proof in the queue + BB_ASSERT(bus_depot.app_return_data_slots_are_empty(), + "DataBusDepot has stale app return-data slots at kernel-completion boundary"); + std::vector points_accumulator; std::optional current_stdlib_verifier_accumulator; if (!is_init_kernel) { @@ -376,9 +384,11 @@ void Chonk::complete_kernel_circuit_logic(ClientCircuit& circuit) // Extract native verifier accumulator from the stdlib accum to use it in the next round recursive_verifier_native_accum = current_stdlib_verifier_accumulator->get_value(); - // Get databus commitments auto kernel_return_data_commitment = bus_depot.get_kernel_return_data_commitment(circuit); - auto app_return_data_commitment = bus_depot.get_app_return_data_commitment(circuit); + KernelIO::AppReturnDataCommitments app_return_data_commitments; + for (size_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) { + app_return_data_commitments[idx] = bus_depot.get_app_return_data_commitment(circuit, idx); + } // Compute hash of output accumulator RecursiveTranscript hash_transcript; @@ -394,7 +404,7 @@ void Chonk::complete_kernel_circuit_logic(ClientCircuit& circuit) // Propagate public inputs KernelIO kernel_output{ pairing_points_aggregator, kernel_return_data_commitment, - app_return_data_commitment, + app_return_data_commitments, running_hash.value(), current_verifier_accum_hash }; kernel_output.set_public(); diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk.test.cpp b/barretenberg/cpp/src/barretenberg/chonk/chonk.test.cpp index 42cc039ad9ea..73d140e66109 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/chonk.test.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk.test.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -22,7 +23,22 @@ using namespace bb; -static constexpr size_t SMALL_LOG_2_NUM_GATES = 5; +namespace { + +constexpr size_t SMALL_LOG_2_NUM_GATES = 5; + +/** + * @brief Enum for specifying which KernelIO field to tamper with in tests. + */ +enum class KernelIOField : uint8_t { + PAIRING_INPUTS, + ACCUMULATOR_HASH, + KERNEL_RETURN_DATA, + APP_RETURN_DATA, + ECC_OP_HASH +}; + +} // namespace class ChonkTests : public ::testing::Test { protected: @@ -40,6 +56,11 @@ class ChonkTests : public ::testing::Test { using ChonkVerifier = ChonkNativeVerifier; public: + /** + * @brief Hook fired after each accumulate() inside run_ivc. + */ + using AccumulateHook = std::function; + /** * @brief Tamper with a proof * @details The first value in the proof after the public inputs is the commitment to the wire w.l (see @@ -58,17 +79,30 @@ class ChonkTests : public ::testing::Test { } } - static std::pair> accumulate_and_prove_ivc( - size_t num_app_circuits, TestSettings settings = {}, bool check_circuit_sizes = false) + static std::pair> run_ivc( + size_t num_app_circuits, + TestSettings settings = {}, + const AccumulateHook& post_hook = nullptr, + bool check_circuit_sizes = false) { CircuitProducer circuit_producer(num_app_circuits); - const size_t num_circuits = circuit_producer.total_num_circuits; - Chonk ivc{ num_circuits }; + return run_ivc_impl(circuit_producer, settings, post_hook, check_circuit_sizes); + }; - for (size_t j = 0; j < num_circuits; ++j) { - circuit_producer.construct_and_accumulate_next_circuit(ivc, settings, check_circuit_sizes); - } - return { ivc.prove(), ivc.get_hiding_kernel_vk_and_hash() }; + static std::pair> run_ivc( + std::vector leading_is_kernel_flags, + TestSettings settings = {}, + const AccumulateHook& post_hook = nullptr, + bool check_circuit_sizes = false) + { + CircuitProducer circuit_producer(std::move(leading_is_kernel_flags), /*large_first_app=*/false); + return run_ivc_impl(circuit_producer, settings, post_hook, check_circuit_sizes); + }; + + static std::pair> accumulate_and_prove_ivc( + size_t num_app_circuits, TestSettings settings = {}, bool check_circuit_sizes = false) + { + return run_ivc(num_app_circuits, settings, /*post_hook=*/nullptr, check_circuit_sizes); }; static bool verify_chonk(const ChonkProof& proof, const std::shared_ptr& vk_and_hash) @@ -77,11 +111,6 @@ class ChonkTests : public ::testing::Test { return verifier.verify(proof); } - /** - * @brief Enum for specifying which KernelIO field to tamper with in tests - */ - enum class KernelIOField { PAIRING_INPUTS, ACCUMULATOR_HASH, KERNEL_RETURN_DATA, APP_RETURN_DATA, ECC_OP_HASH }; - /** * @brief Helper function to test tampering with AppIO pairing inputs * @details Accumulates circuits, doubles the app pairing points (creating valid but different points), @@ -91,17 +120,8 @@ class ChonkTests : public ::testing::Test { { BB_DISABLE_ASSERTS(); - const size_t NUM_APP_CIRCUITS = 2; - CircuitProducer circuit_producer(NUM_APP_CIRCUITS); - const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits; - Chonk ivc{ NUM_CIRCUITS }; TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES }; - - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc, settings); - ivc.accumulate(circuit, vk); - - // After accumulating 3 circuits (app, kernel, app), we have 2 proofs in the queue + auto [proof, vk] = run_ivc(/*num_app_circuits=*/2, settings, [](Chonk& ivc, size_t idx) { if (idx == 2) { EXPECT_EQ(ivc.verification_queue.size(), 2); @@ -120,10 +140,8 @@ class ChonkTests : public ::testing::Test { app_io.to_proof(app_entry.proof, num_public_inputs); } - } - - auto proof = ivc.prove(); - EXPECT_FALSE(verify_chonk(proof, ivc.get_hiding_kernel_vk_and_hash())); + }); + EXPECT_FALSE(verify_chonk(proof, vk)); } /** @@ -135,17 +153,8 @@ class ChonkTests : public ::testing::Test { { BB_DISABLE_ASSERTS(); - const size_t NUM_APP_CIRCUITS = 2; - CircuitProducer circuit_producer(NUM_APP_CIRCUITS); - const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits; - Chonk ivc{ NUM_CIRCUITS }; TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES }; - - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc, settings); - ivc.accumulate(circuit, vk); - - // After accumulating 3 circuits (app, kernel, app), we have 2 proofs in the queue + auto [proof, vk] = run_ivc(/*num_app_circuits=*/2, settings, [field_to_tamper](Chonk& ivc, size_t idx) { if (idx == 2) { EXPECT_EQ(ivc.verification_queue.size(), 2); @@ -159,9 +168,12 @@ class ChonkTests : public ::testing::Test { // Tamper with the specified field switch (field_to_tamper) { case KernelIOField::PAIRING_INPUTS: { - // Replace with a different valid pairing: P0 = G1, P1 = -G1 satisfies e(G1,[1])·e(-G1,[x]) != 1 - // so instead use P0 + random offset to break binding without breaking the pairing trivially - kernel_io.pairing_inputs.P0() = kernel_io.pairing_inputs.P0() + Commitment::one(); + // Set P0 to [x]₁ (the first SRS point after [1]) and P1 to [1]₁ + kernel_io.pairing_inputs.P0() = + srs::get_crs_factory()->get_crs(2)->get_monomial_points()[1]; + kernel_io.pairing_inputs.P1() = -Commitment::one(); + + EXPECT_TRUE(kernel_io.pairing_inputs.check()); break; } case KernelIOField::ACCUMULATOR_HASH: @@ -171,7 +183,7 @@ class ChonkTests : public ::testing::Test { kernel_io.kernel_return_data = kernel_io.kernel_return_data + Commitment::one(); break; case KernelIOField::APP_RETURN_DATA: - kernel_io.app_return_data = kernel_io.app_return_data + Commitment::one(); + kernel_io.app_return_data[0] = kernel_io.app_return_data[0] + Commitment::one(); break; case KernelIOField::ECC_OP_HASH: kernel_io.ecc_op_hash += FF(1); @@ -180,10 +192,8 @@ class ChonkTests : public ::testing::Test { kernel_io.to_proof(kernel_entry.proof, num_public_inputs); } - } - - auto proof = ivc.prove(); - EXPECT_FALSE(verify_chonk(proof, ivc.get_hiding_kernel_vk_and_hash())); + }); + EXPECT_FALSE(verify_chonk(proof, vk)); } /** @@ -202,33 +212,27 @@ class ChonkTests : public ::testing::Test { using KernelIOSerde = bb::stdlib::recursion::honk::KernelIOSerde; const size_t NUM_APP_CIRCUITS = 2; - CircuitProducer circuit_producer(NUM_APP_CIRCUITS); - const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits; - Chonk ivc{ NUM_CIRCUITS }; + const size_t NUM_TOTAL_CIRCUITS = NUM_APP_CIRCUITS * 2 + /*num_trailing_kernels*/ 3; TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES }; - // Extract tail kernel IO before the last accumulation consumes the verification queue. - // The tail kernel (HN_FINAL) uses KernelIO format; the hiding kernel uses HidingKernelIO. + // Extract tail kernel IO before the hiding kernel consumes the verification queue. KernelIOSerde tail_io; - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - if (idx == NUM_CIRCUITS - 1) { - for (auto& it : std::ranges::reverse_view(ivc.verification_queue)) { - if (it.is_kernel) { - size_t num_public_inputs = it.honk_vk->num_public_inputs; - ASSERT_EQ(num_public_inputs, KernelIOSerde::PUBLIC_INPUTS_SIZE) - << "Tail kernel should use KernelIO format"; - ASSERT_GT(it.proof.size(), num_public_inputs) << "Tail kernel proof too small"; - tail_io = KernelIOSerde::from_proof(it.proof, num_public_inputs); - break; + auto [proof, vk_and_hash] = + run_ivc(/*num_app_circuits=*/NUM_APP_CIRCUITS, settings, [&tail_io](Chonk& ivc, size_t idx) { + // With 2 apps the layout is [app, kernel, app, kernel, reset, tail, hiding]. + if (idx == NUM_TOTAL_CIRCUITS - 2) { + for (auto& it : std::ranges::reverse_view(ivc.verification_queue)) { + if (it.is_kernel) { + size_t num_public_inputs = it.honk_vk->num_public_inputs; + ASSERT_EQ(num_public_inputs, KernelIOSerde::PUBLIC_INPUTS_SIZE) + << "Tail kernel should use KernelIO format"; + ASSERT_GT(it.proof.size(), num_public_inputs) << "Tail kernel proof too small"; + tail_io = KernelIOSerde::from_proof(it.proof, num_public_inputs); + break; + } } } - } - auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc, settings); - ivc.accumulate(circuit, vk); - } - - auto proof = ivc.prove(); - auto vk_and_hash = ivc.get_hiding_kernel_vk_and_hash(); + }); size_t hiding_kernel_pub_inputs = vk_and_hash->vk->num_public_inputs; ASSERT_EQ(hiding_kernel_pub_inputs, HidingKernelIOSerde::PUBLIC_INPUTS_SIZE) @@ -240,6 +244,25 @@ class ChonkTests : public ::testing::Test { << "kernel_return_data mismatch: Tail has " << tail_io.kernel_return_data << " but HidingKernel has " << hiding_io.kernel_return_data; } + + private: + static std::pair> run_ivc_impl( + CircuitProducer& circuit_producer, + TestSettings settings, + const AccumulateHook& post_hook, + bool check_circuit_sizes) + { + const size_t num_circuits = circuit_producer.total_num_circuits; + Chonk ivc{ num_circuits }; + + for (size_t idx = 0; idx < num_circuits; ++idx) { + circuit_producer.construct_and_accumulate_next_circuit(ivc, settings, check_circuit_sizes); + if (post_hook) { + post_hook(ivc, idx); + } + } + return { ivc.prove(), ivc.get_hiding_kernel_vk_and_hash() }; + } }; /** @@ -455,15 +478,7 @@ TEST_F(ChonkTests, MsgpackProofFromFileOrBuffer) } }; -/** - * @brief Test that tampering with kernel pairing inputs causes verification to fail - * @details Pairing points (P0, P1) accumulate across the IVC chain through aggregation. - * Even if we replace them with pairing points satisfying pairing check, the public input binding should must catch it. - */ -TEST_F(ChonkTests, KernelPairingInputsTamperingFailure) -{ - ChonkTests::test_kernel_io_tampering(KernelIOField::PAIRING_INPUTS); -} +class KernelIOTamperingTests : public ChonkTests, public testing::WithParamInterface {}; /** * @brief Test that tampering with app pairing inputs causes verification to fail @@ -475,45 +490,33 @@ TEST_F(ChonkTests, AppPairingInputsTamperingFailure) ChonkTests::test_app_io_tampering(); } -/** - * @brief Verify that tampering with the accumulator hash in public inputs causes IVC verification failure - * @details Each kernel outputs `output_hn_accum_hash` as a public input. The next kernel computes the hash of its - * input accumulator and compares it with the hash from the previous kernel's public inputs via assert_equal. - * This test tampers with the hash to verify the binding. - */ -TEST_F(ChonkTests, AccumulatorHashTamperingFailure) -{ - ChonkTests::test_kernel_io_tampering(KernelIOField::ACCUMULATOR_HASH); -} - -/** - * @brief Test that tampering with kernel_return_data causes verification to fail - * @details kernel_return_data is the commitment to the kernel's return data which must match - * the calldata commitment of the next circuit. Tampering should cause databus consistency check to fail. - */ -TEST_F(ChonkTests, KernelReturnDataTamperingFailure) +TEST_P(KernelIOTamperingTests, CausesVerificationFailure) { - ChonkTests::test_kernel_io_tampering(KernelIOField::KERNEL_RETURN_DATA); + test_kernel_io_tampering(GetParam()); } -/** - * @brief Test that tampering with app_return_data causes verification to fail - * @details app_return_data is the commitment to the app's return data which must match - * the secondary_calldata commitment of the next circuit. - */ -TEST_F(ChonkTests, AppReturnDataTamperingFailure) -{ - ChonkTests::test_kernel_io_tampering(KernelIOField::APP_RETURN_DATA); -} - -/** - * @brief Test that tampering with ecc_op_hash causes verification to fail - * @details ecc_op_hash commits to the folded ECC operation subtable commitments for batch merge verification. - */ -TEST_F(ChonkTests, EccOpHashTamperingFailure) -{ - ChonkTests::test_kernel_io_tampering(KernelIOField::ECC_OP_HASH); -} +INSTANTIATE_TEST_SUITE_P(All, + KernelIOTamperingTests, + testing::Values(KernelIOField::PAIRING_INPUTS, + KernelIOField::ACCUMULATOR_HASH, + KernelIOField::KERNEL_RETURN_DATA, + KernelIOField::APP_RETURN_DATA, + KernelIOField::ECC_OP_HASH), + [](const testing::TestParamInfo& info) { + switch (info.param) { + case KernelIOField::PAIRING_INPUTS: + return "PairingInputs"; + case KernelIOField::ACCUMULATOR_HASH: + return "AccumulatorHash"; + case KernelIOField::KERNEL_RETURN_DATA: + return "KernelReturnData"; + case KernelIOField::APP_RETURN_DATA: + return "AppReturnData"; + case KernelIOField::ECC_OP_HASH: + return "EccOpHash"; + } + return "Unknown"; + }); /** * @brief Test that kernel_return_data is consistently propagated from Tail kernel to HidingKernel proof diff --git a/barretenberg/cpp/src/barretenberg/chonk/mock_circuit_producer.hpp b/barretenberg/cpp/src/barretenberg/chonk/mock_circuit_producer.hpp index 94904c109194..cfae070e91f9 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/mock_circuit_producer.hpp +++ b/barretenberg/cpp/src/barretenberg/chonk/mock_circuit_producer.hpp @@ -24,7 +24,7 @@ class MockDatabusProducer { using BusDataArray = std::vector; static constexpr size_t BUS_ARRAY_SIZE = 3; // arbitrary length of mock bus inputs - BusDataArray app_return_data; + std::array app_return_data; BusDataArray kernel_return_data; FF dummy_return_val = 1; // use simple return val for easier test debugging @@ -41,13 +41,18 @@ class MockDatabusProducer { public: /** - * @brief Update the app return data and populate it in the app circuit + * @brief Update the next app return data and populate it in the app circuit. App slots are processed in order. */ void populate_app_databus(ClientCircuit& circuit) { - app_return_data = generate_random_bus_array(); - for (auto& val : app_return_data) { - circuit.add_public_return_data(circuit.add_variable(val)); + for (auto& app_data : app_return_data) { + if (app_data.empty()) { + app_data = generate_random_bus_array(); + for (auto& val : app_data) { + circuit.add_public_return_data(circuit.add_variable(val)); + } + return; + } } }; @@ -59,13 +64,15 @@ class MockDatabusProducer { { // Populate calldata from previous kernel return data (if it exists) for (auto& val : kernel_return_data) { - circuit.add_public_calldata(circuit.add_variable(val)); + circuit.add_public_calldata(BusId::KERNEL_CALLDATA, circuit.add_variable(val)); } // Populate secondary_calldata from app return data (if it exists), then clear the app return data - for (auto& val : app_return_data) { - circuit.add_public_secondary_calldata(circuit.add_variable(val)); + for (size_t idx = 0; idx < app_return_data.size(); ++idx) { + for (auto& val : app_return_data[idx]) { + circuit.add_public_calldata(static_cast(idx + 1), circuit.add_variable(val)); + } + app_return_data[idx].clear(); } - app_return_data.clear(); // Mock the return data for the present kernel circuit kernel_return_data = generate_random_bus_array(); @@ -73,12 +80,6 @@ class MockDatabusProducer { circuit.add_public_return_data(circuit.add_variable(val)); } }; - - /** - * @brief Add an arbitrary value to the app return data. This leads to a descrepency between the values used by the - * app itself and the secondary_calldata values in the kernel that will be set based on these tampered values. - */ - void tamper_with_app_return_data() { app_return_data.emplace_back(17); } }; /** @@ -133,6 +134,18 @@ class PrivateFunctionExecutionMockCircuitProducer { } } + PrivateFunctionExecutionMockCircuitProducer(std::vector leading_is_kernel_flags, bool large_first_app = false) + : is_kernel_flags(std::move(leading_is_kernel_flags)) + , large_first_app(large_first_app) + { + BB_ASSERT(!is_kernel_flags.empty(), "Mock circuit layout must contain at least one leading circuit"); + BB_ASSERT_EQ(is_kernel_flags[0], false, "Mock circuit layout must start with an app circuit"); + for (size_t i = 0; i < NUM_TRAILING_KERNELS; ++i) { + is_kernel_flags.emplace_back(true); + } + total_num_circuits = is_kernel_flags.size(); + } + /** * @brief Precompute the verification key for the given circuit. */ @@ -264,11 +277,6 @@ class PrivateFunctionExecutionMockCircuitProducer { auto [circuit, vk] = create_next_circuit_and_vk(ivc, settings, check_circuit_sizes); ivc.accumulate(circuit, vk); } - - /** - * @brief Tamper with databus data to facilitate failure testing - */ - void tamper_with_databus() { mock_databus.tamper_with_app_return_data(); } }; } // namespace diff --git a/barretenberg/cpp/src/barretenberg/chonk/test_bench_shared.hpp b/barretenberg/cpp/src/barretenberg/chonk/test_bench_shared.hpp index 233ac460ac71..9d79e8e3dffc 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/test_bench_shared.hpp +++ b/barretenberg/cpp/src/barretenberg/chonk/test_bench_shared.hpp @@ -7,15 +7,9 @@ namespace bb { -/** - * @brief Perform a specified number of circuit accumulation rounds - * - * @param NUM_CIRCUITS Number of circuits to accumulate (apps + kernels) - */ std::pair> accumulate_and_prove_with_precomputed_vks( - size_t num_app_circuits, auto& precomputed_vks, const bool large_first_app = true) + PrivateFunctionExecutionMockCircuitProducer& circuit_producer, auto& precomputed_vks) { - PrivateFunctionExecutionMockCircuitProducer circuit_producer(num_app_circuits, large_first_app); const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits; Chonk ivc{ NUM_CIRCUITS }; @@ -33,11 +27,28 @@ std::pair> accumulate_and_p return { ivc.prove(), ivc.get_hiding_kernel_vk_and_hash() }; } -std::vector> precompute_vks(const size_t num_app_circuits, - const bool large_first_app = true) +/** + * @brief Perform a specified number of circuit accumulation rounds + * + * @param num_app_circuits Number of app circuits to accumulate + */ +std::pair> accumulate_and_prove_with_precomputed_vks( + size_t num_app_circuits, auto& precomputed_vks, const bool large_first_app = true) +{ + PrivateFunctionExecutionMockCircuitProducer circuit_producer(num_app_circuits, large_first_app); + return accumulate_and_prove_with_precomputed_vks(circuit_producer, precomputed_vks); +} + +std::pair> accumulate_and_prove_with_precomputed_vks( + std::vector leading_is_kernel_flags, auto& precomputed_vks, const bool large_first_app = false) +{ + PrivateFunctionExecutionMockCircuitProducer circuit_producer(std::move(leading_is_kernel_flags), large_first_app); + return accumulate_and_prove_with_precomputed_vks(circuit_producer, precomputed_vks); +} + +std::vector> precompute_vks( + PrivateFunctionExecutionMockCircuitProducer& circuit_producer) { - using CircuitProducer = PrivateFunctionExecutionMockCircuitProducer; - CircuitProducer circuit_producer(num_app_circuits, large_first_app); const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits; Chonk ivc{ NUM_CIRCUITS }; @@ -46,7 +57,7 @@ std::vector> precompute_vk auto circuit = circuit_producer.create_next_circuit(ivc); const bool is_hiding_kernel = (j == NUM_CIRCUITS - 1); - auto vk = CircuitProducer::get_verification_key(circuit, is_hiding_kernel); + auto vk = PrivateFunctionExecutionMockCircuitProducer::get_verification_key(circuit, is_hiding_kernel); vkeys.push_back(vk); ivc.accumulate(circuit, vk); } @@ -54,4 +65,18 @@ std::vector> precompute_vk return vkeys; } +std::vector> precompute_vks(const size_t num_app_circuits, + const bool large_first_app = true) +{ + PrivateFunctionExecutionMockCircuitProducer circuit_producer(num_app_circuits, large_first_app); + return precompute_vks(circuit_producer); +} + +std::vector> precompute_vks( + std::vector leading_is_kernel_flags, const bool large_first_app = false) +{ + PrivateFunctionExecutionMockCircuitProducer circuit_producer(std::move(leading_is_kernel_flags), large_first_app); + return precompute_vks(circuit_producer); +} + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/circuit_checker/mega_circuit_builder.test.cpp b/barretenberg/cpp/src/barretenberg/circuit_checker/mega_circuit_builder.test.cpp index 05dd9f15a08e..697a1cbc60bf 100644 --- a/barretenberg/cpp/src/barretenberg/circuit_checker/mega_circuit_builder.test.cpp +++ b/barretenberg/cpp/src/barretenberg/circuit_checker/mega_circuit_builder.test.cpp @@ -273,8 +273,8 @@ TEST(MegaCircuitBuilder, EmptyCircuitFinalization) EXPECT_EQ(builder.blocks.nnf.size(), 0); EXPECT_EQ(builder.blocks.poseidon2_external.size(), 0); EXPECT_EQ(builder.blocks.poseidon2_internal.size(), 0); - EXPECT_EQ(builder.get_calldata().size(), 0); - EXPECT_EQ(builder.get_secondary_calldata().size(), 0); + EXPECT_EQ(builder.get_calldata(BusId::KERNEL_CALLDATA).size(), 0); + EXPECT_EQ(builder.get_calldata(BusId::APP_CALLDATA).size(), 0); EXPECT_EQ(builder.get_return_data().size(), 0); EXPECT_TRUE(CircuitChecker::check(builder)); @@ -289,13 +289,13 @@ TEST(MegaCircuitBuilder, DatabusOutOfBoundsReadFails) // Add single entry to calldata auto val = builder.add_variable(fr(42)); - builder.add_public_calldata(val); + builder.add_public_calldata(BusId::KERNEL_CALLDATA, val); // Try to read at index 1 (out of bounds - only index 0 exists) auto bad_idx = builder.add_variable(fr(1)); // This should trigger an assertion in read_calldata - EXPECT_THROW(builder.read_calldata(bad_idx), std::runtime_error); + EXPECT_THROW(builder.read_calldata(BusId::KERNEL_CALLDATA, bad_idx), std::runtime_error); } } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/constants.hpp b/barretenberg/cpp/src/barretenberg/constants.hpp index f5894bde5dca..3ea23fce2512 100644 --- a/barretenberg/cpp/src/barretenberg/constants.hpp +++ b/barretenberg/cpp/src/barretenberg/constants.hpp @@ -62,6 +62,9 @@ static constexpr uint32_t NUM_TRANSLATION_EVALUATIONS = 5; // The number of leading zero rows in the execution trace. Used to enable shifted polynomials. static constexpr size_t NUM_ZERO_ROWS = 1; +// The maximum number of app circuits a single kernel can recursively verify +static constexpr uint8_t MAX_APPS_PER_KERNEL = 1; + static constexpr size_t CHONK_MAX_NUM_CIRCUITS = 56 + /*trailing kernels*/ 3; static constexpr size_t BATCH_MERGE_PROOF_SIZE = diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp index c8450c7ff09f..3dabd9dadb8a 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp @@ -802,10 +802,10 @@ BlockConstraint memory_init_to_block_constraint(Acir::Opcode::MemoryInit const& // array. if (std::holds_alternative(mem_init.block_type.value)) { uint32_t calldata_id = std::get(mem_init.block_type.value).value; - BB_ASSERT(calldata_id == 0 || calldata_id == 1, "acir_format::handle_memory_init: Unsupported calldata id"); + BB_ASSERT_LTE(calldata_id, MAX_APPS_PER_KERNEL, "acir_format::handle_memory_init: Unsupported calldata id"); block.type = BlockType::CallData; - block.calldata_id = calldata_id == 0 ? CallDataType::Primary : CallDataType::Secondary; + block.calldata_id = static_cast(calldata_id); } else if (std::holds_alternative(mem_init.block_type.value)) { block.type = BlockType::ReturnData; } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.cpp index b492baa9c1c6..09ea9da9e5ff 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.cpp @@ -165,17 +165,15 @@ void process_call_data_operations(Builder& builder, } }; - // Process primary or secondary calldata based on calldata_id - switch (constraint.calldata_id) { - case CallDataType::Primary: - process_calldata(databus.calldata); - break; - case CallDataType::Secondary: - process_calldata(databus.secondary_calldata); - break; - default: - bb::assert_failure("Databus only supports two calldata arrays."); - break; + // Process kernel or app calldata based on the ACIR calldata id. Id 0 is kernel calldata; app calldata ids start at + // 1 and map directly onto app_calldata[id - 1]. + const auto calldata_id = static_cast(constraint.calldata_id); + if (calldata_id == static_cast(CallDataType::KernelCalldata)) { + process_calldata(databus.kernel_calldata); + } else { + const size_t app_calldata_idx = calldata_id - /*shift by kernel calldata*/ 1; + BB_ASSERT_LT(app_calldata_idx, MAX_APPS_PER_KERNEL, "Databus app calldata index out of bounds"); + process_calldata(databus.app_calldata[app_calldata_idx]); } } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.hpp index 4123c1e485f3..de9170e275fe 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.hpp @@ -5,6 +5,7 @@ // ===================== #pragma once +#include "barretenberg/constants.hpp" #include "barretenberg/dsl/acir_format/witness_constant.hpp" #include "barretenberg/stdlib/primitives/field/field.hpp" #include @@ -17,10 +18,10 @@ enum AccessType : std::uint8_t { Write = 1, }; -enum CallDataType : std::uint8_t { - None = 0, - Primary = 1, - Secondary = 2, +enum CallDataType : std::uint32_t { + KernelCalldata = 0, + AppCalldata = 1, + None = bb::MAX_APPS_PER_KERNEL + 1, // Used for non-calldata blocks }; /** @@ -46,8 +47,8 @@ enum BlockType : std::uint8_t { * @details 1. init holds the initial values of the RAM/ROM/CallData/ReturnData table * 2. trace holds the sequence of memory operations (reads/writes) performed on the table * 3. type indicates the type of memory being constrained (RAM/ROM/CallData/ReturnData) - * 4. calldata_id (used only for CallData) indicates whether we are operating on primary (kernel) or secondary - * (app) calldata + * 4. calldata_id (used only for CallData) indicates whether we are operating on kernel calldata or an app + * calldata slot. The kernel calldata id is 0, app calldata ids are in [1, MAX_APPS_PER_KERNEL]. */ struct BlockConstraint { std::vector init; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp index 0507713f4e77..98c7e7d49a43 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp @@ -509,12 +509,12 @@ class CallDataTestingFunctions { } }; -using CallDataTestConfigs = testing::Types, - CallDataTestParams, - CallDataTestParams, - CallDataTestParams, - CallDataTestParams, - CallDataTestParams>; +using CallDataTestConfigs = testing::Types, + CallDataTestParams, + CallDataTestParams, + CallDataTestParams, + CallDataTestParams, + CallDataTestParams>; template class CallDataTests : public ::testing::Test, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.cpp index 2c07a6a7edbd..a516266e17dc 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.cpp @@ -32,50 +32,59 @@ using namespace bb; std::shared_ptr create_mock_chonk_from_constraints(const std::vector& constraints) { auto ivc = std::make_shared(std::max(constraints.size(), static_cast(4))); - // Check constraint proof type. Throws if proof_type is not a valid HyperNova type auto constraint_has_type = [](const RecursionConstraint& c, Chonk::QUEUE_TYPE expected) { return proof_type_to_chonk_queue_type(c.proof_type) == expected; }; + BB_ASSERT(!constraints.empty(), "At least one recursion constraint is required to determine Chonk state"); + const bool is_init = constraint_has_type(constraints[0], Chonk::QUEUE_TYPE::OINK); + const bool is_reset = (constraints.size() == 1 && constraint_has_type(constraints[0], Chonk::QUEUE_TYPE::HN)); + const bool is_tail = (constraints.size() == 1 && constraint_has_type(constraints[0], Chonk::QUEUE_TYPE::HN_TAIL)); + const bool is_hiding = + (constraints.size() == 1 && constraint_has_type(constraints[0], Chonk::QUEUE_TYPE::HN_FINAL)); + const size_t upper_bound = is_init ? MAX_APPS_PER_KERNEL : MAX_APPS_PER_KERNEL + 1; + BB_ASSERT_LTE(constraints.size(), upper_bound, "Too many recursion constraints encountered when mocking IVC state"); + // Match constraint patterns to kernel types and populate appropriate mock data: // INIT kernel: Verifies first app circuit (no prior accumulator exists) - if (constraints.size() == 1 && constraint_has_type(constraints[0], Chonk::QUEUE_TYPE::OINK)) { + if (is_init) { mock_chonk_accumulation(ivc, Chonk::QUEUE_TYPE::OINK, /*is_kernel=*/false); + for (size_t idx = 1; idx < constraints.size(); idx++) { + BB_ASSERT(constraint_has_type(constraints[idx], Chonk::QUEUE_TYPE::HN), + "Subsequent constraints in init kernel must be HN type"); + mock_chonk_accumulation(ivc, Chonk::QUEUE_TYPE::HN, /*is_kernel=*/false); + } return ivc; } // RESET kernel: Verifies only a previous kernel (resets the IVC accumulation) - if (constraints.size() == 1 && constraint_has_type(constraints[0], Chonk::QUEUE_TYPE::HN)) { + if (is_reset) { mock_chonk_accumulation(ivc, Chonk::QUEUE_TYPE::HN, /*is_kernel=*/true); return ivc; } - // TAIL kernel: Final kernel in the chain before generating tube proof - if (constraints.size() == 1 && constraint_has_type(constraints[0], Chonk::QUEUE_TYPE::HN_TAIL)) { + // TAIL kernel: Final kernel in the chain before hiding kernel + if (is_tail) { mock_chonk_accumulation(ivc, Chonk::QUEUE_TYPE::HN_TAIL, /*is_kernel=*/true); return ivc; } - // INNER kernel: Verifies previous kernel + new app circuit - if (constraints.size() == 2) { - BB_ASSERT(constraint_has_type(constraints[0], Chonk::QUEUE_TYPE::HN), - "Inner kernel first constraint must be HN type"); - BB_ASSERT(constraint_has_type(constraints[1], Chonk::QUEUE_TYPE::HN), - "Inner kernel second constraint must be HN type"); - mock_chonk_accumulation(ivc, Chonk::QUEUE_TYPE::HN, /*is_kernel=*/true); - mock_chonk_accumulation(ivc, Chonk::QUEUE_TYPE::HN, /*is_kernel=*/false); - return ivc; - } - // HIDING kernel: Adds zero-knowledge hiding to the final proof - if (constraints.size() == 1 && constraint_has_type(constraints[0], Chonk::QUEUE_TYPE::HN_FINAL)) { + if (is_hiding) { mock_chonk_accumulation(ivc, Chonk::QUEUE_TYPE::HN_FINAL, /*is_kernel=*/true); return ivc; } - throw_or_abort("Invalid set of IVC recursion constraints!"); + // INNER kernel: Verifies previous kernel + app circuits + bool is_kernel = true; + for (const auto& constraint : constraints) { + BB_ASSERT(constraint_has_type(constraint, Chonk::QUEUE_TYPE::HN), + "All constraints in inner kernel must be HN type"); + mock_chonk_accumulation(ivc, Chonk::QUEUE_TYPE::HN, /*is_kernel=*/is_kernel); + is_kernel = false; // First constraint verifies previous kernel, subsequent constraints verify apps + } return ivc; } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.test.cpp index 7e7dcdc43287..8bfcdee19220 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.test.cpp @@ -212,10 +212,9 @@ class HypernovaRecursionConstraintTest : public ::testing::Test { program.constraints.max_witness_index = static_cast(program.witness.size() - 1); program.constraints.num_acir_opcodes = static_cast(hn_recursion_constraints.size()); program.constraints.hn_recursion_constraints = hn_recursion_constraints; - program.constraints.original_opcode_indices = - hn_recursion_constraints.size() == 1 - ? AcirFormatOriginalOpcodeIndices{ .hn_recursion_constraints = { 0 } } - : AcirFormatOriginalOpcodeIndices{ .hn_recursion_constraints = { 0, 1 } }; + for (size_t idx = 0; idx < hn_recursion_constraints.size(); ++idx) { + program.constraints.original_opcode_indices.hn_recursion_constraints.push_back(static_cast(idx)); + } return program; } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/opcode_gate_count.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/opcode_gate_count.test.cpp index a12e9ffd9edf..36447401525b 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/opcode_gate_count.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/opcode_gate_count.test.cpp @@ -592,13 +592,13 @@ TYPED_TEST(OpcodeGateCountTests, BlockCallData) .value = WitnessOrConstant::from_index(3), // 10 }); - // Primary calldata + // Kernel calldata { BlockConstraint block_constraint{ .init = init, .trace = trace, .type = BlockType::CallData, - .calldata_id = CallDataType::Primary, + .calldata_id = CallDataType::KernelCalldata, }; AcirFormat constraint_system = constraint_to_acir_format(block_constraint); @@ -614,13 +614,13 @@ TYPED_TEST(OpcodeGateCountTests, BlockCallData) EXPECT_EQ(program.constraints.gates_per_opcode, std::vector({ BLOCK_CALLDATA })); } - // Secondary calldata + // App calldata { BlockConstraint block_constraint{ .init = init, .trace = trace, .type = BlockType::CallData, - .calldata_id = CallDataType::Secondary, + .calldata_id = CallDataType::AppCalldata, }; AcirFormat constraint_system = constraint_to_acir_format(block_constraint); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/test_class.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/test_class.hpp index 89a12f07dfa3..749eb5d35f18 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/test_class.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/test_class.hpp @@ -105,7 +105,7 @@ inline Acir::BlockType block_type_to_acir_block_type(BlockType type, CallDataTyp // ROM and RAM both map to Memory in ACIR return Acir::BlockType{ .value = Acir::BlockType::Memory{} }; case BlockType::CallData: { - uint32_t id = (calldata_id == CallDataType::Primary) ? 0 : 1; + uint32_t id = static_cast(calldata_id); return Acir::BlockType{ .value = Acir::BlockType::CallData{ .value = id } }; } case BlockType::ReturnData: diff --git a/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp index 81508cb9266f..15f9fc44a035 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp @@ -389,15 +389,15 @@ class MegaFlavor { ecc_op_wire_2 = "ECC_OP_WIRE_2"; ecc_op_wire_3 = "ECC_OP_WIRE_3"; ecc_op_wire_4 = "ECC_OP_WIRE_4"; - calldata = "CALLDATA"; - calldata_read_counts = "CALLDATA_READ_COUNTS"; - calldata_inverses = "CALLDATA_INVERSES"; - secondary_calldata = "SECONDARY_CALLDATA"; - secondary_calldata_read_counts = "SECONDARY_CALLDATA_READ_COUNTS"; - secondary_calldata_inverses = "SECONDARY_CALLDATA_INVERSES"; - return_data = "RETURN_DATA"; - return_data_read_counts = "RETURN_DATA_READ_COUNTS"; - return_data_inverses = "RETURN_DATA_INVERSES"; + calldata = "KERNEL_CALLDATA"; + calldata_read_counts = "KERNEL_CALLDATA_READ_COUNTS"; + calldata_inverses = "KERNEL_CALLDATA_INVERSES"; + secondary_calldata = "APP_CALLDATA"; + secondary_calldata_read_counts = "APP_CALLDATA_READ_COUNTS"; + secondary_calldata_inverses = "APP_CALLDATA_INVERSES"; + return_data = "RETURNDATA"; + return_data_read_counts = "RETURNDATA_READ_COUNTS"; + return_data_inverses = "RETURNDATA_INVERSES"; q_c = "Q_C"; q_l = "Q_L"; diff --git a/barretenberg/cpp/src/barretenberg/honk/types/public_inputs_type.hpp b/barretenberg/cpp/src/barretenberg/honk/types/public_inputs_type.hpp index 21288b2e9bc9..4aa381d53fd0 100644 --- a/barretenberg/cpp/src/barretenberg/honk/types/public_inputs_type.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/types/public_inputs_type.hpp @@ -6,6 +6,7 @@ #pragma once +#include "barretenberg/constants.hpp" #include namespace bb { @@ -53,12 +54,15 @@ static constexpr std::size_t INVALID_PUBLIC_INPUTS_SIZE = 0; static constexpr std::size_t MEGA_EXECUTION_TRACE_NUM_WIRES = 4; // Number of bb::fr elements used to represent the public inputs of an INIT/INNER/RESET/TAIL kernel -static constexpr std::size_t KERNEL_PUBLIC_INPUTS_SIZE = - /*pairing_inputs*/ PAIRING_POINTS_SIZE + - /*kernel_return_data*/ GOBLIN_GROUP_PUBLIC_INPUTS_SIZE + - /*app_return_data*/ GOBLIN_GROUP_PUBLIC_INPUTS_SIZE + - /*ecc_op_hash*/ FR_PUBLIC_INPUTS_SIZE + - /*output_hn_accum_hash*/ FR_PUBLIC_INPUTS_SIZE; +// verifying num_apps application circuits in its accumulation group. +constexpr std::size_t kernel_public_inputs_size(std::size_t num_apps) +{ + return /*pairing_inputs*/ PAIRING_POINTS_SIZE + + /*kernel_return_data*/ GOBLIN_GROUP_PUBLIC_INPUTS_SIZE + + /*app_return_data[num_apps]*/ (num_apps * GOBLIN_GROUP_PUBLIC_INPUTS_SIZE) + + /*ecc_op_hash*/ FR_PUBLIC_INPUTS_SIZE + + /*output_hn_accum_hash*/ FR_PUBLIC_INPUTS_SIZE; +} // Number of bb::fr elements used to represent the default public inputs, i.e., the pairing points static constexpr std::size_t DEFAULT_PUBLIC_INPUTS_SIZE = PAIRING_POINTS_SIZE; diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp index 65057dff5c11..3cd8e1b6012f 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp @@ -189,7 +189,7 @@ class HypernovaFoldingVerifierTests : public ::testing::Test { for (const auto& wire : { "ECC_OP_WIRE_1", "ECC_OP_WIRE_2", "ECC_OP_WIRE_3", "ECC_OP_WIRE_4" }) { manifest.add_entry(round, wire, frs_per_G); } - for (const auto& bus : { "CALLDATA", "SECONDARY_CALLDATA", "RETURN_DATA" }) { + for (const auto& bus : { "KERNEL_CALLDATA", "APP_CALLDATA", "RETURNDATA" }) { manifest.add_entry(round, bus, frs_per_G); manifest.add_entry(round, std::string(bus) + "_READ_COUNTS", frs_per_G); } @@ -206,9 +206,9 @@ class HypernovaFoldingVerifierTests : public ::testing::Test { manifest.add_challenge(round, "alpha"); manifest.add_challenge(round, "HypernovaFoldingProver:gate_challenge"); manifest.add_entry(round, "LOOKUP_INVERSES", frs_per_G); - manifest.add_entry(round, "CALLDATA_INVERSES", frs_per_G); - manifest.add_entry(round, "SECONDARY_CALLDATA_INVERSES", frs_per_G); - manifest.add_entry(round, "RETURN_DATA_INVERSES", frs_per_G); + manifest.add_entry(round, "KERNEL_CALLDATA_INVERSES", frs_per_G); + manifest.add_entry(round, "APP_CALLDATA_INVERSES", frs_per_G); + manifest.add_entry(round, "RETURNDATA_INVERSES", frs_per_G); manifest.add_entry(round, "Z_PERM", frs_per_G); round++; diff --git a/barretenberg/cpp/src/barretenberg/relations/databus_lookup_relation_consistency.test.cpp b/barretenberg/cpp/src/barretenberg/relations/databus_lookup_relation_consistency.test.cpp index 3e560312c52b..bed743487709 100644 --- a/barretenberg/cpp/src/barretenberg/relations/databus_lookup_relation_consistency.test.cpp +++ b/barretenberg/cpp/src/barretenberg/relations/databus_lookup_relation_consistency.test.cpp @@ -33,15 +33,15 @@ struct DatabusInputElements { // Column selectors (determine which bus column is being read) FF q_l; // calldata selector - FF q_r; // secondary_calldata selector + FF q_r; // app calldata selector FF q_o; // return_data selector - // Calldata (bus_idx = 0) + // Kernel calldata (bus_idx = 0) FF calldata; FF calldata_read_counts; FF calldata_inverses; - // Secondary calldata (bus_idx = 1) + // App calldata (bus_idx = 1) FF secondary_calldata; FF secondary_calldata_read_counts; FF secondary_calldata_inverses; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp index 209125db27c4..6f57902aa8f4 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp @@ -23,6 +23,8 @@ template class databus { using field_pt = field_t; public: + bus_vector() = default; + bus_vector(const BusId bus_idx) : bus_idx(bus_idx) {}; @@ -59,9 +61,15 @@ template class databus { }; public: - // The columns of the DataBus - bus_vector calldata{ BusId::CALLDATA }; - bus_vector secondary_calldata{ BusId::SECONDARY_CALLDATA }; + // The columns of the DataBus. + bus_vector kernel_calldata{ BusId::KERNEL_CALLDATA }; + std::array app_calldata = []() { + std::array result{}; + for (uint8_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) { + result[idx] = bus_vector{ static_cast(idx + 1) }; + } + return result; + }(); bus_vector return_data{ BusId::RETURNDATA }; }; @@ -73,13 +81,12 @@ template class databus { * \pi_i, and it has access to [C_i] directly from \pi_i. The consistency checks in circuit (i+1) are thus of the * form \pi_i.public_inputs.[R_{i-1}] = \pi_i.[C_i]. * - * For consistent behavior across kernels, every kernel propagates two return data commitments via its - * public inputs. If one of either the app or kernel return data does not exist, it is populated with a default - * value that will satisfy the consistency check on the next cycle. For example, the first kernel has no previous - * kernel to verify and thus neither receives a previous kernel return data commitment nor a calldata input - * corresponding to a previous kernel. The "empty" calldata will be populated with a default value, resulting in a - * default commitment value. We set the same value for the missing return data herein so that the commitments agree - * and the corresponding consistency check will be satisfied in the kernel in which it's performed. + * For consistent behavior across kernels, every kernel propagates `MAX_APPS_PER_KERNEL + 1` return-data commitments + * via its public inputs: one for the previous kernel and one per app slot. If any of these does not exist (e.g., the + * first kernel has no previous kernel; a kernel with fewer than MAX apps leaves the trailing app slots unset), it is + * populated with a default commitment value that will satisfy the consistency check on the next cycle. The "empty" + * calldata column on the next kernel side will commit to the same default value, so the commitments agree and the + * consistency check passes trivially. * * @tparam Builder */ @@ -91,11 +98,15 @@ template class DataBusDepot { using FrNative = typename Curve::ScalarFieldNative; // Storage for the return data commitments to be propagated via the public inputs - Commitment app_return_data_commitment; + std::array app_return_data_commitments; Commitment kernel_return_data_commitment; // Existence flags indicating whether each return data commitment has been set - bool app_return_data_commitment_exists = false; + std::array app_return_data_commitment_exists = []() { + std::array result{}; + result.fill(false); + return result; + }(); bool kernel_return_data_commitment_exists = false; void set_kernel_return_data_commitment(const Commitment& commitment) @@ -104,10 +115,39 @@ template class DataBusDepot { kernel_return_data_commitment_exists = true; } + /** + * @brief Whether all app return-data slots are currently empty. + * @details Used to assert the kernel-boundary invariant: at the start of each kernel completion, every slot must + * have been drained by the prior kernel's get-loop so that `set_app_return_data_commitment` begins filling from + * slot 0. + */ + bool app_return_data_slots_are_empty() const + { + for (size_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) { + if (app_return_data_commitment_exists[idx]) { + return false; + } + } + return true; + } + + /** + * @brief Record an app return-data commitment in the next available slot. + * @details Slot assignment is implicit: the depot fills slot 0 first, then slot 1, etc., as apps are processed in + * the kernel's verification queue. Slots are released by `get_app_return_data_commitment`; each kernel-completion + * pass drains every slot via the get-loop in `Chonk::complete_kernel_circuit_logic`, so the next kernel begins + * filling from slot 0 again. + */ void set_app_return_data_commitment(const Commitment& commitment) { - app_return_data_commitment = commitment; - app_return_data_commitment_exists = true; + for (size_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) { + if (!app_return_data_commitment_exists[idx]) { + app_return_data_commitments[idx] = commitment; + app_return_data_commitment_exists[idx] = true; + return; + } + } + BB_ASSERT(false, "DataBusDepot has no free app return-data slot"); } /** @@ -139,13 +179,14 @@ template class DataBusDepot { * @brief Get the previously set app return data commitment if it exists, else a default one * */ - Commitment get_app_return_data_commitment(Builder& builder) + Commitment get_app_return_data_commitment(Builder& builder, const size_t idx) { - if (!app_return_data_commitment_exists) { + BB_ASSERT_LT(idx, MAX_APPS_PER_KERNEL, "DataBusDepot app return-data index out of bounds"); + if (!app_return_data_commitment_exists[idx]) { return construct_default_commitment(builder); } - app_return_data_commitment_exists = false; // Reset the existence flag after retrieval - return app_return_data_commitment; + app_return_data_commitment_exists[idx] = false; // Reset the existence flag after retrieval + return app_return_data_commitments[idx]; } }; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.test.cpp index 6a5e11197833..6b7b3482bf5c 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.test.cpp @@ -41,7 +41,7 @@ TEST(Databus, CallDataAndReturnData) for (auto& value : raw_calldata_values) { calldata_values.emplace_back(witness_ct(&builder, value)); } - databus.calldata.set_values(calldata_values); + databus.kernel_calldata.set_values(calldata_values); // Populate the return data in the databus std::vector return_data_values; @@ -53,14 +53,14 @@ TEST(Databus, CallDataAndReturnData) // Establish that the first two outputs are simply copied over from the inputs. Each 'copy' requires two read gates. field_ct idx_0(witness_ct(&builder, 0)); field_ct idx_1(witness_ct(&builder, 1)); - databus.calldata[idx_0].assert_equal(databus.return_data[idx_0]); - databus.calldata[idx_1].assert_equal(databus.return_data[idx_1]); + databus.kernel_calldata[idx_0].assert_equal(databus.return_data[idx_0]); + databus.kernel_calldata[idx_1].assert_equal(databus.return_data[idx_1]); // Get the last two entries in calldata and compute their sum field_ct idx_2(witness_ct(&builder, 2)); field_ct idx_3(witness_ct(&builder, 3)); // This line creates an arithmetic gate and two calldata read gates (via operator[]). - field_ct sum = databus.calldata[idx_2] + databus.calldata[idx_3]; + field_ct sum = databus.kernel_calldata[idx_2] + databus.kernel_calldata[idx_3]; // Read the last index of the return data. (Creates a return data read gate via operator[]). field_ct idx(witness_ct(&builder, 2)); @@ -124,14 +124,14 @@ TEST(Databus, UnnormalizedEntryAccess) // add the value to itself to make it unnormalized (the multiplicative constant will be 2) returndata_entries.emplace_back(entry_witness + entry_witness); } - databus.calldata.set_values(calldata_entries); + databus.kernel_calldata.set_values(calldata_entries); databus.return_data.set_values(returndata_entries); field_ct idx_0 = witness_ct(&builder, 0); field_ct idx_1 = witness_ct(&builder, 1); field_ct idx_2 = witness_ct(&builder, 2); - databus.return_data[idx_0].assert_equal(databus.calldata[idx_0] + databus.calldata[idx_0]); - databus.return_data[idx_1].assert_equal(databus.calldata[idx_1] + databus.calldata[idx_1]); - databus.return_data[idx_2].assert_equal(databus.calldata[idx_2] + databus.calldata[idx_2]); + databus.return_data[idx_0].assert_equal(databus.kernel_calldata[idx_0] + databus.kernel_calldata[idx_0]); + databus.return_data[idx_1].assert_equal(databus.kernel_calldata[idx_1] + databus.kernel_calldata[idx_1]); + databus.return_data[idx_2].assert_equal(databus.kernel_calldata[idx_2] + databus.kernel_calldata[idx_2]); EXPECT_TRUE(CircuitChecker::check(builder)); } @@ -150,7 +150,7 @@ TEST(Databus, ConstantAndUnnormalizedIndices) for (auto& value : raw_calldata_values) { calldata_values.emplace_back(witness_ct(&builder, value)); } - databus.calldata.set_values(calldata_values); + databus.kernel_calldata.set_values(calldata_values); // Populate the return data in the databus std::vector returndata_values; @@ -164,10 +164,10 @@ TEST(Databus, ConstantAndUnnormalizedIndices) field_ct idx_1(witness_ct(&builder, 1)); // un-normalized index (with multiplicative constant 2) field_ct idx_2 = idx_1 + idx_1; - field_ct sum = databus.calldata[idx_0] + databus.calldata[idx_1] + databus.calldata[idx_2]; + field_ct sum = databus.kernel_calldata[idx_0] + databus.kernel_calldata[idx_1] + databus.kernel_calldata[idx_2]; - databus.return_data[idx_0].assert_equal(databus.calldata[idx_0]); - databus.return_data[idx_1].assert_equal(databus.calldata[idx_1]); + databus.return_data[idx_0].assert_equal(databus.kernel_calldata[idx_0]); + databus.return_data[idx_1].assert_equal(databus.kernel_calldata[idx_1]); databus.return_data[idx_2].assert_equal(sum); EXPECT_TRUE(CircuitChecker::check(builder)); @@ -218,7 +218,7 @@ TEST(Databus, BadCopyFailure) // Populate calldata with a single input fr input = 13; - databus.calldata.set_values({ witness_ct(&builder, input) }); + databus.kernel_calldata.set_values({ witness_ct(&builder, input) }); // Populate return data with an output different from the input fr output = input - 1; @@ -227,7 +227,7 @@ TEST(Databus, BadCopyFailure) // Attempt to attest that the calldata has been copied into the return data size_t raw_idx = 0; // read at 0th index field_ct idx(witness_ct(&builder, raw_idx)); - databus.calldata[idx].assert_equal(databus.return_data[idx]); + databus.kernel_calldata[idx].assert_equal(databus.return_data[idx]); // Since the output data is not a copy of the input, the checker should fail EXPECT_FALSE(CircuitChecker::check(builder)); @@ -251,7 +251,7 @@ TEST(Databus, DuplicateRead) for (auto& value : raw_calldata_values) { calldata_values.emplace_back(witness_ct(&builder, value)); } - databus.calldata.set_values(calldata_values); + databus.kernel_calldata.set_values(calldata_values); // Populate the return data in the databus std::vector return_data_values; @@ -264,10 +264,10 @@ TEST(Databus, DuplicateRead) field_ct idx_1(witness_ct(&builder, 1)); field_ct idx_2(witness_ct(&builder, 2)); - databus.calldata[idx_1]; - databus.calldata[idx_1]; - databus.calldata[idx_1]; - databus.calldata[idx_2]; + databus.kernel_calldata[idx_1]; + databus.kernel_calldata[idx_1]; + databus.kernel_calldata[idx_1]; + databus.kernel_calldata[idx_2]; databus.return_data[idx_2]; databus.return_data[idx_2]; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs.hpp b/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs.hpp index 552587fb8d9c..c9d36be8fc9a 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs.hpp @@ -55,10 +55,11 @@ std::array::Group, Builder::NUM_WIRES> empty_ecc_op_tabl } /** - * @brief Manages the data that is propagated on the public inputs of a kernel circuit + * @brief Manages the data that is propagated on the public inputs of a kernel circuit. * + * @tparam N Number of app return-data commitments carried by the kernel public inputs. */ -class KernelIO { +template class KernelIO_ { public: using Builder = MegaCircuitBuilder; // kernel builder is always Mega using Curve = stdlib::bn254; // curve is always bn254 @@ -66,19 +67,20 @@ class KernelIO { using FF = Curve::ScalarField; using PairingInputs = stdlib::recursion::PairingPoints; using TableCommitments = std::array; + using AppReturnDataCommitments = std::array; using PublicPoint = stdlib::PublicInputComponent; using PublicPairingPoints = stdlib::PublicInputComponent; using PublicFF = stdlib::PublicInputComponent; - PairingInputs pairing_inputs; // Inputs {P0, P1} to an EC pairing check - G1 kernel_return_data; // Commitment to the return data of a kernel circuit - G1 app_return_data; // Commitment to the return data of an app circuit - FF ecc_op_hash; // Running Poseidon2 hash over ECC op column commitments - FF output_hn_accum_hash; // hash of the output HN verifier accumulator + PairingInputs pairing_inputs; // Inputs {P0, P1} to an EC pairing check + G1 kernel_return_data; // Commitment to the return data of a kernel circuit + AppReturnDataCommitments app_return_data; // Commitment to each verified app circuit's return data + FF ecc_op_hash; // Running Poseidon2 hash over ECC op column commitments + FF output_hn_accum_hash; // hash of the output HN verifier accumulator // Total size of the kernel IO public inputs - static constexpr size_t PUBLIC_INPUTS_SIZE = KERNEL_PUBLIC_INPUTS_SIZE; + static constexpr size_t PUBLIC_INPUTS_SIZE = kernel_public_inputs_size(N); static constexpr bool HasIPA = false; /** @@ -97,8 +99,10 @@ class KernelIO { index += PairingInputs::PUBLIC_INPUTS_SIZE; kernel_return_data = PublicPoint::reconstruct(public_inputs, PublicComponentKey{ index }); index += G1::PUBLIC_INPUTS_SIZE; - app_return_data = PublicPoint::reconstruct(public_inputs, PublicComponentKey{ index }); - index += G1::PUBLIC_INPUTS_SIZE; + for (auto& app_commitment : app_return_data) { + app_commitment = PublicPoint::reconstruct(public_inputs, PublicComponentKey{ index }); + index += G1::PUBLIC_INPUTS_SIZE; + } ecc_op_hash = PublicFF::reconstruct(public_inputs, PublicComponentKey{ index }); index += FF::PUBLIC_INPUTS_SIZE; output_hn_accum_hash = PublicFF::reconstruct(public_inputs, PublicComponentKey{ index }); @@ -115,7 +119,9 @@ class KernelIO { pairing_inputs.set_public(builder); kernel_return_data.set_public(); - app_return_data.set_public(); + for (auto& app_commitment : app_return_data) { + app_commitment.set_public(); + } ecc_op_hash.set_public(); output_hn_accum_hash.set_public(); @@ -129,17 +135,21 @@ class KernelIO { */ static void add_default(Builder& builder) { - KernelIO inputs; + KernelIO_ inputs; inputs.pairing_inputs = PairingInputs::construct_default(); inputs.kernel_return_data = DataBusDepot::construct_default_commitment(builder); - inputs.app_return_data = DataBusDepot::construct_default_commitment(builder); + for (auto& app_commitment : inputs.app_return_data) { + app_commitment = DataBusDepot::construct_default_commitment(builder); + } inputs.ecc_op_hash = FF::from_witness(&builder, typename FF::native(0)); inputs.output_hn_accum_hash = FF::from_witness(&builder, typename FF::native(0)); inputs.set_public(); } }; +using KernelIO = KernelIO_; + /** * @brief Manages the data that is propagated on the public inputs of an application/function circuit * diff --git a/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs.test.cpp index 0d63d3b81109..0c489ead75d9 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs.test.cpp @@ -24,7 +24,10 @@ TEST_F(SpecialPublicInputsTests, Basic) G1Native P0_val = G1Native::random_element(); G1Native P1_val = G1Native::random_element(); G1Native kernel_return_data_val = G1Native::random_element(); - G1Native app_return_data_val = G1Native::random_element(); + std::array app_return_data_val; + for (auto& value : app_return_data_val) { + value = G1Native::random_element(); + } FFNative ecc_op_hash_val = FFNative::random_element(); FFNative output_hn_accum_hash_val = FFNative::random_element(); @@ -40,7 +43,9 @@ TEST_F(SpecialPublicInputsTests, Basic) PairingInputs pairing_inputs{ G1::from_witness(&builder, P0_val), G1::from_witness(&builder, P1_val) }; kernel_output.pairing_inputs = pairing_inputs; kernel_output.kernel_return_data = G1::from_witness(&builder, kernel_return_data_val); - kernel_output.app_return_data = G1::from_witness(&builder, app_return_data_val); + for (size_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) { + kernel_output.app_return_data[idx] = G1::from_witness(&builder, app_return_data_val[idx]); + } kernel_output.ecc_op_hash = FF::from_witness(&builder, ecc_op_hash_val); kernel_output.output_hn_accum_hash = FF::from_witness(&builder, output_hn_accum_hash_val); @@ -70,7 +75,9 @@ TEST_F(SpecialPublicInputsTests, Basic) EXPECT_EQ(kernel_input.pairing_inputs.P0().get_value(), P0_val); EXPECT_EQ(kernel_input.pairing_inputs.P1().get_value(), P1_val); EXPECT_EQ(kernel_input.kernel_return_data.get_value(), kernel_return_data_val); - EXPECT_EQ(kernel_input.app_return_data.get_value(), app_return_data_val); + for (size_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) { + EXPECT_EQ(kernel_input.app_return_data[idx].get_value(), app_return_data_val[idx]); + } EXPECT_EQ(kernel_input.ecc_op_hash.get_value(), ecc_op_hash_val); EXPECT_EQ(kernel_input.output_hn_accum_hash.get_value(), output_hn_accum_hash_val); } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs_test_serde.hpp b/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs_test_serde.hpp index 7d4925775d3a..c1e2c3bcbab7 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs_test_serde.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/special_public_inputs/special_public_inputs_test_serde.hpp @@ -12,20 +12,21 @@ namespace bb::stdlib::recursion::honk { /** * @brief **For test purposes only**: Native representation and serde for KernelIO public inputs * @details Used for testing and verification with native bb::fr vectors. - * Mirrors the structure of stdlib KernelIO but works with native types. + * Mirrors the structure of stdlib KernelIO_ but works with native types. */ -class KernelIOSerde { +template class KernelIOSerde_ { public: using NativeFF = bb::fr; using NativeG1 = curve::BN254::AffineElement; using NativeFq = curve::BN254::BaseField; using NativePairingPoints = bb::PairingPoints; + using NativeAppReturnDataCommitments = std::array; - static constexpr size_t PUBLIC_INPUTS_SIZE = KERNEL_PUBLIC_INPUTS_SIZE; + static constexpr size_t PUBLIC_INPUTS_SIZE = kernel_public_inputs_size(N); NativePairingPoints pairing_inputs; NativeG1 kernel_return_data; - NativeG1 app_return_data; + NativeAppReturnDataCommitments app_return_data; NativeFF ecc_op_hash; NativeFF output_hn_accum_hash; @@ -36,9 +37,9 @@ class KernelIOSerde { * @details KernelIO is at the END of the public inputs section, so we start at * offset (num_public_inputs - PUBLIC_INPUTS_SIZE) */ - static KernelIOSerde from_proof(const std::vector& proof, size_t num_public_inputs) + static KernelIOSerde_ from_proof(const std::vector& proof, size_t num_public_inputs) { - KernelIOSerde result; + KernelIOSerde_ result; // KernelIO is at the end of public inputs, which are at the start of the proof size_t idx = num_public_inputs - PUBLIC_INPUTS_SIZE; @@ -53,7 +54,9 @@ class KernelIOSerde { result.pairing_inputs.P0() = deserialize_point(); result.pairing_inputs.P1() = deserialize_point(); result.kernel_return_data = deserialize_point(); - result.app_return_data = deserialize_point(); + for (auto& app_commitment : result.app_return_data) { + app_commitment = deserialize_point(); + } result.ecc_op_hash = proof[idx++]; result.output_hn_accum_hash = proof[idx]; @@ -93,12 +96,16 @@ class KernelIOSerde { serialize_point(pairing_inputs.P0()); serialize_point(pairing_inputs.P1()); serialize_point(kernel_return_data); - serialize_point(app_return_data); + for (const auto& app_commitment : app_return_data) { + serialize_point(app_commitment); + } proof[idx++] = ecc_op_hash; proof[idx] = output_hn_accum_hash; } }; +using KernelIOSerde = KernelIOSerde_; + /** * @brief Native representation and serde for HidingKernelIO public inputs * @details Used for testing and verification with native bb::fr vectors. diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/databus.hpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/databus.hpp index 07173d8d7386..eb0eab6afe1e 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/databus.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/databus.hpp @@ -7,6 +7,7 @@ #pragma once #include "barretenberg/common/assert.hpp" +#include "barretenberg/constants.hpp" #include "barretenberg/ecc/curves/bn254/fr.hpp" #include "barretenberg/public_input_component/public_component_key.hpp" #include @@ -73,9 +74,14 @@ struct BusVector { * in-circuit as we would with public inputs). * */ -constexpr size_t NUM_BUS_COLUMNS = 3; +constexpr size_t NUM_BUS_COLUMNS = MAX_APPS_PER_KERNEL + /*kernel calldata*/ 1 + /*return data*/ 1; using DataBus = std::array; -enum class BusId { CALLDATA, SECONDARY_CALLDATA, RETURNDATA }; +enum class BusId : uint8_t { + KERNEL_CALLDATA = 0, + APP_CALLDATA = 1, + RETURNDATA = MAX_APPS_PER_KERNEL + 1, +}; +static_assert(static_cast(BusId::RETURNDATA) == NUM_BUS_COLUMNS - 1, "BusId must match DataBus layout"); } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp index 16c5c4dbbfb4..d83579b85452 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp @@ -125,20 +125,10 @@ template class MegaCircuitBuilder_ : public UltraCircuitBuilder_ class MegaCircuitBuilder_ : public UltraCircuitBuilder_ class MegaCircuitBuilder_ : public UltraCircuitBuilder_(bus_idx)].append(witness_idx); } - const BusVector& get_calldata() const { return databus[static_cast(BusId::CALLDATA)]; } - const BusVector& get_secondary_calldata() const { return databus[static_cast(BusId::SECONDARY_CALLDATA)]; } + const BusVector& get_calldata(BusId idx) const { return databus[static_cast(idx)]; } const BusVector& get_return_data() const { return databus[static_cast(BusId::RETURNDATA)]; } // Indexed access to the databus columns; enables NUM_BUS_COLUMNS-driven iteration over bus vectors. const BusVector& get_bus_vector(size_t bus_idx) const { return databus[bus_idx]; } diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/databus.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/databus.test.cpp index 2bf5e816818f..0673325944c5 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/databus.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/databus.test.cpp @@ -1,3 +1,4 @@ +#include "barretenberg/stdlib_circuit_builders/databus.hpp" #include #include #include @@ -61,10 +62,7 @@ template class DataBusTests : public ::testing::Test { * @param read_bus_data Method for reading from a given bus column * @return Builder */ - static Builder construct_circuit_with_databus_reads( - Builder& builder, - const std::function& add_bus_data, - const std::function& read_bus_data) + static Builder construct_circuit_with_databus_reads(Builder& builder, const BusId& bus_idx) { const uint32_t NUM_BUS_ENTRIES = 5; // number of entries in the bus column @@ -74,49 +72,18 @@ template class DataBusTests : public ::testing::Test { for (size_t i = 0; i < NUM_BUS_ENTRIES; ++i) { FF val = FF::random_element(); uint32_t val_witness_idx = builder.add_variable(val); - add_bus_data(builder, val_witness_idx); + builder.add_public_calldata(bus_idx, val_witness_idx); } // Read from the bus at some random indices for (size_t i = 0; i < NUM_READS; ++i) { uint32_t read_idx = engine.get_random_uint32() % NUM_BUS_ENTRIES; uint32_t read_idx_witness_idx = builder.add_variable(FF(read_idx)); - read_bus_data(builder, read_idx_witness_idx); + builder.read_calldata(bus_idx, read_idx_witness_idx); } return builder; } - - static Builder construct_circuit_with_calldata_reads(Builder& builder) - { - // Define interfaces for the add and read methods for databus calldata - auto add_method = [](Builder& builder, uint32_t witness_idx) { builder.add_public_calldata(witness_idx); }; - auto read_method = [](Builder& builder, uint32_t witness_idx) { return builder.read_calldata(witness_idx); }; - - return construct_circuit_with_databus_reads(builder, add_method, read_method); - } - - static Builder construct_circuit_with_secondary_calldata_reads(Builder& builder) - { - // Define interfaces for the add and read methods for databus secondary_calldata - auto add_method = [](Builder& builder, uint32_t witness_idx) { - builder.add_public_secondary_calldata(witness_idx); - }; - auto read_method = [](Builder& builder, uint32_t witness_idx) { - return builder.read_secondary_calldata(witness_idx); - }; - - return construct_circuit_with_databus_reads(builder, add_method, read_method); - } - - static Builder construct_circuit_with_return_data_reads(Builder& builder) - { - // Define interfaces for the add and read methods for databus return data - auto add_method = [](Builder& builder, uint32_t witness_idx) { builder.add_public_return_data(witness_idx); }; - auto read_method = [](Builder& builder, uint32_t witness_idx) { return builder.read_return_data(witness_idx); }; - - return construct_circuit_with_databus_reads(builder, add_method, read_method); - } }; TYPED_TEST_SUITE(DataBusTests, FlavorTypes); @@ -125,10 +92,10 @@ TYPED_TEST_SUITE(DataBusTests, FlavorTypes); * @brief Test proof construction/verification for a circuit with calldata lookup gates * */ -TYPED_TEST(DataBusTests, CallDataRead) +TYPED_TEST(DataBusTests, KernelCallDataRead) { typename TypeParam::CircuitBuilder builder = this->construct_test_builder(); - this->construct_circuit_with_calldata_reads(builder); + this->construct_circuit_with_databus_reads(builder, BusId::KERNEL_CALLDATA); EXPECT_TRUE(CircuitChecker::check(builder)); EXPECT_TRUE(this->construct_and_verify_proof(builder)); } @@ -137,12 +104,14 @@ TYPED_TEST(DataBusTests, CallDataRead) * @brief Test proof construction/verification for a circuit with secondary_calldata lookup gates * */ -TYPED_TEST(DataBusTests, CallData2Read) +TYPED_TEST(DataBusTests, AppCallDataRead) { - typename TypeParam::CircuitBuilder builder = this->construct_test_builder(); - this->construct_circuit_with_secondary_calldata_reads(builder); + for (size_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) { + typename TypeParam::CircuitBuilder builder = this->construct_test_builder(); + this->construct_circuit_with_databus_reads(builder, static_cast(idx + 1)); - EXPECT_TRUE(this->construct_and_verify_proof(builder)); + EXPECT_TRUE(this->construct_and_verify_proof(builder)) << "Failed for app calldata bus with index " << idx; + } } /** @@ -152,7 +121,7 @@ TYPED_TEST(DataBusTests, CallData2Read) TYPED_TEST(DataBusTests, ReturnDataRead) { typename TypeParam::CircuitBuilder builder = this->construct_test_builder(); - this->construct_circuit_with_return_data_reads(builder); + this->construct_circuit_with_databus_reads(builder, BusId::RETURNDATA); EXPECT_TRUE(this->construct_and_verify_proof(builder)); } @@ -164,9 +133,11 @@ TYPED_TEST(DataBusTests, ReturnDataRead) TYPED_TEST(DataBusTests, ReadAll) { typename TypeParam::CircuitBuilder builder = this->construct_test_builder(); - this->construct_circuit_with_calldata_reads(builder); - this->construct_circuit_with_secondary_calldata_reads(builder); - this->construct_circuit_with_return_data_reads(builder); + this->construct_circuit_with_databus_reads(builder, BusId::KERNEL_CALLDATA); + for (size_t idx = 0; idx < MAX_APPS_PER_KERNEL; ++idx) { + this->construct_circuit_with_databus_reads(builder, static_cast(idx + 1)); + } + this->construct_circuit_with_databus_reads(builder, BusId::RETURNDATA); EXPECT_TRUE(this->construct_and_verify_proof(builder)); } @@ -186,7 +157,7 @@ TYPED_TEST(DataBusTests, CallDataDuplicateRead) std::vector calldata_values = { 7, 10, 3, 12, 1 }; for (auto& val : calldata_values) { - builder.add_public_calldata(builder.add_variable(val)); + builder.add_public_calldata(BusId::KERNEL_CALLDATA, builder.add_variable(val)); } // Define some read indices with a duplicate @@ -198,7 +169,7 @@ TYPED_TEST(DataBusTests, CallDataDuplicateRead) // Create a variable corresponding to the index at which we want to read into calldata uint32_t read_idx_witness_idx = builder.add_variable(FF(read_idx)); - auto value_witness_idx = builder.read_calldata(read_idx_witness_idx); + auto value_witness_idx = builder.read_calldata(BusId::KERNEL_CALLDATA, read_idx_witness_idx); result_witness_indices.emplace_back(value_witness_idx); } diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp index d3b8a054ed94..8f97982884bb 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp @@ -106,12 +106,12 @@ template class HonkTranscriptTests : public ::testing::Test { manifest_expected.add_entry(round, "ECC_OP_WIRE_2", data_types_per_G); manifest_expected.add_entry(round, "ECC_OP_WIRE_3", data_types_per_G); manifest_expected.add_entry(round, "ECC_OP_WIRE_4", data_types_per_G); - manifest_expected.add_entry(round, "CALLDATA", data_types_per_G); - manifest_expected.add_entry(round, "CALLDATA_READ_COUNTS", data_types_per_G); - manifest_expected.add_entry(round, "SECONDARY_CALLDATA", data_types_per_G); - manifest_expected.add_entry(round, "SECONDARY_CALLDATA_READ_COUNTS", data_types_per_G); - manifest_expected.add_entry(round, "RETURN_DATA", data_types_per_G); - manifest_expected.add_entry(round, "RETURN_DATA_READ_COUNTS", data_types_per_G); + manifest_expected.add_entry(round, "KERNEL_CALLDATA", data_types_per_G); + manifest_expected.add_entry(round, "KERNEL_CALLDATA_READ_COUNTS", data_types_per_G); + manifest_expected.add_entry(round, "APP_CALLDATA", data_types_per_G); + manifest_expected.add_entry(round, "APP_CALLDATA_READ_COUNTS", data_types_per_G); + manifest_expected.add_entry(round, "RETURNDATA", data_types_per_G); + manifest_expected.add_entry(round, "RETURNDATA_READ_COUNTS", data_types_per_G); } manifest_expected.add_challenge(round, "eta"); @@ -126,9 +126,9 @@ template class HonkTranscriptTests : public ::testing::Test { manifest_expected.add_entry(round, "LOOKUP_INVERSES", data_types_per_G); // Mega-specific databus inverse commitments if constexpr (IsMegaFlavor) { - manifest_expected.add_entry(round, "CALLDATA_INVERSES", data_types_per_G); - manifest_expected.add_entry(round, "SECONDARY_CALLDATA_INVERSES", data_types_per_G); - manifest_expected.add_entry(round, "RETURN_DATA_INVERSES", data_types_per_G); + manifest_expected.add_entry(round, "KERNEL_CALLDATA_INVERSES", data_types_per_G); + manifest_expected.add_entry(round, "APP_CALLDATA_INVERSES", data_types_per_G); + manifest_expected.add_entry(round, "RETURNDATA_INVERSES", data_types_per_G); } manifest_expected.add_entry(round, "Z_PERM", data_types_per_G); diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/relation_correctness.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/relation_correctness.test.cpp index 46d4af703c5f..1ae2eef4b06f 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/relation_correctness.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/relation_correctness.test.cpp @@ -180,10 +180,10 @@ void create_some_databus_gates(auto& builder) { using FF = typename Flavor::FF; auto val = builder.add_variable(FF::random_element()); - builder.add_public_calldata(val); - builder.read_calldata(builder.add_variable(FF(0))); - builder.add_public_secondary_calldata(val); - builder.read_secondary_calldata(builder.add_variable(FF(0))); + builder.add_public_calldata(BusId::KERNEL_CALLDATA, val); + builder.read_calldata(BusId::KERNEL_CALLDATA, builder.add_variable(FF(0))); + builder.add_public_calldata(BusId::APP_CALLDATA, val); + builder.read_calldata(BusId::APP_CALLDATA, builder.add_variable(FF(0))); builder.add_public_return_data(val); builder.read_return_data(builder.add_variable(FF(0))); } From 024b01bf6892fb97bf42b48e739bfed7b49204c5 Mon Sep 17 00:00:00 2001 From: Jean M <132435771+jeanmon@users.noreply.github.com> Date: Wed, 6 May 2026 14:18:16 +0200 Subject: [PATCH 10/15] refactor(avm)!: consolidate revertible/non-revertible append selectors (#22982) The 6 boolean pairs (non_revertible_append_X, revertible_append_X) for X in {note_hash, nullifier, l2_l1_msg} in TX_PHASE_SPEC_MAP were perfectly correlated with is_revertible: in every phase where one of them is set, is_revertible already determines which side. Replace each pair with a single sel_append_X selector and let is_revertible carry the revertibility bit. This removes 3 precomputed columns and 3 committed columns in the tx trace, shrinks the #[READ_PHASE_SPEC] lookup tuple, and simplifies the sel_try_X_append / is_tree_insert_phase / SEL_CAN_EMIT_X expressions. In #[NOTE_HASH_APPEND], the sel_unique flag (previously fed by sel_revertible_append_note_hash) is now is_revertible directly: on the two rows where the lookup is gated on, the two values agree by construction, and is_revertible is the more direct semantic for the "make this note hash unique with a nonce" decision. Updates the hardcoded precomputed VK commitments in avm_fixed_vk.hpp and the corresponding vk_hash; new values obtained from AvmFixedVKTests.FixedVKCommitments. --- barretenberg/cpp/pil/vm2/precomputed.pil | 44 +++++++++---------- barretenberg/cpp/pil/vm2/tx.pil | 44 ++++++++----------- barretenberg/cpp/pil/vm2/tx_context.pil | 6 +-- .../vm2/constraining/avm_fixed_vk.hpp | 38 ++++++---------- .../vm2/constraining/relations/tx.test.cpp | 18 ++++---- .../barretenberg/vm2/generated/columns.hpp | 14 +++--- .../vm2/generated/flavor_variables.hpp | 8 ++-- .../vm2/generated/relations/lookups_tx.hpp | 34 +++++--------- .../generated/relations/tx_context_impl.hpp | 12 ++--- .../vm2/generated/relations/tx_impl.hpp | 15 +++---- .../vm2/tracegen/lib/phase_spec.cpp | 12 ++--- .../vm2/tracegen/lib/phase_spec.hpp | 9 ++-- .../vm2/tracegen/precomputed_trace.cpp | 9 ++-- .../barretenberg/vm2/tracegen/tx_trace.cpp | 41 ++++------------- .../vm2/tracegen/tx_trace.test.cpp | 2 +- 15 files changed, 116 insertions(+), 190 deletions(-) diff --git a/barretenberg/cpp/pil/vm2/precomputed.pil b/barretenberg/cpp/pil/vm2/precomputed.pil index a8611d6826da..734d6b761e46 100644 --- a/barretenberg/cpp/pil/vm2/precomputed.pil +++ b/barretenberg/cpp/pil/vm2/precomputed.pil @@ -209,7 +209,7 @@ pol constant sel_mem_tag_out_of_range; // - instruction size (in bytes): instr_size // - Selector on whether the instruction has a tag: sel_has_tag // - Selector on whether operand op2 is a tag: sel_tag_is_op2 -// +// // Used by the instruction fetching subtrace to decode raw bytecode. // Selectors for operands decomposition into bytes (required by instr_fetching.pil) // This table is populated by a map generated by a cpp test defined in op_decomposition.test.cpp. @@ -361,22 +361,23 @@ pol constant addressing_gas; // properties: which tree operations are allowed, public-input offsets, revert behavior. // Used by the transaction trace to enforce phase-based constraints. // -// Example trace (idx encodes the TransactionPhase enum; abbreviated column names): -// idx | sel_phase | is_call | is_teardown | is_collect_fee | is_tree_padding | is_cleanup | is_revertible | nr_null | nr_note | nr_l2l1 | r_null | r_note | r_l2l1 | next_on_revert -// ----+-----------+---------+-------------+----------------+-----------------+------------+---------------+---------+---------+---------+--------+--------+--------+--------------- -// 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 (NR_NULLIFIER_INSERTION) -// 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 (NR_NOTE_INSERTION) -// 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 (NR_L2_TO_L1_MESSAGE) -// 3 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (SETUP) -// 4 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 8 (R_NULLIFIER_INSERTION) -// 5 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 8 (R_NOTE_INSERTION) -// 6 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 8 (R_L2_TO_L1_MESSAGE) -// 7 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 8 (APP_LOGIC) -// 8 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 9 (TEARDOWN) -// 9 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (COLLECT_GAS_FEES) -// 10 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (TREE_PADDING) -// 11 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (CLEANUP) -// 12 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (beyond phase range) +// Example trace (idx encodes the TransactionPhase enum; abbreviated column names). +// Whether an append is revertible or not is determined by `is_revertible`. +// idx | sel_phase | is_call | is_teardown | is_collect_fee | is_tree_padding | is_cleanup | is_revertible | append_null | append_note | append_l2l1 | next_on_revert +// ----+-----------+---------+-------------+----------------+-----------------+------------+---------------+-------------+-------------+-------------+--------------- +// 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 (NR_NULLIFIER_INSERTION) +// 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 (NR_NOTE_INSERTION) +// 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 (NR_L2_TO_L1_MESSAGE) +// 3 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (SETUP) +// 4 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 8 (R_NULLIFIER_INSERTION) +// 5 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 8 (R_NOTE_INSERTION) +// 6 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 8 (R_L2_TO_L1_MESSAGE) +// 7 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 8 (APP_LOGIC) +// 8 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 9 (TEARDOWN) +// 9 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (COLLECT_GAS_FEES) +// 10 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 (TREE_PADDING) +// 11 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 (CLEANUP) +// 12 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 (beyond phase range) // Phase Table for Tx Trace pol constant sel_phase; // Selector for phase table @@ -389,12 +390,9 @@ pol constant is_cleanup; pol constant is_revertible; pol constant read_pi_start_offset; pol constant read_pi_length_offset; -pol constant sel_non_revertible_append_note_hash; -pol constant sel_non_revertible_append_nullifier; -pol constant sel_non_revertible_append_l2_l1_msg; -pol constant sel_revertible_append_note_hash; -pol constant sel_revertible_append_nullifier; -pol constant sel_revertible_append_l2_l1_msg; +pol constant sel_append_note_hash; +pol constant sel_append_nullifier; +pol constant sel_append_l2_l1_msg; pol constant next_phase_on_revert; // ===== Section 12: Keccak round constants ===== diff --git a/barretenberg/cpp/pil/vm2/tx.pil b/barretenberg/cpp/pil/vm2/tx.pil index 0b4b62757ecd..fefc07a34d31 100644 --- a/barretenberg/cpp/pil/vm2/tx.pil +++ b/barretenberg/cpp/pil/vm2/tx.pil @@ -209,12 +209,9 @@ namespace tx; is_revertible, read_pi_start_offset, read_pi_length_offset, - sel_non_revertible_append_note_hash, - sel_non_revertible_append_nullifier, - sel_non_revertible_append_l2_l1_msg, - sel_revertible_append_note_hash, - sel_revertible_append_nullifier, - sel_revertible_append_l2_l1_msg, + sel_append_note_hash, + sel_append_nullifier, + sel_append_l2_l1_msg, next_phase_on_revert } in @@ -228,12 +225,9 @@ namespace tx; precomputed.is_revertible, precomputed.read_pi_start_offset, precomputed.read_pi_length_offset, - precomputed.sel_non_revertible_append_note_hash, - precomputed.sel_non_revertible_append_nullifier, - precomputed.sel_non_revertible_append_l2_l1_msg, - precomputed.sel_revertible_append_note_hash, - precomputed.sel_revertible_append_nullifier, - precomputed.sel_revertible_append_l2_l1_msg, + precomputed.sel_append_note_hash, + precomputed.sel_append_nullifier, + precomputed.sel_append_l2_l1_msg, precomputed.next_phase_on_revert }; @@ -465,18 +459,16 @@ namespace tx; /*************************************************************************** * Private Side Effect Insertions **************************************************************************/ - pol commit sel_revertible_append_note_hash; // @boolean - pol commit sel_non_revertible_append_note_hash; // @boolean - pol commit sel_revertible_append_nullifier; // @boolean - pol commit sel_non_revertible_append_nullifier; // @boolean - pol commit sel_revertible_append_l2_l1_msg; // @boolean - pol commit sel_non_revertible_append_l2_l1_msg; // @boolean - // The 6 above selectors are booleans thanks to #[READ_PHASE_SPEC] on active rows. + pol commit sel_append_note_hash; // @boolean + pol commit sel_append_nullifier; // @boolean + pol commit sel_append_l2_l1_msg; // @boolean + // The 3 above selectors are booleans thanks to #[READ_PHASE_SPEC] on active rows. // Furthermore, phase spec guarantees that they are mutually exclusive. + // Whether such an append is revertible or not is determined by `is_revertible`. // A tree selector means we need to get the tree value - pol commit is_tree_insert_phase; // @boolean on active rows (follows from mutual exclusivity of the 6 above selectors) - is_tree_insert_phase = sel_revertible_append_note_hash + sel_non_revertible_append_note_hash + sel_revertible_append_nullifier + sel_non_revertible_append_nullifier; + pol commit is_tree_insert_phase; // @boolean on active rows (follows from mutual exclusivity of the above selectors) + is_tree_insert_phase = sel_append_note_hash + sel_append_nullifier; pol commit leaf_value; // Shared column to track the inverse of the remaining side effects for note hashes, nullifiers, and L2 to L1 messages pol commit remaining_side_effects_inv; @@ -491,7 +483,7 @@ namespace tx; // ===== NOTE HASHES ===== pol commit sel_try_note_hash_append; // @boolean (follows from definition) - sel_try_note_hash_append = (sel - is_padded) * (sel_revertible_append_note_hash + sel_non_revertible_append_note_hash); + sel_try_note_hash_append = (sel - is_padded) * sel_append_note_hash; // If we are at the maximum emitted note hashes, we must revert pol REMAINING_NOTE_HASH_WRITES = constants.MAX_NOTE_HASHES_PER_TX - prev_num_note_hashes_emitted; @@ -509,7 +501,9 @@ namespace tx; prev_note_hash_tree_size, prev_note_hash_tree_root, precomputed.zero, // Already siloed. (No need to pass address.) - sel_revertible_append_note_hash, // Not unique for revertible note hashes. + is_revertible, // Used as `sel_unique`: revertible note hashes need to be made unique with a nonce. + // On rows where sel_note_hash_append == 1 (NR_NOTE_INSERTION or R_NOTE_INSERTION), + // is_revertible is exactly the discriminator we want. prev_num_note_hashes_emitted, discard, // from tx_discard.pil virtual trace next_note_hash_tree_root @@ -532,7 +526,7 @@ namespace tx; // ===== NULLIFIERS ===== pol commit sel_try_nullifier_append; // @boolean (follows from definition) - sel_try_nullifier_append = (sel - is_padded) * (sel_revertible_append_nullifier + sel_non_revertible_append_nullifier); + sel_try_nullifier_append = (sel - is_padded) * sel_append_nullifier; pol commit nullifier_limit_error; // @boolean nullifier_limit_error * (1 - nullifier_limit_error) = 0; @@ -594,7 +588,7 @@ namespace tx; // ===== L2 - L1 Messages ===== pol commit sel_try_l2_l1_msg_append; // @boolean (follows from definition) - sel_try_l2_l1_msg_append = (sel - is_padded) * (sel_revertible_append_l2_l1_msg + sel_non_revertible_append_l2_l1_msg); + sel_try_l2_l1_msg_append = (sel - is_padded) * sel_append_l2_l1_msg; pol commit l2_l1_msg_contract_address; pol commit l2_l1_msg_recipient; diff --git a/barretenberg/cpp/pil/vm2/tx_context.pil b/barretenberg/cpp/pil/vm2/tx_context.pil index d17074b09cd7..832773e0f5d8 100644 --- a/barretenberg/cpp/pil/vm2/tx_context.pil +++ b/barretenberg/cpp/pil/vm2/tx_context.pil @@ -330,12 +330,12 @@ namespace tx; NOT_LAST_ROW * (1 - is_teardown') * (da_gas_limit - da_gas_limit') = 0; // Selectors to allow prev => next state changes in the different phases - pol SEL_CAN_EMIT_NOTE_HASH = is_public_call_request + sel_non_revertible_append_note_hash + sel_revertible_append_note_hash; - pol SEL_CAN_EMIT_NULLIFIER = is_public_call_request + sel_non_revertible_append_nullifier + sel_revertible_append_nullifier; + pol SEL_CAN_EMIT_NOTE_HASH = is_public_call_request + sel_append_note_hash; + pol SEL_CAN_EMIT_NULLIFIER = is_public_call_request + sel_append_nullifier; pol SEL_CAN_WRITE_PUBLIC_DATA = is_public_call_request + is_collect_fee; pol SEL_CAN_WRITE_WRITTEN_PUBLIC_DATA_SLOTS = is_public_call_request; pol SEL_CAN_EMIT_PUBLIC_LOG = is_public_call_request; - pol SEL_CAN_EMIT_L2_L1_MSG = is_public_call_request + sel_non_revertible_append_l2_l1_msg + sel_revertible_append_l2_l1_msg; + pol SEL_CAN_EMIT_L2_L1_MSG = is_public_call_request + sel_append_l2_l1_msg; // The 6 above selectors are booleans on active rows due to #[READ_PHASE_SPEC] and // mutual exclusivity of the selectors on the right-hand side. diff --git a/barretenberg/cpp/src/barretenberg/vm2/constraining/avm_fixed_vk.hpp b/barretenberg/cpp/src/barretenberg/vm2/constraining/avm_fixed_vk.hpp index 52906d44e2c2..c060244f6965 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/constraining/avm_fixed_vk.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/constraining/avm_fixed_vk.hpp @@ -17,7 +17,7 @@ class AvmHardCodedVKAndHash { using FF = bb::curve::BN254::ScalarField; // Precomputed VK hash (hash of all commitments below). - static FF vk_hash() { return FF(uint256_t("0x183783fd7c3f269b595307224be99cf3773b781f8d1d59a305ce711101d43a60")); } + static FF vk_hash() { return FF(uint256_t("0x23a03c6f87c465dbecc386b091e8123a8936597b5b0749f276d042a8964bd390")); } static constexpr std::array get_all() { @@ -232,6 +232,18 @@ class AvmHardCodedVKAndHash { uint256_t("0x0752e216f6398f2dc16b86cd762f9bd9f961964f9c6a354530c45b04920f06ab"), uint256_t( "0x062522db0dc283ad1d328147904f0fdc0e44add870aa0b099cf16c3d73352a9e")), // precomputed_sel_addressing_gas + Commitment( + uint256_t("0x095419f3dc475e499012c5d001c266643669a19173217b51fd5f2a86b3e1a8b2"), + uint256_t( + "0x0f9bf4c4f62da52213998f25ab3eca754175cf4580e070f1abb251e2d8a8e64a")), // precomputed_sel_append_l2_l1_msg + Commitment( + uint256_t("0x2932e8961b4b905fe11c2f93092e57d7e541a9bb00aca69af2a6d213577670ea"), + uint256_t( + "0x20b57b640b0186c53727c6f4724dc71b51387a34d8db51af06b1f9ad3a92d467")), // precomputed_sel_append_note_hash + Commitment( + uint256_t("0x1eba8da14083ce2c1b307a5493006a232c89e55fa657f9c193f5654990f06544"), + uint256_t( + "0x07b64d628ee70ee71e89aaabb91abd9005096a24e8c4cd3543cbf1b9344e108f")), // precomputed_sel_append_nullifier Commitment( uint256_t("0x2059be69211e5ea9bb365ab69c1132eb7b7c6814925453953f62bf731e5e42f9"), uint256_t( @@ -280,18 +292,6 @@ class AvmHardCodedVKAndHash { uint256_t("0x089cdab4e8e8381977b093cb267a1b7c8c60f4466c39a99af1247e37fe56ebfe"), uint256_t( "0x1144347d2bfe5c1f4a6d44418562facb9a5c9c7bf2b6b463424e8b0915254710")), // precomputed_sel_mem_tag_out_of_range - Commitment( - uint256_t("0x020ad6e43ccd48a6a39e43897cc85187bd364919be8a3b82d4809715cfe489db"), - uint256_t( - "0x21a79ebae2ea3d92b49c521407d2600ac061146f2c188c6c6a33c598179e4543")), // precomputed_sel_non_revertible_append_l2_l1_msg - Commitment( - uint256_t("0x2d360628289ff943ff6bd1a87bbe4e62abe7fb61ba83effd266f22bdcf31e6f9"), - uint256_t( - "0x26b92a79e563c3f48252cce7feeca2f0f8d33dcb4ef7b0643bf07bd405700aaa")), // precomputed_sel_non_revertible_append_note_hash - Commitment( - uint256_t("0x0000000000000000000000000000000000000000000000000000000000000001"), - uint256_t( - "0x0000000000000000000000000000000000000000000000000000000000000002")), // precomputed_sel_non_revertible_append_nullifier Commitment( uint256_t("0x0bf1970c2e92fee577ba15d063fa78fdd17752cafd19261ff0f176a1d3348769"), uint256_t( @@ -404,18 +404,6 @@ class AvmHardCodedVKAndHash { uint256_t("0x2e51e57417ece86800e7afa2ac53cfffcf35343cfb4bad1f6016a5b657fc3bfe"), uint256_t( "0x2c8617a36d1bbb5e7bf06c192e8ffc9aa90c714d222f8c8c29ed6a8a7e5eb717")), // precomputed_sel_range_8 - Commitment( - uint256_t("0x262d212add82bcbcf96d0773c59926e1b8e68e45c662f9348f2e4f64770595b3"), - uint256_t( - "0x2fe4de705da2b7bfb03cb3baa199ed4cc97e6ce620d0e939b603493223e88703")), // precomputed_sel_revertible_append_l2_l1_msg - Commitment( - uint256_t("0x041008987db8f55ded689b589133da9860150ed8c97b6bb5e87f0a31f78582b8"), - uint256_t( - "0x113ecb4f4d07b4efb19a22b59e5634d58e5f1d5a433b08a32f1ac2bdd0e7c01a")), // precomputed_sel_revertible_append_note_hash - Commitment( - uint256_t("0x2a56ce41f6b0be13b9c26747621b821eee81b23a887f299049b14c11e98460d6"), - uint256_t( - "0x1aa98f2de3ddda547d8f6de4e725ded5827d6338c78656c0d12ca1aea6ef2c7c")), // precomputed_sel_revertible_append_nullifier Commitment( uint256_t("0x2db8d548af3efd182047c9081ce2870f3c2e7a96b4a6469aca26167209285d9b"), uint256_t( diff --git a/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/tx.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/tx.test.cpp index 23fc4ba1a774..2330f2d13d62 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/tx.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/constraining/relations/tx.test.cpp @@ -95,7 +95,7 @@ class TxExecutionConstrainingTestHelper : public ::testing::Test { { C::tx_phase_value, static_cast(TransactionPhase::NR_NULLIFIER_INSERTION) }, { C::tx_is_padded, 1 }, { C::tx_is_tree_insert_phase, 1 }, - { C::tx_sel_non_revertible_append_nullifier, 1 }, + { C::tx_sel_append_nullifier, 1 }, { C::tx_read_pi_start_offset, AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_NULLIFIERS_ROW_IDX }, @@ -114,7 +114,7 @@ class TxExecutionConstrainingTestHelper : public ::testing::Test { { C::tx_phase_value, static_cast(TransactionPhase::NR_NOTE_INSERTION) }, { C::tx_is_padded, 1 }, { C::tx_is_tree_insert_phase, 1 }, - { C::tx_sel_non_revertible_append_note_hash, 1 }, + { C::tx_sel_append_note_hash, 1 }, { C::tx_read_pi_start_offset, AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_NOTE_HASHES_ROW_IDX }, { C::tx_read_pi_offset, @@ -132,7 +132,7 @@ class TxExecutionConstrainingTestHelper : public ::testing::Test { { C::tx_sel, 1 }, { C::tx_phase_value, static_cast(TransactionPhase::NR_L2_TO_L1_MESSAGE) }, { C::tx_is_padded, 1 }, - { C::tx_sel_non_revertible_append_l2_l1_msg, 1 }, + { C::tx_sel_append_l2_l1_msg, 1 }, { C::tx_read_pi_start_offset, AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX }, @@ -187,7 +187,7 @@ class TxExecutionConstrainingTestHelper : public ::testing::Test { { C::tx_phase_value, static_cast(TransactionPhase::R_NULLIFIER_INSERTION) }, { C::tx_is_padded, 1 }, { C::tx_is_tree_insert_phase, 1 }, - { C::tx_sel_revertible_append_nullifier, 1 }, + { C::tx_sel_append_nullifier, 1 }, { C::tx_is_revertible, 1 }, { C::tx_read_pi_start_offset, AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_NULLIFIERS_ROW_IDX }, @@ -206,7 +206,7 @@ class TxExecutionConstrainingTestHelper : public ::testing::Test { { C::tx_phase_value, static_cast(TransactionPhase::R_NOTE_INSERTION) }, { C::tx_is_padded, 1 }, { C::tx_is_tree_insert_phase, 1 }, - { C::tx_sel_revertible_append_note_hash, 1 }, + { C::tx_sel_append_note_hash, 1 }, { C::tx_is_revertible, 1 }, { C::tx_read_pi_start_offset, AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_NOTE_HASHES_ROW_IDX }, @@ -224,7 +224,7 @@ class TxExecutionConstrainingTestHelper : public ::testing::Test { { C::tx_sel, 1 }, { C::tx_phase_value, static_cast(TransactionPhase::R_L2_TO_L1_MESSAGE) }, { C::tx_is_padded, 1 }, - { C::tx_sel_revertible_append_l2_l1_msg, 1 }, + { C::tx_sel_append_l2_l1_msg, 1 }, { C::tx_is_revertible, 1 }, { C::tx_read_pi_start_offset, AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX }, @@ -423,7 +423,7 @@ TEST_F(TxExecutionConstrainingTestHelper, JumpOnRevert) trace.set(7, { { { C::tx_is_padded, 0 }, - { C::tx_sel_revertible_append_l2_l1_msg, 0 }, // switch off for testing + { C::tx_sel_append_l2_l1_msg, 0 }, // switch off for testing { C::tx_remaining_phase_counter, 1 }, { C::tx_remaining_phase_inv, 1 }, { C::tx_is_revertible, 1 }, @@ -488,7 +488,7 @@ TEST(TxExecutionConstrainingTest, WriteTreeValue) { C::tx_read_pi_offset, AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX }, { C::tx_write_pi_offset, AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX }, - { C::tx_sel_non_revertible_append_l2_l1_msg, 1 }, + { C::tx_sel_append_l2_l1_msg, 1 }, { C::tx_l2_l1_msg_content, test_public_inputs.previous_non_revertible_accumulated_data.l2_to_l1_msgs[0].message.content }, { C::tx_l2_l1_msg_recipient, @@ -546,7 +546,7 @@ TEST(TxExecutionConstrainingTest, WriteTreeValue) { C::tx_read_pi_offset, AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX }, { C::tx_write_pi_offset, AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX + 1 }, - { C::tx_sel_revertible_append_l2_l1_msg, 1 }, + { C::tx_sel_append_l2_l1_msg, 1 }, { C::tx_l2_l1_msg_content, test_public_inputs.previous_revertible_accumulated_data.l2_to_l1_msgs[0].message.content }, { C::tx_l2_l1_msg_recipient, diff --git a/barretenberg/cpp/src/barretenberg/vm2/generated/columns.hpp b/barretenberg/cpp/src/barretenberg/vm2/generated/columns.hpp index c5f65f828e6a..3d1bfac00e40 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/generated/columns.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/generated/columns.hpp @@ -7,8 +7,8 @@ namespace bb::avm2 { // clang-format off -#define AVM2_PRECOMPUTED_ENTITIES_E(e) e precomputed_addressing_gas, e precomputed_bitwise_input_a, e precomputed_bitwise_input_b, e precomputed_bitwise_output_and, e precomputed_bitwise_output_or, e precomputed_bitwise_output_xor, e precomputed_dyn_gas_id, e precomputed_envvar_pi_row_idx, e precomputed_exec_opcode, e precomputed_exec_opcode_base_da_gas, e precomputed_exec_opcode_dynamic_da_gas, e precomputed_exec_opcode_dynamic_l2_gas, e precomputed_exec_opcode_opcode_gas, e precomputed_expected_tag_reg_0_, e precomputed_expected_tag_reg_1_, e precomputed_expected_tag_reg_2_, e precomputed_expected_tag_reg_3_, e precomputed_expected_tag_reg_4_, e precomputed_expected_tag_reg_5_, e precomputed_first_row, e precomputed_idx, e precomputed_instr_size, e precomputed_invalid_envvar_enum, e precomputed_is_address, e precomputed_is_class_id, e precomputed_is_cleanup, e precomputed_is_collect_fee, e precomputed_is_dagasleft, e precomputed_is_deployer, e precomputed_is_init_hash, e precomputed_is_isstaticcall, e precomputed_is_l2gasleft, e precomputed_is_public_call_request, e precomputed_is_revertible, e precomputed_is_sender, e precomputed_is_teardown, e precomputed_is_transactionfee, e precomputed_is_tree_padding, e precomputed_is_valid_member_enum, e precomputed_keccak_round_constant, e precomputed_next_phase_on_revert, e precomputed_opcode_out_of_range, e precomputed_out_tag, e precomputed_p_decomposition_limb, e precomputed_p_decomposition_limb_index, e precomputed_p_decomposition_radix, e precomputed_power_of_2, e precomputed_read_pi_length_offset, e precomputed_read_pi_start_offset, e precomputed_rw_reg_0_, e precomputed_rw_reg_1_, e precomputed_rw_reg_2_, e precomputed_rw_reg_3_, e precomputed_rw_reg_4_, e precomputed_rw_reg_5_, e precomputed_sel_addressing_gas, e precomputed_sel_envvar_pi_lookup_col0, e precomputed_sel_envvar_pi_lookup_col1, e precomputed_sel_exec_spec, e precomputed_sel_has_tag, e precomputed_sel_keccak, e precomputed_sel_mem_op_reg_0_, e precomputed_sel_mem_op_reg_1_, e precomputed_sel_mem_op_reg_2_, e precomputed_sel_mem_op_reg_3_, e precomputed_sel_mem_op_reg_4_, e precomputed_sel_mem_op_reg_5_, e precomputed_sel_mem_tag_out_of_range, e precomputed_sel_non_revertible_append_l2_l1_msg, e precomputed_sel_non_revertible_append_note_hash, e precomputed_sel_non_revertible_append_nullifier, e precomputed_sel_op_dc_0, e precomputed_sel_op_dc_1, e precomputed_sel_op_dc_10, e precomputed_sel_op_dc_11, e precomputed_sel_op_dc_12, e precomputed_sel_op_dc_13, e precomputed_sel_op_dc_14, e precomputed_sel_op_dc_15, e precomputed_sel_op_dc_16, e precomputed_sel_op_dc_2, e precomputed_sel_op_dc_3, e precomputed_sel_op_dc_4, e precomputed_sel_op_dc_5, e precomputed_sel_op_dc_6, e precomputed_sel_op_dc_7, e precomputed_sel_op_dc_8, e precomputed_sel_op_dc_9, e precomputed_sel_op_is_address_0_, e precomputed_sel_op_is_address_1_, e precomputed_sel_op_is_address_2_, e precomputed_sel_op_is_address_3_, e precomputed_sel_op_is_address_4_, e precomputed_sel_op_is_address_5_, e precomputed_sel_op_is_address_6_, e precomputed_sel_p_decomposition, e precomputed_sel_phase, e precomputed_sel_range_16, e precomputed_sel_range_8, e precomputed_sel_revertible_append_l2_l1_msg, e precomputed_sel_revertible_append_note_hash, e precomputed_sel_revertible_append_nullifier, e precomputed_sel_sha256_compression, e precomputed_sel_tag_check_reg_0_, e precomputed_sel_tag_check_reg_1_, e precomputed_sel_tag_check_reg_2_, e precomputed_sel_tag_check_reg_3_, e precomputed_sel_tag_check_reg_4_, e precomputed_sel_tag_check_reg_5_, e precomputed_sel_tag_is_op2, e precomputed_sel_tag_parameters, e precomputed_sel_to_radix_p_limb_counts, e precomputed_sha256_compression_round_constant, e precomputed_subtrace_id, e precomputed_subtrace_operation_id, e precomputed_tag_byte_length, e precomputed_tag_max_bits, e precomputed_tag_max_value, e precomputed_to_radix_num_limbs_for_p, e precomputed_to_radix_safe_limbs, e precomputed_zero, e public_inputs_sel -#define AVM2_WIRE_ENTITIES_E(e) e public_inputs_cols_0_, e public_inputs_cols_1_, e public_inputs_cols_2_, e public_inputs_cols_3_, e address_derivation_address, e address_derivation_address_y, e address_derivation_class_id, e address_derivation_const_four, e address_derivation_const_thirteen, e address_derivation_const_three, e address_derivation_const_two, e address_derivation_deployer_addr, e address_derivation_g1_x, e address_derivation_g1_y, e address_derivation_incoming_viewing_key_x, e address_derivation_incoming_viewing_key_y, e address_derivation_init_hash, e address_derivation_nullifier_key_x, e address_derivation_nullifier_key_y, e address_derivation_outgoing_viewing_key_x, e address_derivation_outgoing_viewing_key_y, e address_derivation_partial_address, e address_derivation_partial_address_domain_separator, e address_derivation_preaddress, e address_derivation_preaddress_domain_separator, e address_derivation_preaddress_public_key_x, e address_derivation_preaddress_public_key_y, e address_derivation_public_keys_hash, e address_derivation_public_keys_hash_domain_separator, e address_derivation_salt, e address_derivation_salted_init_hash, e address_derivation_salted_init_hash_domain_separator, e address_derivation_sel, e address_derivation_tagging_key_x, e address_derivation_tagging_key_y, e alu_a_hi, e alu_a_hi_bits, e alu_a_lo, e alu_a_lo_bits, e alu_ab_diff_inv, e alu_ab_tags_diff_inv, e alu_b_hi, e alu_b_inv, e alu_b_lo, e alu_c_hi, e alu_cf, e alu_constant_64, e alu_gt_input_a, e alu_gt_input_b, e alu_gt_result_c, e alu_helper1, e alu_ia, e alu_ia_tag, e alu_ib, e alu_ib_tag, e alu_ic, e alu_ic_tag, e alu_max_bits, e alu_max_value, e alu_mid, e alu_mid_bits, e alu_op_id, e alu_sel, e alu_sel_ab_tag_mismatch, e alu_sel_decompose_a, e alu_sel_div_0_err, e alu_sel_div_no_err, e alu_sel_err, e alu_sel_ff_gt, e alu_sel_int_gt, e alu_sel_is_ff, e alu_sel_is_u128, e alu_sel_mul_div_u128, e alu_sel_mul_no_err_non_ff, e alu_sel_op_add, e alu_sel_op_div, e alu_sel_op_eq, e alu_sel_op_fdiv, e alu_sel_op_lt, e alu_sel_op_lte, e alu_sel_op_mul, e alu_sel_op_not, e alu_sel_op_shl, e alu_sel_op_shr, e alu_sel_op_sub, e alu_sel_op_truncate, e alu_sel_shift_ops_no_overflow, e alu_sel_tag_err, e alu_sel_trunc_gte_128, e alu_sel_trunc_lt_128, e alu_sel_trunc_non_trivial, e alu_sel_trunc_trivial, e alu_shift_lo_bits, e alu_tag_ff_diff_inv, e alu_tag_u128_diff_inv, e alu_two_pow_shift_lo_bits, e bc_decomposition_bytes_pc_plus_36, e bc_decomposition_bytes_rem_inv, e bc_decomposition_bytes_rem_min_one_inv, e bc_decomposition_bytes_to_read, e bc_decomposition_last_of_contract, e bc_decomposition_next_packed_pc_min_pc_inv, e bc_decomposition_packed_field, e bc_decomposition_sel_packed, e bc_decomposition_sel_packed_read_0_, e bc_decomposition_sel_packed_read_1_, e bc_decomposition_sel_packed_read_2_, e bc_decomposition_sel_windows_eq_remaining, e bc_decomposition_windows_min_remaining_inv, e bc_hashing_end, e bc_hashing_input_len, e bc_hashing_packed_fields_0, e bc_hashing_packed_fields_1, e bc_hashing_packed_fields_2, e bc_hashing_pc_index, e bc_hashing_pc_index_2, e bc_hashing_sel_not_padding_1, e bc_hashing_sel_not_padding_2, e bc_hashing_size_in_bytes, e bc_retrieval_address, e bc_retrieval_artifact_hash, e bc_retrieval_bytecode_id, e bc_retrieval_current_class_id, e bc_retrieval_error, e bc_retrieval_instance_exists, e bc_retrieval_is_new_class, e bc_retrieval_next_retrieved_bytecodes_tree_root, e bc_retrieval_next_retrieved_bytecodes_tree_size, e bc_retrieval_no_remaining_bytecodes, e bc_retrieval_nullifier_tree_root, e bc_retrieval_prev_retrieved_bytecodes_tree_root, e bc_retrieval_prev_retrieved_bytecodes_tree_size, e bc_retrieval_private_functions_root, e bc_retrieval_public_data_tree_root, e bc_retrieval_remaining_bytecodes_inv, e bc_retrieval_retrieved_bytecodes_merkle_separator, e bc_retrieval_retrieved_bytecodes_tree_height, e bc_retrieval_sel, e bc_retrieval_should_retrieve, e bitwise_ctr_min_one_inv, e bitwise_end, e bitwise_err, e bitwise_ia_byte, e bitwise_ib_byte, e bitwise_ic_byte, e bitwise_output_and, e bitwise_output_or, e bitwise_output_xor, e bitwise_sel_and, e bitwise_sel_compute, e bitwise_sel_get_ctr, e bitwise_sel_or, e bitwise_sel_tag_ff_err, e bitwise_sel_tag_mismatch_err, e bitwise_sel_xor, e bitwise_start_keccak, e bitwise_start_sha256, e bitwise_tag_a, e bitwise_tag_a_inv, e bitwise_tag_ab_diff_inv, e bitwise_tag_b, e bitwise_tag_c, e calldata_end, e calldata_hashing_end, e calldata_hashing_index_1_, e calldata_hashing_index_2_, e calldata_hashing_input_0_, e calldata_hashing_input_1_, e calldata_hashing_input_2_, e calldata_hashing_input_len, e calldata_hashing_sel_end_not_empty, e calldata_hashing_sel_not_padding_1, e calldata_hashing_sel_not_padding_2, e calldata_hashing_sel_not_start, e calldata_value, e class_id_derivation_artifact_hash, e class_id_derivation_class_id, e class_id_derivation_const_four, e class_id_derivation_gen_index_contract_class_id, e class_id_derivation_private_functions_root, e class_id_derivation_public_bytecode_commitment, e class_id_derivation_sel, e context_stack_bytecode_id, e context_stack_context_id, e context_stack_contract_address, e context_stack_entered_context_id, e context_stack_internal_call_id, e context_stack_internal_call_return_id, e context_stack_is_static, e context_stack_msg_sender, e context_stack_next_internal_call_id, e context_stack_next_pc, e context_stack_note_hash_tree_root, e context_stack_note_hash_tree_size, e context_stack_nullifier_tree_root, e context_stack_nullifier_tree_size, e context_stack_num_l2_to_l1_messages, e context_stack_num_note_hashes_emitted, e context_stack_num_nullifiers_emitted, e context_stack_num_public_log_fields, e context_stack_parent_calldata_addr, e context_stack_parent_calldata_size, e context_stack_parent_da_gas_limit, e context_stack_parent_da_gas_used, e context_stack_parent_id, e context_stack_parent_l2_gas_limit, e context_stack_parent_l2_gas_used, e context_stack_public_data_tree_root, e context_stack_public_data_tree_size, e context_stack_sel, e context_stack_written_public_data_slots_tree_root, e context_stack_written_public_data_slots_tree_size, e contract_instance_retrieval_address, e contract_instance_retrieval_address_sub_one, e contract_instance_retrieval_current_class_id, e contract_instance_retrieval_deployer_addr, e contract_instance_retrieval_deployer_protocol_contract_address, e contract_instance_retrieval_derived_address, e contract_instance_retrieval_derived_address_pi_index, e contract_instance_retrieval_exists, e contract_instance_retrieval_incoming_viewing_key_x, e contract_instance_retrieval_incoming_viewing_key_y, e contract_instance_retrieval_init_hash, e contract_instance_retrieval_is_protocol_contract, e contract_instance_retrieval_max_protocol_contracts, e contract_instance_retrieval_nullifier_key_x, e contract_instance_retrieval_nullifier_key_y, e contract_instance_retrieval_nullifier_merkle_separator, e contract_instance_retrieval_nullifier_tree_height, e contract_instance_retrieval_nullifier_tree_root, e contract_instance_retrieval_original_class_id, e contract_instance_retrieval_outgoing_viewing_key_x, e contract_instance_retrieval_outgoing_viewing_key_y, e contract_instance_retrieval_protocol_contract_derived_address_inv, e contract_instance_retrieval_public_data_tree_root, e contract_instance_retrieval_salt, e contract_instance_retrieval_sel, e contract_instance_retrieval_should_check_for_update, e contract_instance_retrieval_should_check_nullifier, e contract_instance_retrieval_siloing_separator, e contract_instance_retrieval_tagging_key_x, e contract_instance_retrieval_tagging_key_y, e data_copy_cd_copy_col_read, e data_copy_clamped_read_index_upper_bound, e data_copy_dst_out_of_range_err, e data_copy_end, e data_copy_is_top_level, e data_copy_mem_size, e data_copy_offset, e data_copy_offset_plus_size, e data_copy_offset_plus_size_is_gt, e data_copy_parent_id_inv, e data_copy_read_addr_plus_one, e data_copy_read_addr_upper_bound, e data_copy_reads_left_inv, e data_copy_sel_cd_copy_start, e data_copy_sel_has_reads, e data_copy_sel_mem_read, e data_copy_sel_mem_write, e data_copy_sel_rd_copy_start, e data_copy_sel_write_count_is_zero, e data_copy_src_addr, e data_copy_src_data_size, e data_copy_src_reads_exceed_mem, e data_copy_start_no_err, e data_copy_tag, e data_copy_value, e data_copy_write_addr_upper_bound, e data_copy_write_count_minus_one_inv, e data_copy_write_count_zero_inv, e ecc_add_mem_dst_addr_0_, e ecc_add_mem_dst_addr_1_, e ecc_add_mem_dst_addr_2_, e ecc_add_mem_err, e ecc_add_mem_execution_clk, e ecc_add_mem_max_mem_addr, e ecc_add_mem_p_is_inf, e ecc_add_mem_p_is_on_curve_eqn, e ecc_add_mem_p_is_on_curve_eqn_inv, e ecc_add_mem_p_x, e ecc_add_mem_p_x_n, e ecc_add_mem_p_y, e ecc_add_mem_p_y_n, e ecc_add_mem_q_is_inf, e ecc_add_mem_q_is_on_curve_eqn, e ecc_add_mem_q_is_on_curve_eqn_inv, e ecc_add_mem_q_x, e ecc_add_mem_q_x_n, e ecc_add_mem_q_y, e ecc_add_mem_q_y_n, e ecc_add_mem_res_is_inf, e ecc_add_mem_res_x, e ecc_add_mem_res_y, e ecc_add_mem_sel, e ecc_add_mem_sel_dst_out_of_range_err, e ecc_add_mem_sel_p_not_on_curve_err, e ecc_add_mem_sel_q_not_on_curve_err, e ecc_add_mem_sel_should_exec, e ecc_add_mem_space_id, e ecc_add_op, e ecc_double_op, e ecc_inv_2_p_y, e ecc_inv_x_diff, e ecc_inv_y_diff, e ecc_lambda, e ecc_p_is_inf, e ecc_p_x, e ecc_p_y, e ecc_q_is_inf, e ecc_q_x, e ecc_q_y, e ecc_r_is_inf, e ecc_r_x, e ecc_r_y, e ecc_result_infinity, e ecc_sel, e ecc_use_computed_result, e ecc_x_match, e ecc_y_match, e emit_public_log_discard, e emit_public_log_end, e emit_public_log_end_log_address_upper_bound, e emit_public_log_error, e emit_public_log_error_too_many_log_fields, e emit_public_log_expected_next_log_fields, e emit_public_log_is_static, e emit_public_log_log_size, e emit_public_log_max_mem_size, e emit_public_log_max_public_logs_payload_length, e emit_public_log_next_num_public_log_fields, e emit_public_log_prev_num_public_log_fields, e emit_public_log_public_inputs_value, e emit_public_log_remaining_rows_inv, e emit_public_log_sel_read_memory, e emit_public_log_tag, e emit_public_log_tag_inv, e emit_public_log_value, e execution_addressing_error_collection_inv, e execution_addressing_gas, e execution_addressing_mode, e execution_base_address_tag, e execution_base_address_tag_diff_inv, e execution_base_address_val, e execution_base_da_gas, e execution_batched_tags_diff_inv, e execution_batched_tags_diff_inv_reg, e execution_da_gas_left, e execution_da_gas_used, e execution_dying_context_diff_inv, e execution_dying_context_id_inv, e execution_dyn_gas_id, e execution_dynamic_da_gas, e execution_dynamic_da_gas_factor, e execution_dynamic_l2_gas, e execution_dynamic_l2_gas_factor, e execution_enqueued_call_end, e execution_envvar_pi_row_idx, e execution_exec_opcode, e execution_expected_tag_reg_0_, e execution_expected_tag_reg_1_, e execution_expected_tag_reg_2_, e execution_expected_tag_reg_3_, e execution_expected_tag_reg_4_, e execution_expected_tag_reg_5_, e execution_has_parent_ctx, e execution_highest_address, e execution_instr_size, e execution_internal_call_return_id_inv, e execution_is_address, e execution_is_da_gas_left_gt_allocated, e execution_is_dagasleft, e execution_is_dying_context, e execution_is_isstaticcall, e execution_is_l2_gas_left_gt_allocated, e execution_is_l2gasleft, e execution_is_parent_id_inv, e execution_is_sender, e execution_is_transactionfee, e execution_l1_to_l2_msg_leaf_in_range, e execution_l1_to_l2_msg_tree_leaf_count, e execution_l2_gas_left, e execution_l2_gas_used, e execution_max_data_writes_reached, e execution_max_eth_address_value, e execution_mem_tag_reg_0_, e execution_mem_tag_reg_1_, e execution_mem_tag_reg_2_, e execution_mem_tag_reg_3_, e execution_mem_tag_reg_4_, e execution_mem_tag_reg_5_, e execution_nested_failure, e execution_nested_return, e execution_next_pc, e execution_note_hash_leaf_in_range, e execution_note_hash_tree_leaf_count, e execution_note_hash_tree_root, e execution_note_hash_tree_size, e execution_nullifier_merkle_separator, e execution_nullifier_pi_offset, e execution_nullifier_siloing_separator, e execution_nullifier_tree_height, e execution_nullifier_tree_root, e execution_nullifier_tree_size, e execution_num_l2_to_l1_messages, e execution_num_note_hashes_emitted, e execution_num_nullifiers_emitted, e execution_num_p_limbs, e execution_num_public_log_fields, e execution_num_relative_operands_inv, e execution_op_0_, e execution_op_1_, e execution_op_2_, e execution_op_3_, e execution_op_4_, e execution_op_5_, e execution_op_6_, e execution_op_after_relative_0_, e execution_op_after_relative_1_, e execution_op_after_relative_2_, e execution_op_after_relative_3_, e execution_op_after_relative_4_, e execution_op_after_relative_5_, e execution_op_after_relative_6_, e execution_opcode_gas, e execution_out_of_gas_da, e execution_out_of_gas_l2, e execution_public_data_tree_root, e execution_public_data_tree_size, e execution_public_inputs_index, e execution_register_0_, e execution_register_1_, e execution_register_2_, e execution_register_3_, e execution_register_4_, e execution_register_5_, e execution_remaining_data_writes_inv, e execution_remaining_l2_to_l1_msgs_inv, e execution_remaining_note_hashes_inv, e execution_remaining_nullifiers_inv, e execution_retrieved_bytecodes_tree_root, e execution_retrieved_bytecodes_tree_size, e execution_rop_0_, e execution_rop_1_, e execution_rop_2_, e execution_rop_3_, e execution_rop_4_, e execution_rop_5_, e execution_rop_6_, e execution_rop_tag_0_, e execution_rop_tag_1_, e execution_rop_tag_2_, e execution_rop_tag_3_, e execution_rop_tag_4_, e execution_rop_tag_5_, e execution_rop_tag_6_, e execution_rw_reg_0_, e execution_rw_reg_1_, e execution_rw_reg_2_, e execution_rw_reg_3_, e execution_rw_reg_4_, e execution_rw_reg_5_, e execution_sel_addressing_error, e execution_sel_apply_indirection_0_, e execution_sel_apply_indirection_1_, e execution_sel_apply_indirection_2_, e execution_sel_apply_indirection_3_, e execution_sel_apply_indirection_4_, e execution_sel_apply_indirection_5_, e execution_sel_apply_indirection_6_, e execution_sel_base_address_failure, e execution_sel_bytecode_retrieval_failure, e execution_sel_bytecode_retrieval_success, e execution_sel_check_gas, e execution_sel_do_base_check, e execution_sel_enter_call, e execution_sel_envvar_pi_lookup_col0, e execution_sel_envvar_pi_lookup_col1, e execution_sel_error, e execution_sel_exec_dispatch_alu, e execution_sel_exec_dispatch_bitwise, e execution_sel_exec_dispatch_calldata_copy, e execution_sel_exec_dispatch_cast, e execution_sel_exec_dispatch_ecc_add, e execution_sel_exec_dispatch_emit_public_log, e execution_sel_exec_dispatch_execution, e execution_sel_exec_dispatch_get_contract_instance, e execution_sel_exec_dispatch_keccakf1600, e execution_sel_exec_dispatch_poseidon2_perm, e execution_sel_exec_dispatch_returndata_copy, e execution_sel_exec_dispatch_set, e execution_sel_exec_dispatch_sha256_compression, e execution_sel_exec_dispatch_to_radix, e execution_sel_execute_call, e execution_sel_execute_debug_log, e execution_sel_execute_emit_notehash, e execution_sel_execute_emit_nullifier, e execution_sel_execute_get_env_var, e execution_sel_execute_internal_call, e execution_sel_execute_internal_return, e execution_sel_execute_jump, e execution_sel_execute_jumpi, e execution_sel_execute_l1_to_l2_message_exists, e execution_sel_execute_mov, e execution_sel_execute_notehash_exists, e execution_sel_execute_nullifier_exists, e execution_sel_execute_opcode, e execution_sel_execute_return, e execution_sel_execute_returndata_size, e execution_sel_execute_revert, e execution_sel_execute_send_l2_to_l1_msg, e execution_sel_execute_sload, e execution_sel_execute_sstore, e execution_sel_execute_static_call, e execution_sel_execute_success_copy, e execution_sel_exit_call, e execution_sel_failure, e execution_sel_gas_bitwise, e execution_sel_gas_calldata_copy, e execution_sel_gas_emit_public_log, e execution_sel_gas_returndata_copy, e execution_sel_gas_sstore, e execution_sel_gas_to_radix, e execution_sel_instruction_fetching_failure, e execution_sel_instruction_fetching_success, e execution_sel_l2_to_l1_msg_limit_error, e execution_sel_lookup_num_p_limbs, e execution_sel_mem_op_reg_0_, e execution_sel_mem_op_reg_1_, e execution_sel_mem_op_reg_2_, e execution_sel_mem_op_reg_3_, e execution_sel_mem_op_reg_4_, e execution_sel_mem_op_reg_5_, e execution_sel_op_do_overflow_check_0_, e execution_sel_op_do_overflow_check_1_, e execution_sel_op_do_overflow_check_2_, e execution_sel_op_do_overflow_check_3_, e execution_sel_op_do_overflow_check_4_, e execution_sel_op_do_overflow_check_5_, e execution_sel_op_do_overflow_check_6_, e execution_sel_op_is_address_0_, e execution_sel_op_is_address_1_, e execution_sel_op_is_address_2_, e execution_sel_op_is_address_3_, e execution_sel_op_is_address_4_, e execution_sel_op_is_address_5_, e execution_sel_op_is_address_6_, e execution_sel_op_is_indirect_wire_0_, e execution_sel_op_is_indirect_wire_1_, e execution_sel_op_is_indirect_wire_2_, e execution_sel_op_is_indirect_wire_3_, e execution_sel_op_is_indirect_wire_4_, e execution_sel_op_is_indirect_wire_5_, e execution_sel_op_is_indirect_wire_6_, e execution_sel_op_is_indirect_wire_7_, e execution_sel_op_is_relative_wire_0_, e execution_sel_op_is_relative_wire_1_, e execution_sel_op_is_relative_wire_2_, e execution_sel_op_is_relative_wire_3_, e execution_sel_op_is_relative_wire_4_, e execution_sel_op_is_relative_wire_5_, e execution_sel_op_is_relative_wire_6_, e execution_sel_op_is_relative_wire_7_, e execution_sel_op_reg_effective_0_, e execution_sel_op_reg_effective_1_, e execution_sel_op_reg_effective_2_, e execution_sel_op_reg_effective_3_, e execution_sel_op_reg_effective_4_, e execution_sel_op_reg_effective_5_, e execution_sel_opcode_error, e execution_sel_out_of_gas, e execution_sel_radix_gt_256, e execution_sel_reached_max_note_hashes, e execution_sel_reached_max_nullifiers, e execution_sel_read_registers, e execution_sel_read_unwind_call_stack, e execution_sel_register_read_error, e execution_sel_relative_overflow_0_, e execution_sel_relative_overflow_1_, e execution_sel_relative_overflow_2_, e execution_sel_relative_overflow_3_, e execution_sel_relative_overflow_4_, e execution_sel_relative_overflow_5_, e execution_sel_relative_overflow_6_, e execution_sel_some_final_check_failed, e execution_sel_tag_check_reg_0_, e execution_sel_tag_check_reg_1_, e execution_sel_tag_check_reg_2_, e execution_sel_tag_check_reg_3_, e execution_sel_tag_check_reg_4_, e execution_sel_tag_check_reg_5_, e execution_sel_too_large_recipient_error, e execution_sel_use_num_limbs, e execution_sel_write_l2_to_l1_msg, e execution_sel_write_note_hash, e execution_sel_write_nullifier, e execution_sel_write_public_data, e execution_sel_write_registers, e execution_subtrace_id, e execution_subtrace_operation_id, e execution_total_gas_da, e execution_total_gas_l2, e execution_two_five_six, e execution_value_from_pi, e execution_written_public_data_slots_tree_root, e execution_written_public_data_slots_tree_size, e execution_written_slots_merkle_separator, e execution_written_slots_tree_height, e execution_written_slots_tree_siloing_separator, e ff_gt_a, e ff_gt_b, e ff_gt_borrow, e ff_gt_constant_128, e ff_gt_end, e ff_gt_p_a_borrow, e ff_gt_p_b_borrow, e ff_gt_res_hi, e ff_gt_res_lo, e ff_gt_result, e get_contract_instance_clk, e get_contract_instance_contract_address, e get_contract_instance_dst_offset, e get_contract_instance_dst_offset_diff_max_inv, e get_contract_instance_exists_tag, e get_contract_instance_instance_exists, e get_contract_instance_is_class_id, e get_contract_instance_is_deployer, e get_contract_instance_is_init_hash, e get_contract_instance_is_valid_member_enum, e get_contract_instance_is_valid_writes_in_bounds, e get_contract_instance_member_enum, e get_contract_instance_member_tag, e get_contract_instance_member_write_offset, e get_contract_instance_nullifier_tree_root, e get_contract_instance_public_data_tree_root, e get_contract_instance_retrieved_class_id, e get_contract_instance_retrieved_deployer_addr, e get_contract_instance_retrieved_init_hash, e get_contract_instance_sel, e get_contract_instance_sel_error, e get_contract_instance_selected_member, e get_contract_instance_space_id, e gt_abs_diff, e gt_input_a, e gt_input_b, e gt_num_bits, e gt_res, e gt_sel, e gt_sel_addressing, e gt_sel_alu, e gt_sel_gas, e gt_sel_others, e gt_sel_sha256, e indexed_tree_check_address, e indexed_tree_check_const_three, e indexed_tree_check_discard, e indexed_tree_check_exists, e indexed_tree_check_intermediate_root, e indexed_tree_check_low_leaf_hash, e indexed_tree_check_low_leaf_index, e indexed_tree_check_low_leaf_next_index, e indexed_tree_check_low_leaf_next_value, e indexed_tree_check_low_leaf_value, e indexed_tree_check_merkle_hash_separator, e indexed_tree_check_new_leaf_hash, e indexed_tree_check_next_value_inv, e indexed_tree_check_next_value_is_nonzero, e indexed_tree_check_not_exists, e indexed_tree_check_public_inputs_index, e indexed_tree_check_root, e indexed_tree_check_sel, e indexed_tree_check_sel_insert, e indexed_tree_check_sel_silo, e indexed_tree_check_sel_write_to_public_inputs, e indexed_tree_check_siloed_value, e indexed_tree_check_siloing_separator, e indexed_tree_check_tree_height, e indexed_tree_check_tree_size_after_write, e indexed_tree_check_tree_size_before_write, e indexed_tree_check_updated_low_leaf_hash, e indexed_tree_check_updated_low_leaf_next_index, e indexed_tree_check_updated_low_leaf_next_value, e indexed_tree_check_value, e indexed_tree_check_value_low_leaf_value_diff_inv, e indexed_tree_check_write, e indexed_tree_check_write_root, e instr_fetching_addressing_mode, e instr_fetching_bd0, e instr_fetching_bd1, e instr_fetching_bd10, e instr_fetching_bd11, e instr_fetching_bd12, e instr_fetching_bd13, e instr_fetching_bd14, e instr_fetching_bd15, e instr_fetching_bd16, e instr_fetching_bd17, e instr_fetching_bd18, e instr_fetching_bd19, e instr_fetching_bd2, e instr_fetching_bd20, e instr_fetching_bd21, e instr_fetching_bd22, e instr_fetching_bd23, e instr_fetching_bd24, e instr_fetching_bd25, e instr_fetching_bd26, e instr_fetching_bd27, e instr_fetching_bd28, e instr_fetching_bd29, e instr_fetching_bd3, e instr_fetching_bd30, e instr_fetching_bd31, e instr_fetching_bd32, e instr_fetching_bd33, e instr_fetching_bd34, e instr_fetching_bd35, e instr_fetching_bd36, e instr_fetching_bd4, e instr_fetching_bd5, e instr_fetching_bd6, e instr_fetching_bd7, e instr_fetching_bd8, e instr_fetching_bd9, e instr_fetching_bytecode_id, e instr_fetching_bytecode_size, e instr_fetching_bytes_to_read, e instr_fetching_exec_opcode, e instr_fetching_instr_abs_diff, e instr_fetching_instr_out_of_range, e instr_fetching_instr_size, e instr_fetching_op1, e instr_fetching_op2, e instr_fetching_op3, e instr_fetching_op4, e instr_fetching_op5, e instr_fetching_op6, e instr_fetching_op7, e instr_fetching_opcode_out_of_range, e instr_fetching_pc, e instr_fetching_pc_abs_diff, e instr_fetching_pc_out_of_range, e instr_fetching_pc_size_in_bits, e instr_fetching_sel, e instr_fetching_sel_has_tag, e instr_fetching_sel_op_dc_0, e instr_fetching_sel_op_dc_1, e instr_fetching_sel_op_dc_10, e instr_fetching_sel_op_dc_11, e instr_fetching_sel_op_dc_12, e instr_fetching_sel_op_dc_13, e instr_fetching_sel_op_dc_14, e instr_fetching_sel_op_dc_15, e instr_fetching_sel_op_dc_16, e instr_fetching_sel_op_dc_2, e instr_fetching_sel_op_dc_3, e instr_fetching_sel_op_dc_4, e instr_fetching_sel_op_dc_5, e instr_fetching_sel_op_dc_6, e instr_fetching_sel_op_dc_7, e instr_fetching_sel_op_dc_8, e instr_fetching_sel_op_dc_9, e instr_fetching_sel_parsing_err, e instr_fetching_sel_pc_in_range, e instr_fetching_sel_tag_is_op2, e instr_fetching_tag_out_of_range, e instr_fetching_tag_value, e internal_call_stack_call_id, e internal_call_stack_context_id, e internal_call_stack_entered_call_id, e internal_call_stack_return_call_id, e internal_call_stack_return_pc, e internal_call_stack_sel, e keccak_memory_ctr_end, e keccak_memory_end, e keccak_memory_single_tag_error, e keccak_memory_state_size_min_ctr_inv, e keccak_memory_tag, e keccak_memory_tag_min_u64_inv, e keccak_memory_val_24_, e keccakf1600_bitwise_and_op_id, e keccakf1600_bitwise_xor_op_id, e keccakf1600_dst_out_of_range_error, e keccakf1600_end, e keccakf1600_error, e keccakf1600_highest_slice_address, e keccakf1600_rot_64_min_len_01, e keccakf1600_rot_64_min_len_03, e keccakf1600_rot_64_min_len_11, e keccakf1600_rot_64_min_len_13, e keccakf1600_rot_64_min_len_20, e keccakf1600_rot_64_min_len_22, e keccakf1600_rot_64_min_len_24, e keccakf1600_rot_64_min_len_31, e keccakf1600_rot_64_min_len_34, e keccakf1600_rot_64_min_len_42, e keccakf1600_rot_len_02, e keccakf1600_rot_len_04, e keccakf1600_rot_len_10, e keccakf1600_rot_len_12, e keccakf1600_rot_len_14, e keccakf1600_rot_len_21, e keccakf1600_rot_len_23, e keccakf1600_rot_len_30, e keccakf1600_rot_len_32, e keccakf1600_rot_len_33, e keccakf1600_rot_len_40, e keccakf1600_rot_len_41, e keccakf1600_rot_len_43, e keccakf1600_rot_len_44, e keccakf1600_round_cst, e keccakf1600_sel_slice_read, e keccakf1600_sel_slice_write, e keccakf1600_src_addr, e keccakf1600_src_out_of_range_error, e keccakf1600_state_chi_00, e keccakf1600_state_chi_01, e keccakf1600_state_chi_02, e keccakf1600_state_chi_03, e keccakf1600_state_chi_04, e keccakf1600_state_chi_10, e keccakf1600_state_chi_11, e keccakf1600_state_chi_12, e keccakf1600_state_chi_13, e keccakf1600_state_chi_14, e keccakf1600_state_chi_20, e keccakf1600_state_chi_21, e keccakf1600_state_chi_22, e keccakf1600_state_chi_23, e keccakf1600_state_chi_24, e keccakf1600_state_chi_30, e keccakf1600_state_chi_31, e keccakf1600_state_chi_32, e keccakf1600_state_chi_33, e keccakf1600_state_chi_34, e keccakf1600_state_chi_40, e keccakf1600_state_chi_41, e keccakf1600_state_chi_42, e keccakf1600_state_chi_43, e keccakf1600_state_chi_44, e keccakf1600_state_iota_00, e keccakf1600_state_pi_and_00, e keccakf1600_state_pi_and_01, e keccakf1600_state_pi_and_02, e keccakf1600_state_pi_and_03, e keccakf1600_state_pi_and_04, e keccakf1600_state_pi_and_10, e keccakf1600_state_pi_and_11, e keccakf1600_state_pi_and_12, e keccakf1600_state_pi_and_13, e keccakf1600_state_pi_and_14, e keccakf1600_state_pi_and_20, e keccakf1600_state_pi_and_21, e keccakf1600_state_pi_and_22, e keccakf1600_state_pi_and_23, e keccakf1600_state_pi_and_24, e keccakf1600_state_pi_and_30, e keccakf1600_state_pi_and_31, e keccakf1600_state_pi_and_32, e keccakf1600_state_pi_and_33, e keccakf1600_state_pi_and_34, e keccakf1600_state_pi_and_40, e keccakf1600_state_pi_and_41, e keccakf1600_state_pi_and_42, e keccakf1600_state_pi_and_43, e keccakf1600_state_pi_and_44, e keccakf1600_state_pi_not_00, e keccakf1600_state_pi_not_01, e keccakf1600_state_pi_not_02, e keccakf1600_state_pi_not_03, e keccakf1600_state_pi_not_04, e keccakf1600_state_pi_not_10, e keccakf1600_state_pi_not_11, e keccakf1600_state_pi_not_12, e keccakf1600_state_pi_not_13, e keccakf1600_state_pi_not_14, e keccakf1600_state_pi_not_20, e keccakf1600_state_pi_not_21, e keccakf1600_state_pi_not_22, e keccakf1600_state_pi_not_23, e keccakf1600_state_pi_not_24, e keccakf1600_state_pi_not_30, e keccakf1600_state_pi_not_31, e keccakf1600_state_pi_not_32, e keccakf1600_state_pi_not_33, e keccakf1600_state_pi_not_34, e keccakf1600_state_pi_not_40, e keccakf1600_state_pi_not_41, e keccakf1600_state_pi_not_42, e keccakf1600_state_pi_not_43, e keccakf1600_state_pi_not_44, e keccakf1600_state_rho_01, e keccakf1600_state_rho_02, e keccakf1600_state_rho_03, e keccakf1600_state_rho_04, e keccakf1600_state_rho_10, e keccakf1600_state_rho_11, e keccakf1600_state_rho_12, e keccakf1600_state_rho_13, e keccakf1600_state_rho_14, e keccakf1600_state_rho_20, e keccakf1600_state_rho_21, e keccakf1600_state_rho_22, e keccakf1600_state_rho_23, e keccakf1600_state_rho_24, e keccakf1600_state_rho_30, e keccakf1600_state_rho_31, e keccakf1600_state_rho_32, e keccakf1600_state_rho_33, e keccakf1600_state_rho_34, e keccakf1600_state_rho_40, e keccakf1600_state_rho_41, e keccakf1600_state_rho_42, e keccakf1600_state_rho_43, e keccakf1600_state_rho_44, e keccakf1600_state_theta_00, e keccakf1600_state_theta_01, e keccakf1600_state_theta_02, e keccakf1600_state_theta_03, e keccakf1600_state_theta_04, e keccakf1600_state_theta_10, e keccakf1600_state_theta_11, e keccakf1600_state_theta_12, e keccakf1600_state_theta_13, e keccakf1600_state_theta_14, e keccakf1600_state_theta_20, e keccakf1600_state_theta_21, e keccakf1600_state_theta_22, e keccakf1600_state_theta_23, e keccakf1600_state_theta_24, e keccakf1600_state_theta_30, e keccakf1600_state_theta_31, e keccakf1600_state_theta_32, e keccakf1600_state_theta_33, e keccakf1600_state_theta_34, e keccakf1600_state_theta_40, e keccakf1600_state_theta_41, e keccakf1600_state_theta_42, e keccakf1600_state_theta_43, e keccakf1600_state_theta_44, e keccakf1600_state_theta_hi_02, e keccakf1600_state_theta_hi_04, e keccakf1600_state_theta_hi_10, e keccakf1600_state_theta_hi_12, e keccakf1600_state_theta_hi_14, e keccakf1600_state_theta_hi_21, e keccakf1600_state_theta_hi_23, e keccakf1600_state_theta_hi_30, e keccakf1600_state_theta_hi_32, e keccakf1600_state_theta_hi_33, e keccakf1600_state_theta_hi_40, e keccakf1600_state_theta_hi_41, e keccakf1600_state_theta_hi_43, e keccakf1600_state_theta_hi_44, e keccakf1600_state_theta_low_01, e keccakf1600_state_theta_low_03, e keccakf1600_state_theta_low_11, e keccakf1600_state_theta_low_13, e keccakf1600_state_theta_low_20, e keccakf1600_state_theta_low_22, e keccakf1600_state_theta_low_24, e keccakf1600_state_theta_low_31, e keccakf1600_state_theta_low_34, e keccakf1600_state_theta_low_42, e keccakf1600_tag_error, e keccakf1600_tag_u64, e keccakf1600_theta_combined_xor_0, e keccakf1600_theta_combined_xor_1, e keccakf1600_theta_combined_xor_2, e keccakf1600_theta_combined_xor_3, e keccakf1600_theta_combined_xor_4, e keccakf1600_theta_xor_01, e keccakf1600_theta_xor_02, e keccakf1600_theta_xor_03, e keccakf1600_theta_xor_11, e keccakf1600_theta_xor_12, e keccakf1600_theta_xor_13, e keccakf1600_theta_xor_21, e keccakf1600_theta_xor_22, e keccakf1600_theta_xor_23, e keccakf1600_theta_xor_31, e keccakf1600_theta_xor_32, e keccakf1600_theta_xor_33, e keccakf1600_theta_xor_41, e keccakf1600_theta_xor_42, e keccakf1600_theta_xor_43, e keccakf1600_theta_xor_row_0, e keccakf1600_theta_xor_row_1, e keccakf1600_theta_xor_row_2, e keccakf1600_theta_xor_row_3, e keccakf1600_theta_xor_row_4, e keccakf1600_theta_xor_row_msb_0, e keccakf1600_theta_xor_row_msb_1, e keccakf1600_theta_xor_row_msb_2, e keccakf1600_theta_xor_row_msb_3, e keccakf1600_theta_xor_row_msb_4, e keccakf1600_theta_xor_row_rotl1_0, e keccakf1600_theta_xor_row_rotl1_1, e keccakf1600_theta_xor_row_rotl1_2, e keccakf1600_theta_xor_row_rotl1_3, e keccakf1600_theta_xor_row_rotl1_4, e l1_to_l2_message_tree_check_exists, e l1_to_l2_message_tree_check_l1_to_l2_message_tree_height, e l1_to_l2_message_tree_check_leaf_index, e l1_to_l2_message_tree_check_leaf_value, e l1_to_l2_message_tree_check_leaf_value_msg_hash_diff_inv, e l1_to_l2_message_tree_check_merkle_hash_separator, e l1_to_l2_message_tree_check_msg_hash, e l1_to_l2_message_tree_check_root, e l1_to_l2_message_tree_check_sel, e memory_diff, e memory_glob_addr_diff_inv, e memory_last_access, e memory_limb_0_, e memory_limb_1_, e memory_limb_2_, e memory_max_bits, e memory_sel_addressing_base, e memory_sel_addressing_indirect_0_, e memory_sel_addressing_indirect_1_, e memory_sel_addressing_indirect_2_, e memory_sel_addressing_indirect_3_, e memory_sel_addressing_indirect_4_, e memory_sel_addressing_indirect_5_, e memory_sel_addressing_indirect_6_, e memory_sel_data_copy_read, e memory_sel_data_copy_write, e memory_sel_ecc_write_0_, e memory_sel_ecc_write_1_, e memory_sel_ecc_write_2_, e memory_sel_get_contract_instance_exists_write, e memory_sel_get_contract_instance_member_write, e memory_sel_keccak, e memory_sel_poseidon2_read_0_, e memory_sel_poseidon2_read_1_, e memory_sel_poseidon2_read_2_, e memory_sel_poseidon2_read_3_, e memory_sel_poseidon2_write_0_, e memory_sel_poseidon2_write_1_, e memory_sel_poseidon2_write_2_, e memory_sel_poseidon2_write_3_, e memory_sel_public_log_read, e memory_sel_register_op_0_, e memory_sel_register_op_1_, e memory_sel_register_op_2_, e memory_sel_register_op_3_, e memory_sel_register_op_4_, e memory_sel_register_op_5_, e memory_sel_rng_chk, e memory_sel_rng_write, e memory_sel_sha256_op_0_, e memory_sel_sha256_op_1_, e memory_sel_sha256_op_2_, e memory_sel_sha256_op_3_, e memory_sel_sha256_op_4_, e memory_sel_sha256_op_5_, e memory_sel_sha256_op_6_, e memory_sel_sha256_op_7_, e memory_sel_sha256_read, e memory_sel_tag_is_ff, e memory_sel_to_radix_write, e memory_tag_ff_diff_inv, e merkle_check_const_three, e merkle_check_end, e merkle_check_index_is_even, e merkle_check_path_len_min_one_inv, e merkle_check_read_left_node, e merkle_check_read_output_hash, e merkle_check_read_right_node, e merkle_check_sibling, e merkle_check_write_left_node, e merkle_check_write_output_hash, e merkle_check_write_right_node, e note_hash_tree_check_address, e note_hash_tree_check_const_three, e note_hash_tree_check_discard, e note_hash_tree_check_exists, e note_hash_tree_check_first_nullifier, e note_hash_tree_check_first_nullifier_pi_index, e note_hash_tree_check_leaf_index, e note_hash_tree_check_merkle_hash_separator, e note_hash_tree_check_next_leaf_value, e note_hash_tree_check_next_root, e note_hash_tree_check_nonce, e note_hash_tree_check_nonce_separator, e note_hash_tree_check_note_hash, e note_hash_tree_check_note_hash_index, e note_hash_tree_check_note_hash_tree_height, e note_hash_tree_check_prev_leaf_value, e note_hash_tree_check_prev_leaf_value_unique_note_hash_diff_inv, e note_hash_tree_check_prev_root, e note_hash_tree_check_public_inputs_index, e note_hash_tree_check_sel, e note_hash_tree_check_sel_silo, e note_hash_tree_check_sel_unique, e note_hash_tree_check_sel_write_to_public_inputs, e note_hash_tree_check_siloed_note_hash, e note_hash_tree_check_siloing_separator, e note_hash_tree_check_unique_note_hash, e note_hash_tree_check_unique_note_hash_separator, e note_hash_tree_check_write, e poseidon2_hash_b_0, e poseidon2_hash_b_1, e poseidon2_hash_b_2, e poseidon2_hash_b_3, e poseidon2_hash_end, e poseidon2_hash_input_len, e poseidon2_hash_num_perm_rounds_rem_min_one_inv, e poseidon2_hash_padding, e poseidon2_perm_B_10_0, e poseidon2_perm_B_10_1, e poseidon2_perm_B_10_2, e poseidon2_perm_B_10_3, e poseidon2_perm_B_11_0, e poseidon2_perm_B_11_1, e poseidon2_perm_B_11_2, e poseidon2_perm_B_11_3, e poseidon2_perm_B_12_0, e poseidon2_perm_B_12_1, e poseidon2_perm_B_12_2, e poseidon2_perm_B_12_3, e poseidon2_perm_B_13_0, e poseidon2_perm_B_13_1, e poseidon2_perm_B_13_2, e poseidon2_perm_B_13_3, e poseidon2_perm_B_14_0, e poseidon2_perm_B_14_1, e poseidon2_perm_B_14_2, e poseidon2_perm_B_14_3, e poseidon2_perm_B_15_0, e poseidon2_perm_B_15_1, e poseidon2_perm_B_15_2, e poseidon2_perm_B_15_3, e poseidon2_perm_B_16_0, e poseidon2_perm_B_16_1, e poseidon2_perm_B_16_2, e poseidon2_perm_B_16_3, e poseidon2_perm_B_17_0, e poseidon2_perm_B_17_1, e poseidon2_perm_B_17_2, e poseidon2_perm_B_17_3, e poseidon2_perm_B_18_0, e poseidon2_perm_B_18_1, e poseidon2_perm_B_18_2, e poseidon2_perm_B_18_3, e poseidon2_perm_B_19_0, e poseidon2_perm_B_19_1, e poseidon2_perm_B_19_2, e poseidon2_perm_B_19_3, e poseidon2_perm_B_20_0, e poseidon2_perm_B_20_1, e poseidon2_perm_B_20_2, e poseidon2_perm_B_20_3, e poseidon2_perm_B_21_0, e poseidon2_perm_B_21_1, e poseidon2_perm_B_21_2, e poseidon2_perm_B_21_3, e poseidon2_perm_B_22_0, e poseidon2_perm_B_22_1, e poseidon2_perm_B_22_2, e poseidon2_perm_B_22_3, e poseidon2_perm_B_23_0, e poseidon2_perm_B_23_1, e poseidon2_perm_B_23_2, e poseidon2_perm_B_23_3, e poseidon2_perm_B_24_0, e poseidon2_perm_B_24_1, e poseidon2_perm_B_24_2, e poseidon2_perm_B_24_3, e poseidon2_perm_B_25_0, e poseidon2_perm_B_25_1, e poseidon2_perm_B_25_2, e poseidon2_perm_B_25_3, e poseidon2_perm_B_26_0, e poseidon2_perm_B_26_1, e poseidon2_perm_B_26_2, e poseidon2_perm_B_26_3, e poseidon2_perm_B_27_0, e poseidon2_perm_B_27_1, e poseidon2_perm_B_27_2, e poseidon2_perm_B_27_3, e poseidon2_perm_B_28_0, e poseidon2_perm_B_28_1, e poseidon2_perm_B_28_2, e poseidon2_perm_B_28_3, e poseidon2_perm_B_29_0, e poseidon2_perm_B_29_1, e poseidon2_perm_B_29_2, e poseidon2_perm_B_29_3, e poseidon2_perm_B_30_0, e poseidon2_perm_B_30_1, e poseidon2_perm_B_30_2, e poseidon2_perm_B_30_3, e poseidon2_perm_B_31_0, e poseidon2_perm_B_31_1, e poseidon2_perm_B_31_2, e poseidon2_perm_B_31_3, e poseidon2_perm_B_32_0, e poseidon2_perm_B_32_1, e poseidon2_perm_B_32_2, e poseidon2_perm_B_32_3, e poseidon2_perm_B_33_0, e poseidon2_perm_B_33_1, e poseidon2_perm_B_33_2, e poseidon2_perm_B_33_3, e poseidon2_perm_B_34_0, e poseidon2_perm_B_34_1, e poseidon2_perm_B_34_2, e poseidon2_perm_B_34_3, e poseidon2_perm_B_35_0, e poseidon2_perm_B_35_1, e poseidon2_perm_B_35_2, e poseidon2_perm_B_35_3, e poseidon2_perm_B_36_0, e poseidon2_perm_B_36_1, e poseidon2_perm_B_36_2, e poseidon2_perm_B_36_3, e poseidon2_perm_B_37_0, e poseidon2_perm_B_37_1, e poseidon2_perm_B_37_2, e poseidon2_perm_B_37_3, e poseidon2_perm_B_38_0, e poseidon2_perm_B_38_1, e poseidon2_perm_B_38_2, e poseidon2_perm_B_38_3, e poseidon2_perm_B_39_0, e poseidon2_perm_B_39_1, e poseidon2_perm_B_39_2, e poseidon2_perm_B_39_3, e poseidon2_perm_B_40_0, e poseidon2_perm_B_40_1, e poseidon2_perm_B_40_2, e poseidon2_perm_B_40_3, e poseidon2_perm_B_41_0, e poseidon2_perm_B_41_1, e poseidon2_perm_B_41_2, e poseidon2_perm_B_41_3, e poseidon2_perm_B_42_0, e poseidon2_perm_B_42_1, e poseidon2_perm_B_42_2, e poseidon2_perm_B_42_3, e poseidon2_perm_B_43_0, e poseidon2_perm_B_43_1, e poseidon2_perm_B_43_2, e poseidon2_perm_B_43_3, e poseidon2_perm_B_44_0, e poseidon2_perm_B_44_1, e poseidon2_perm_B_44_2, e poseidon2_perm_B_44_3, e poseidon2_perm_B_45_0, e poseidon2_perm_B_45_1, e poseidon2_perm_B_45_2, e poseidon2_perm_B_45_3, e poseidon2_perm_B_46_0, e poseidon2_perm_B_46_1, e poseidon2_perm_B_46_2, e poseidon2_perm_B_46_3, e poseidon2_perm_B_47_0, e poseidon2_perm_B_47_1, e poseidon2_perm_B_47_2, e poseidon2_perm_B_47_3, e poseidon2_perm_B_48_0, e poseidon2_perm_B_48_1, e poseidon2_perm_B_48_2, e poseidon2_perm_B_48_3, e poseidon2_perm_B_49_0, e poseidon2_perm_B_49_1, e poseidon2_perm_B_49_2, e poseidon2_perm_B_49_3, e poseidon2_perm_B_4_0, e poseidon2_perm_B_4_1, e poseidon2_perm_B_4_2, e poseidon2_perm_B_4_3, e poseidon2_perm_B_50_0, e poseidon2_perm_B_50_1, e poseidon2_perm_B_50_2, e poseidon2_perm_B_50_3, e poseidon2_perm_B_51_0, e poseidon2_perm_B_51_1, e poseidon2_perm_B_51_2, e poseidon2_perm_B_51_3, e poseidon2_perm_B_52_0, e poseidon2_perm_B_52_1, e poseidon2_perm_B_52_2, e poseidon2_perm_B_52_3, e poseidon2_perm_B_53_0, e poseidon2_perm_B_53_1, e poseidon2_perm_B_53_2, e poseidon2_perm_B_53_3, e poseidon2_perm_B_54_0, e poseidon2_perm_B_54_1, e poseidon2_perm_B_54_2, e poseidon2_perm_B_54_3, e poseidon2_perm_B_55_0, e poseidon2_perm_B_55_1, e poseidon2_perm_B_55_2, e poseidon2_perm_B_55_3, e poseidon2_perm_B_56_0, e poseidon2_perm_B_56_1, e poseidon2_perm_B_56_2, e poseidon2_perm_B_56_3, e poseidon2_perm_B_57_0, e poseidon2_perm_B_57_1, e poseidon2_perm_B_57_2, e poseidon2_perm_B_57_3, e poseidon2_perm_B_58_0, e poseidon2_perm_B_58_1, e poseidon2_perm_B_58_2, e poseidon2_perm_B_58_3, e poseidon2_perm_B_59_0, e poseidon2_perm_B_59_1, e poseidon2_perm_B_59_2, e poseidon2_perm_B_59_3, e poseidon2_perm_B_5_0, e poseidon2_perm_B_5_1, e poseidon2_perm_B_5_2, e poseidon2_perm_B_5_3, e poseidon2_perm_B_6_0, e poseidon2_perm_B_6_1, e poseidon2_perm_B_6_2, e poseidon2_perm_B_6_3, e poseidon2_perm_B_7_0, e poseidon2_perm_B_7_1, e poseidon2_perm_B_7_2, e poseidon2_perm_B_7_3, e poseidon2_perm_B_8_0, e poseidon2_perm_B_8_1, e poseidon2_perm_B_8_2, e poseidon2_perm_B_8_3, e poseidon2_perm_B_9_0, e poseidon2_perm_B_9_1, e poseidon2_perm_B_9_2, e poseidon2_perm_B_9_3, e poseidon2_perm_EXT_LAYER_4, e poseidon2_perm_EXT_LAYER_5, e poseidon2_perm_EXT_LAYER_6, e poseidon2_perm_EXT_LAYER_7, e poseidon2_perm_T_0_4, e poseidon2_perm_T_0_5, e poseidon2_perm_T_0_6, e poseidon2_perm_T_0_7, e poseidon2_perm_T_1_4, e poseidon2_perm_T_1_5, e poseidon2_perm_T_1_6, e poseidon2_perm_T_1_7, e poseidon2_perm_T_2_4, e poseidon2_perm_T_2_5, e poseidon2_perm_T_2_6, e poseidon2_perm_T_2_7, e poseidon2_perm_T_3_4, e poseidon2_perm_T_3_5, e poseidon2_perm_T_3_6, e poseidon2_perm_T_3_7, e poseidon2_perm_T_60_4, e poseidon2_perm_T_60_5, e poseidon2_perm_T_60_6, e poseidon2_perm_T_60_7, e poseidon2_perm_T_61_4, e poseidon2_perm_T_61_5, e poseidon2_perm_T_61_6, e poseidon2_perm_T_61_7, e poseidon2_perm_T_62_4, e poseidon2_perm_T_62_5, e poseidon2_perm_T_62_6, e poseidon2_perm_T_62_7, e poseidon2_perm_T_63_4, e poseidon2_perm_T_63_5, e poseidon2_perm_T_63_6, e poseidon2_perm_T_63_7, e poseidon2_perm_a_0, e poseidon2_perm_a_1, e poseidon2_perm_a_2, e poseidon2_perm_a_3, e poseidon2_perm_b_0, e poseidon2_perm_b_1, e poseidon2_perm_b_2, e poseidon2_perm_b_3, e poseidon2_perm_mem_batch_tag_inv, e poseidon2_perm_mem_err, e poseidon2_perm_mem_execution_clk, e poseidon2_perm_mem_input_0_, e poseidon2_perm_mem_input_1_, e poseidon2_perm_mem_input_2_, e poseidon2_perm_mem_input_3_, e poseidon2_perm_mem_input_tag_0_, e poseidon2_perm_mem_input_tag_1_, e poseidon2_perm_mem_input_tag_2_, e poseidon2_perm_mem_input_tag_3_, e poseidon2_perm_mem_max_mem_addr, e poseidon2_perm_mem_output_0_, e poseidon2_perm_mem_output_1_, e poseidon2_perm_mem_output_2_, e poseidon2_perm_mem_output_3_, e poseidon2_perm_mem_read_address_0_, e poseidon2_perm_mem_read_address_1_, e poseidon2_perm_mem_read_address_2_, e poseidon2_perm_mem_read_address_3_, e poseidon2_perm_mem_sel, e poseidon2_perm_mem_sel_dst_out_of_range_err, e poseidon2_perm_mem_sel_invalid_tag_err, e poseidon2_perm_mem_sel_should_exec, e poseidon2_perm_mem_sel_should_read_mem, e poseidon2_perm_mem_sel_src_out_of_range_err, e poseidon2_perm_mem_space_id, e poseidon2_perm_mem_write_address_0_, e poseidon2_perm_mem_write_address_1_, e poseidon2_perm_mem_write_address_2_, e poseidon2_perm_mem_write_address_3_, e poseidon2_perm_sel, e public_data_check_address, e public_data_check_clk_diff_hi, e public_data_check_clk_diff_lo, e public_data_check_const_four, e public_data_check_const_three, e public_data_check_discard, e public_data_check_end, e public_data_check_final_value, e public_data_check_intermediate_root, e public_data_check_leaf_not_exists, e public_data_check_leaf_slot, e public_data_check_leaf_slot_low_leaf_slot_diff_inv, e public_data_check_length_pi_idx, e public_data_check_low_leaf_hash, e public_data_check_low_leaf_index, e public_data_check_low_leaf_next_index, e public_data_check_low_leaf_next_slot, e public_data_check_low_leaf_slot, e public_data_check_low_leaf_value, e public_data_check_merkle_hash_separator, e public_data_check_new_leaf_hash, e public_data_check_next_slot_inv, e public_data_check_next_slot_is_nonzero, e public_data_check_non_discarded_write, e public_data_check_non_protocol_write, e public_data_check_not_end, e public_data_check_protocol_write, e public_data_check_public_data_writes_length, e public_data_check_root, e public_data_check_sel_write_to_public_inputs, e public_data_check_should_insert, e public_data_check_siloing_separator, e public_data_check_slot, e public_data_check_tree_height, e public_data_check_tree_size_after_write, e public_data_check_tree_size_before_write, e public_data_check_updated_low_leaf_hash, e public_data_check_updated_low_leaf_next_index, e public_data_check_updated_low_leaf_next_slot, e public_data_check_updated_low_leaf_value, e public_data_check_value, e public_data_check_write, e public_data_check_write_root, e public_data_squash_check_clock, e public_data_squash_clk_diff_hi, e public_data_squash_clk_diff_lo, e public_data_squash_leaf_slot_increase, e public_data_squash_value, e range_check_dyn_diff, e range_check_dyn_rng_chk_bits, e range_check_dyn_rng_chk_pow_2, e range_check_is_lte_u112, e range_check_is_lte_u128, e range_check_is_lte_u16, e range_check_is_lte_u32, e range_check_is_lte_u48, e range_check_is_lte_u64, e range_check_is_lte_u80, e range_check_is_lte_u96, e range_check_rng_chk_bits, e range_check_sel, e range_check_sel_alu, e range_check_sel_gt, e range_check_sel_keccak, e range_check_sel_memory, e range_check_sel_r0_16_bit_rng_lookup, e range_check_sel_r1_16_bit_rng_lookup, e range_check_sel_r2_16_bit_rng_lookup, e range_check_sel_r3_16_bit_rng_lookup, e range_check_sel_r4_16_bit_rng_lookup, e range_check_sel_r5_16_bit_rng_lookup, e range_check_sel_r6_16_bit_rng_lookup, e range_check_u16_r0, e range_check_u16_r1, e range_check_u16_r2, e range_check_u16_r3, e range_check_u16_r4, e range_check_u16_r5, e range_check_u16_r6, e range_check_u16_r7, e range_check_value, e scalar_mul_bit, e scalar_mul_const_two, e scalar_mul_end, e scalar_mul_sel_not_end, e scalar_mul_should_add, e sha256_a_and_b, e sha256_a_and_b_xor_a_and_c, e sha256_a_and_c, e sha256_a_rotr_13, e sha256_a_rotr_2, e sha256_a_rotr_22, e sha256_a_rotr_2_xor_a_rotr_13, e sha256_and_op_id, e sha256_b_and_c, e sha256_batch_tag_inv, e sha256_ch, e sha256_computed_w_lhs, e sha256_computed_w_rhs, e sha256_e_and_f, e sha256_e_rotr_11, e sha256_e_rotr_25, e sha256_e_rotr_6, e sha256_e_rotr_6_xor_e_rotr_11, e sha256_end, e sha256_err, e sha256_input, e sha256_input_rounds_rem_inv, e sha256_input_tag, e sha256_input_tag_diff_inv, e sha256_last, e sha256_lhs_w_10, e sha256_lhs_w_3, e sha256_maj, e sha256_max_input_addr, e sha256_max_mem_addr, e sha256_max_output_addr, e sha256_max_state_addr, e sha256_mem_out_of_range_err, e sha256_memory_address_0_, e sha256_memory_address_1_, e sha256_memory_address_2_, e sha256_memory_address_3_, e sha256_memory_address_4_, e sha256_memory_address_5_, e sha256_memory_address_6_, e sha256_memory_address_7_, e sha256_memory_register_0_, e sha256_memory_register_1_, e sha256_memory_register_2_, e sha256_memory_register_3_, e sha256_memory_register_4_, e sha256_memory_register_5_, e sha256_memory_register_6_, e sha256_memory_register_7_, e sha256_memory_tag_0_, e sha256_memory_tag_1_, e sha256_memory_tag_2_, e sha256_memory_tag_3_, e sha256_memory_tag_4_, e sha256_memory_tag_5_, e sha256_memory_tag_6_, e sha256_memory_tag_7_, e sha256_next_a_lhs, e sha256_next_a_rhs, e sha256_next_e_lhs, e sha256_next_e_rhs, e sha256_not_e, e sha256_not_e_and_g, e sha256_output_a_lhs, e sha256_output_a_rhs, e sha256_output_b_lhs, e sha256_output_b_rhs, e sha256_output_c_lhs, e sha256_output_c_rhs, e sha256_output_d_lhs, e sha256_output_d_rhs, e sha256_output_e_lhs, e sha256_output_e_rhs, e sha256_output_f_lhs, e sha256_output_f_rhs, e sha256_output_g_lhs, e sha256_output_g_rhs, e sha256_output_h_lhs, e sha256_output_h_rhs, e sha256_perform_round, e sha256_rhs_a_13, e sha256_rhs_a_2, e sha256_rhs_a_22, e sha256_rhs_e_11, e sha256_rhs_e_25, e sha256_rhs_e_6, e sha256_rhs_w_10, e sha256_rhs_w_17, e sha256_rhs_w_18, e sha256_rhs_w_19, e sha256_rhs_w_3, e sha256_rhs_w_7, e sha256_round_constant, e sha256_round_count, e sha256_rounds_remaining_inv, e sha256_rw, e sha256_s_0, e sha256_s_1, e sha256_sel_compute_w, e sha256_sel_input_out_of_range_err, e sha256_sel_invalid_input_row_tag_err, e sha256_sel_invalid_state_tag_err, e sha256_sel_is_input_round, e sha256_sel_mem_state_or_output, e sha256_sel_output_out_of_range_err, e sha256_sel_read_input_from_memory, e sha256_sel_state_out_of_range_err, e sha256_state_addr, e sha256_two_pow_10, e sha256_two_pow_11, e sha256_two_pow_13, e sha256_two_pow_17, e sha256_two_pow_18, e sha256_two_pow_19, e sha256_two_pow_2, e sha256_two_pow_22, e sha256_two_pow_25, e sha256_two_pow_3, e sha256_two_pow_32, e sha256_two_pow_6, e sha256_two_pow_7, e sha256_u32_tag, e sha256_w, e sha256_w_15_rotr_18, e sha256_w_15_rotr_7, e sha256_w_15_rotr_7_xor_w_15_rotr_18, e sha256_w_2_rotr_17, e sha256_w_2_rotr_17_xor_w_2_rotr_19, e sha256_w_2_rotr_19, e sha256_w_s_0, e sha256_w_s_1, e sha256_xor_op_id, e to_radix_end, e to_radix_found, e to_radix_is_unsafe_limb, e to_radix_limb_p_diff, e to_radix_limb_radix_diff, e to_radix_mem_err, e to_radix_mem_input_validation_error, e to_radix_mem_last, e to_radix_mem_limb_index_to_lookup, e to_radix_mem_limb_value, e to_radix_mem_max_mem_size, e to_radix_mem_num_limbs_inv, e to_radix_mem_num_limbs_minus_one_inv, e to_radix_mem_output_tag, e to_radix_mem_radix_min_two_inv, e to_radix_mem_sel_dst_out_of_range_err, e to_radix_mem_sel_invalid_bitwise_radix, e to_radix_mem_sel_num_limbs_is_zero, e to_radix_mem_sel_radix_eq_2, e to_radix_mem_sel_radix_gt_256_err, e to_radix_mem_sel_radix_lt_2_err, e to_radix_mem_sel_value_is_zero, e to_radix_mem_two, e to_radix_mem_two_five_six, e to_radix_mem_value_found, e to_radix_mem_value_inv, e to_radix_mem_write_addr_upper_bound, e to_radix_p_limb, e to_radix_rem_inverse, e to_radix_safety_diff_inverse, e tx_array_length_l2_to_l1_messages_pi_offset, e tx_array_length_note_hashes_pi_offset, e tx_array_length_nullifiers_pi_offset, e tx_calldata_hash, e tx_calldata_size, e tx_const_three, e tx_contract_addr, e tx_dom_sep_public_storage_map_slot, e tx_effective_fee_per_da_gas, e tx_effective_fee_per_l2_gas, e tx_end_phase, e tx_fee_juice_balance_slot, e tx_fee_juice_balances_slot_constant, e tx_fee_juice_contract_address, e tx_fee_payer, e tx_fee_payer_balance, e tx_fee_payer_new_balance, e tx_fee_payer_pi_offset, e tx_fields_length_public_logs_pi_offset, e tx_gas_limit_pi_offset, e tx_gas_used_pi_offset, e tx_is_cleanup, e tx_is_collect_fee, e tx_is_padded, e tx_is_public_call_request, e tx_is_static, e tx_is_tree_insert_phase, e tx_is_tree_padding, e tx_l1_l2_pi_offset, e tx_l2_l1_msg_content, e tx_l2_l1_msg_contract_address, e tx_l2_l1_msg_recipient, e tx_leaf_value, e tx_msg_sender, e tx_next_da_gas_used, e tx_next_da_gas_used_sent_to_enqueued_call, e tx_next_l2_gas_used, e tx_next_l2_gas_used_sent_to_enqueued_call, e tx_next_note_hash_tree_root, e tx_next_note_hash_tree_size, e tx_next_nullifier_tree_root, e tx_next_nullifier_tree_size, e tx_next_num_l2_to_l1_messages, e tx_next_num_note_hashes_emitted, e tx_next_num_nullifiers_emitted, e tx_next_num_public_log_fields, e tx_next_phase_on_revert, e tx_next_public_data_tree_root, e tx_next_public_data_tree_size, e tx_next_retrieved_bytecodes_tree_root, e tx_next_retrieved_bytecodes_tree_size, e tx_next_written_public_data_slots_tree_root, e tx_next_written_public_data_slots_tree_size, e tx_note_hash_pi_offset, e tx_nullifier_limit_error, e tx_nullifier_merkle_separator, e tx_nullifier_pi_offset, e tx_nullifier_tree_height, e tx_prev_da_gas_used_sent_to_enqueued_call, e tx_prev_l2_gas_used_sent_to_enqueued_call, e tx_public_data_pi_offset, e tx_read_pi_length_offset, e tx_read_pi_start_offset, e tx_remaining_phase_inv, e tx_remaining_phase_minus_one_inv, e tx_remaining_side_effects_inv, e tx_reverted_pi_offset, e tx_sel_l2_l1_msg_append, e tx_sel_non_revertible_append_l2_l1_msg, e tx_sel_non_revertible_append_note_hash, e tx_sel_non_revertible_append_nullifier, e tx_sel_note_hash_append, e tx_sel_nullifier_append, e tx_sel_process_call_request, e tx_sel_read_phase_length, e tx_sel_read_trees_and_gas_used, e tx_sel_revertible_append_l2_l1_msg, e tx_sel_revertible_append_note_hash, e tx_sel_revertible_append_nullifier, e tx_sel_try_l2_l1_msg_append, e tx_sel_try_note_hash_append, e tx_sel_try_nullifier_append, e tx_setup_phase_value, e tx_should_read_gas_limit, e tx_uint32_max, e tx_write_nullifier_pi_offset, e tx_write_pi_offset, e update_check_address, e update_check_const_three, e update_check_contract_instance_registry_address, e update_check_current_class_id, e update_check_delayed_public_mutable_hash_slot, e update_check_delayed_public_mutable_slot, e update_check_dom_sep_public_storage_map_slot, e update_check_hash_not_zero, e update_check_original_class_id, e update_check_public_data_tree_root, e update_check_sel, e update_check_timestamp, e update_check_timestamp_is_lt_timestamp_of_change, e update_check_timestamp_of_change, e update_check_timestamp_of_change_bit_size, e update_check_timestamp_pi_offset, e update_check_update_hash, e update_check_update_hash_inv, e update_check_update_hi_metadata, e update_check_update_hi_metadata_bit_size, e update_check_update_post_class_id_is_zero, e update_check_update_post_class_inv, e update_check_update_pre_class_id_is_zero, e update_check_update_pre_class_inv, e update_check_update_preimage_metadata, e update_check_update_preimage_post_class_id, e update_check_update_preimage_pre_class_id, e update_check_updated_class_ids_slot, e lookup_range_check_dyn_rng_chk_pow_2_counts, e lookup_range_check_dyn_diff_is_u16_counts, e lookup_range_check_r0_is_u16_counts, e lookup_range_check_r1_is_u16_counts, e lookup_range_check_r2_is_u16_counts, e lookup_range_check_r3_is_u16_counts, e lookup_range_check_r4_is_u16_counts, e lookup_range_check_r5_is_u16_counts, e lookup_range_check_r6_is_u16_counts, e lookup_range_check_r7_is_u16_counts, e lookup_ff_gt_a_lo_range_counts, e lookup_ff_gt_a_hi_range_counts, e lookup_gt_gt_range_counts, e lookup_alu_tag_max_bits_value_counts, e lookup_alu_range_check_decomposition_a_lo_counts, e lookup_alu_range_check_decomposition_a_hi_counts, e lookup_alu_range_check_decomposition_b_lo_counts, e lookup_alu_range_check_decomposition_b_hi_counts, e lookup_alu_range_check_mul_c_hi_counts, e lookup_alu_range_check_div_remainder_counts, e lookup_alu_ff_gt_counts, e lookup_alu_int_gt_counts, e lookup_alu_shifts_two_pow_counts, e lookup_alu_large_trunc_canonical_dec_counts, e lookup_alu_range_check_trunc_mid_counts, e lookup_bitwise_integral_tag_length_counts, e lookup_bitwise_byte_operations_counts, e lookup_memory_range_check_limb_0_counts, e lookup_memory_range_check_limb_1_counts, e lookup_memory_range_check_limb_2_counts, e lookup_memory_tag_max_bits_counts, e lookup_memory_range_check_write_tagged_value_counts, e lookup_data_copy_offset_plus_size_is_gt_data_size_counts, e lookup_data_copy_check_src_addr_in_range_counts, e lookup_data_copy_check_dst_addr_in_range_counts, e lookup_data_copy_sel_has_reads_counts, e lookup_data_copy_col_read_counts, e lookup_ecc_mem_check_dst_addr_in_range_counts, e lookup_ecc_mem_input_output_ecc_add_counts, e lookup_keccakf1600_theta_xor_01_counts, e lookup_keccakf1600_theta_xor_02_counts, e lookup_keccakf1600_theta_xor_03_counts, e lookup_keccakf1600_theta_xor_row_0_counts, e lookup_keccakf1600_theta_xor_11_counts, e lookup_keccakf1600_theta_xor_12_counts, e lookup_keccakf1600_theta_xor_13_counts, e lookup_keccakf1600_theta_xor_row_1_counts, e lookup_keccakf1600_theta_xor_21_counts, e lookup_keccakf1600_theta_xor_22_counts, e lookup_keccakf1600_theta_xor_23_counts, e lookup_keccakf1600_theta_xor_row_2_counts, e lookup_keccakf1600_theta_xor_31_counts, e lookup_keccakf1600_theta_xor_32_counts, e lookup_keccakf1600_theta_xor_33_counts, e lookup_keccakf1600_theta_xor_row_3_counts, e lookup_keccakf1600_theta_xor_41_counts, e lookup_keccakf1600_theta_xor_42_counts, e lookup_keccakf1600_theta_xor_43_counts, e lookup_keccakf1600_theta_xor_row_4_counts, e lookup_keccakf1600_theta_combined_xor_0_counts, e lookup_keccakf1600_theta_combined_xor_1_counts, e lookup_keccakf1600_theta_combined_xor_2_counts, e lookup_keccakf1600_theta_combined_xor_3_counts, e lookup_keccakf1600_theta_combined_xor_4_counts, e lookup_keccakf1600_state_theta_00_counts, e lookup_keccakf1600_state_theta_01_counts, e lookup_keccakf1600_state_theta_02_counts, e lookup_keccakf1600_state_theta_03_counts, e lookup_keccakf1600_state_theta_04_counts, e lookup_keccakf1600_state_theta_10_counts, e lookup_keccakf1600_state_theta_11_counts, e lookup_keccakf1600_state_theta_12_counts, e lookup_keccakf1600_state_theta_13_counts, e lookup_keccakf1600_state_theta_14_counts, e lookup_keccakf1600_state_theta_20_counts, e lookup_keccakf1600_state_theta_21_counts, e lookup_keccakf1600_state_theta_22_counts, e lookup_keccakf1600_state_theta_23_counts, e lookup_keccakf1600_state_theta_24_counts, e lookup_keccakf1600_state_theta_30_counts, e lookup_keccakf1600_state_theta_31_counts, e lookup_keccakf1600_state_theta_32_counts, e lookup_keccakf1600_state_theta_33_counts, e lookup_keccakf1600_state_theta_34_counts, e lookup_keccakf1600_state_theta_40_counts, e lookup_keccakf1600_state_theta_41_counts, e lookup_keccakf1600_state_theta_42_counts, e lookup_keccakf1600_state_theta_43_counts, e lookup_keccakf1600_state_theta_44_counts, e lookup_keccakf1600_theta_limb_02_range_counts, e lookup_keccakf1600_theta_limb_04_range_counts, e lookup_keccakf1600_theta_limb_10_range_counts, e lookup_keccakf1600_theta_limb_12_range_counts, e lookup_keccakf1600_theta_limb_14_range_counts, e lookup_keccakf1600_theta_limb_21_range_counts, e lookup_keccakf1600_theta_limb_23_range_counts, e lookup_keccakf1600_theta_limb_30_range_counts, e lookup_keccakf1600_theta_limb_32_range_counts, e lookup_keccakf1600_theta_limb_33_range_counts, e lookup_keccakf1600_theta_limb_40_range_counts, e lookup_keccakf1600_theta_limb_41_range_counts, e lookup_keccakf1600_theta_limb_43_range_counts, e lookup_keccakf1600_theta_limb_44_range_counts, e lookup_keccakf1600_theta_limb_01_range_counts, e lookup_keccakf1600_theta_limb_03_range_counts, e lookup_keccakf1600_theta_limb_11_range_counts, e lookup_keccakf1600_theta_limb_13_range_counts, e lookup_keccakf1600_theta_limb_20_range_counts, e lookup_keccakf1600_theta_limb_22_range_counts, e lookup_keccakf1600_theta_limb_24_range_counts, e lookup_keccakf1600_theta_limb_31_range_counts, e lookup_keccakf1600_theta_limb_34_range_counts, e lookup_keccakf1600_theta_limb_42_range_counts, e lookup_keccakf1600_state_pi_and_00_counts, e lookup_keccakf1600_state_pi_and_01_counts, e lookup_keccakf1600_state_pi_and_02_counts, e lookup_keccakf1600_state_pi_and_03_counts, e lookup_keccakf1600_state_pi_and_04_counts, e lookup_keccakf1600_state_pi_and_10_counts, e lookup_keccakf1600_state_pi_and_11_counts, e lookup_keccakf1600_state_pi_and_12_counts, e lookup_keccakf1600_state_pi_and_13_counts, e lookup_keccakf1600_state_pi_and_14_counts, e lookup_keccakf1600_state_pi_and_20_counts, e lookup_keccakf1600_state_pi_and_21_counts, e lookup_keccakf1600_state_pi_and_22_counts, e lookup_keccakf1600_state_pi_and_23_counts, e lookup_keccakf1600_state_pi_and_24_counts, e lookup_keccakf1600_state_pi_and_30_counts, e lookup_keccakf1600_state_pi_and_31_counts, e lookup_keccakf1600_state_pi_and_32_counts, e lookup_keccakf1600_state_pi_and_33_counts, e lookup_keccakf1600_state_pi_and_34_counts, e lookup_keccakf1600_state_pi_and_40_counts, e lookup_keccakf1600_state_pi_and_41_counts, e lookup_keccakf1600_state_pi_and_42_counts, e lookup_keccakf1600_state_pi_and_43_counts, e lookup_keccakf1600_state_pi_and_44_counts, e lookup_keccakf1600_state_chi_00_counts, e lookup_keccakf1600_state_chi_01_counts, e lookup_keccakf1600_state_chi_02_counts, e lookup_keccakf1600_state_chi_03_counts, e lookup_keccakf1600_state_chi_04_counts, e lookup_keccakf1600_state_chi_10_counts, e lookup_keccakf1600_state_chi_11_counts, e lookup_keccakf1600_state_chi_12_counts, e lookup_keccakf1600_state_chi_13_counts, e lookup_keccakf1600_state_chi_14_counts, e lookup_keccakf1600_state_chi_20_counts, e lookup_keccakf1600_state_chi_21_counts, e lookup_keccakf1600_state_chi_22_counts, e lookup_keccakf1600_state_chi_23_counts, e lookup_keccakf1600_state_chi_24_counts, e lookup_keccakf1600_state_chi_30_counts, e lookup_keccakf1600_state_chi_31_counts, e lookup_keccakf1600_state_chi_32_counts, e lookup_keccakf1600_state_chi_33_counts, e lookup_keccakf1600_state_chi_34_counts, e lookup_keccakf1600_state_chi_40_counts, e lookup_keccakf1600_state_chi_41_counts, e lookup_keccakf1600_state_chi_42_counts, e lookup_keccakf1600_state_chi_43_counts, e lookup_keccakf1600_state_chi_44_counts, e lookup_keccakf1600_round_cst_counts, e lookup_keccakf1600_state_iota_00_counts, e lookup_keccakf1600_src_out_of_range_toggle_counts, e lookup_keccakf1600_dst_out_of_range_toggle_counts, e lookup_poseidon2_mem_check_src_addr_in_range_counts, e lookup_poseidon2_mem_check_dst_addr_in_range_counts, e lookup_poseidon2_mem_input_output_poseidon2_perm_counts, e lookup_to_radix_limb_range_counts, e lookup_to_radix_limb_less_than_radix_range_counts, e lookup_to_radix_fetch_safe_limbs_counts, e lookup_to_radix_fetch_p_limb_counts, e lookup_to_radix_limb_p_diff_range_counts, e lookup_scalar_mul_to_radix_counts, e lookup_scalar_mul_double_counts, e lookup_scalar_mul_add_counts, e lookup_sha256_range_comp_w_lhs_counts, e lookup_sha256_range_comp_w_rhs_counts, e lookup_sha256_range_rhs_w_7_counts, e lookup_sha256_range_rhs_w_18_counts, e lookup_sha256_range_rhs_w_3_counts, e lookup_sha256_w_s_0_xor_0_counts, e lookup_sha256_w_s_0_xor_1_counts, e lookup_sha256_range_rhs_w_17_counts, e lookup_sha256_range_rhs_w_19_counts, e lookup_sha256_range_rhs_w_10_counts, e lookup_sha256_w_s_1_xor_0_counts, e lookup_sha256_w_s_1_xor_1_counts, e lookup_sha256_range_rhs_e_6_counts, e lookup_sha256_range_rhs_e_11_counts, e lookup_sha256_range_rhs_e_25_counts, e lookup_sha256_s_1_xor_0_counts, e lookup_sha256_s_1_xor_1_counts, e lookup_sha256_ch_and_0_counts, e lookup_sha256_ch_and_1_counts, e lookup_sha256_ch_xor_counts, e lookup_sha256_round_constant_counts, e lookup_sha256_range_rhs_a_2_counts, e lookup_sha256_range_rhs_a_13_counts, e lookup_sha256_range_rhs_a_22_counts, e lookup_sha256_s_0_xor_0_counts, e lookup_sha256_s_0_xor_1_counts, e lookup_sha256_maj_and_0_counts, e lookup_sha256_maj_and_1_counts, e lookup_sha256_maj_and_2_counts, e lookup_sha256_maj_xor_0_counts, e lookup_sha256_maj_xor_1_counts, e lookup_sha256_range_comp_next_a_lhs_counts, e lookup_sha256_range_comp_next_a_rhs_counts, e lookup_sha256_range_comp_next_e_lhs_counts, e lookup_sha256_range_comp_next_e_rhs_counts, e lookup_sha256_range_comp_a_rhs_counts, e lookup_sha256_range_comp_b_rhs_counts, e lookup_sha256_range_comp_c_rhs_counts, e lookup_sha256_range_comp_d_rhs_counts, e lookup_sha256_range_comp_e_rhs_counts, e lookup_sha256_range_comp_f_rhs_counts, e lookup_sha256_range_comp_g_rhs_counts, e lookup_sha256_range_comp_h_rhs_counts, e lookup_sha256_mem_check_state_addr_in_range_counts, e lookup_sha256_mem_check_input_addr_in_range_counts, e lookup_sha256_mem_check_output_addr_in_range_counts, e lookup_to_radix_mem_check_dst_addr_in_range_counts, e lookup_to_radix_mem_check_radix_lt_2_counts, e lookup_to_radix_mem_check_radix_gt_256_counts, e lookup_to_radix_mem_input_output_to_radix_counts, e lookup_poseidon2_hash_poseidon2_perm_counts, e lookup_address_derivation_salted_initialization_hash_poseidon2_0_counts, e lookup_address_derivation_salted_initialization_hash_poseidon2_1_counts, e lookup_address_derivation_partial_address_poseidon2_counts, e lookup_address_derivation_public_keys_hash_poseidon2_0_counts, e lookup_address_derivation_public_keys_hash_poseidon2_1_counts, e lookup_address_derivation_public_keys_hash_poseidon2_2_counts, e lookup_address_derivation_public_keys_hash_poseidon2_3_counts, e lookup_address_derivation_public_keys_hash_poseidon2_4_counts, e lookup_address_derivation_preaddress_poseidon2_counts, e lookup_address_derivation_preaddress_scalar_mul_counts, e lookup_address_derivation_address_ecadd_counts, e lookup_bc_decomposition_bytes_are_bytes_counts, e lookup_bc_hashing_poseidon2_hash_counts, e lookup_merkle_check_merkle_poseidon2_read_counts, e lookup_merkle_check_merkle_poseidon2_write_counts, e lookup_indexed_tree_check_silo_poseidon2_counts, e lookup_indexed_tree_check_low_leaf_value_validation_counts, e lookup_indexed_tree_check_low_leaf_next_value_validation_counts, e lookup_indexed_tree_check_low_leaf_poseidon2_counts, e lookup_indexed_tree_check_updated_low_leaf_poseidon2_counts, e lookup_indexed_tree_check_low_leaf_merkle_check_counts, e lookup_indexed_tree_check_new_leaf_poseidon2_counts, e lookup_indexed_tree_check_new_leaf_merkle_check_counts, e lookup_indexed_tree_check_write_value_to_public_inputs_counts, e lookup_public_data_squash_leaf_slot_increase_ff_gt_counts, e lookup_public_data_squash_clk_diff_range_lo_counts, e lookup_public_data_squash_clk_diff_range_hi_counts, e lookup_public_data_check_clk_diff_range_lo_counts, e lookup_public_data_check_clk_diff_range_hi_counts, e lookup_public_data_check_silo_poseidon2_counts, e lookup_public_data_check_low_leaf_slot_validation_counts, e lookup_public_data_check_low_leaf_next_slot_validation_counts, e lookup_public_data_check_low_leaf_poseidon2_0_counts, e lookup_public_data_check_low_leaf_poseidon2_1_counts, e lookup_public_data_check_updated_low_leaf_poseidon2_0_counts, e lookup_public_data_check_updated_low_leaf_poseidon2_1_counts, e lookup_public_data_check_low_leaf_merkle_check_counts, e lookup_public_data_check_new_leaf_poseidon2_0_counts, e lookup_public_data_check_new_leaf_poseidon2_1_counts, e lookup_public_data_check_new_leaf_merkle_check_counts, e lookup_public_data_check_write_public_data_to_public_inputs_counts, e lookup_public_data_check_write_writes_length_to_public_inputs_counts, e lookup_update_check_timestamp_from_public_inputs_counts, e lookup_update_check_delayed_public_mutable_slot_poseidon2_counts, e lookup_update_check_update_hash_public_data_read_counts, e lookup_update_check_update_hash_poseidon2_counts, e lookup_update_check_update_hi_metadata_range_counts, e lookup_update_check_update_lo_metadata_range_counts, e lookup_update_check_timestamp_is_lt_timestamp_of_change_counts, e lookup_contract_instance_retrieval_check_protocol_address_range_counts, e lookup_contract_instance_retrieval_read_derived_address_from_public_inputs_counts, e lookup_contract_instance_retrieval_deployment_nullifier_read_counts, e lookup_contract_instance_retrieval_address_derivation_counts, e lookup_contract_instance_retrieval_update_check_counts, e lookup_class_id_derivation_class_id_poseidon2_0_counts, e lookup_class_id_derivation_class_id_poseidon2_1_counts, e lookup_bc_retrieval_contract_instance_retrieval_counts, e lookup_bc_retrieval_class_id_derivation_counts, e lookup_bc_retrieval_is_new_class_check_counts, e lookup_bc_retrieval_retrieved_bytecodes_insertion_counts, e lookup_instr_fetching_pc_abs_diff_positive_counts, e lookup_instr_fetching_instr_abs_diff_positive_counts, e lookup_instr_fetching_tag_value_validation_counts, e lookup_instr_fetching_bytecode_size_from_bc_dec_counts, e lookup_instr_fetching_bytes_from_bc_dec_counts, e lookup_instr_fetching_wire_instruction_info_counts, e lookup_emit_public_log_check_memory_out_of_bounds_counts, e lookup_emit_public_log_check_log_fields_count_counts, e lookup_emit_public_log_write_data_to_public_inputs_counts, e lookup_get_contract_instance_precomputed_info_counts, e lookup_get_contract_instance_contract_instance_retrieval_counts, e lookup_l1_to_l2_message_tree_check_merkle_check_counts, e lookup_internal_call_unwind_call_stack_counts, e lookup_context_ctx_stack_rollback_counts, e lookup_context_ctx_stack_return_counts, e lookup_addressing_relative_overflow_result_0_counts, e lookup_addressing_relative_overflow_result_1_counts, e lookup_addressing_relative_overflow_result_2_counts, e lookup_addressing_relative_overflow_result_3_counts, e lookup_addressing_relative_overflow_result_4_counts, e lookup_addressing_relative_overflow_result_5_counts, e lookup_addressing_relative_overflow_result_6_counts, e lookup_gas_addressing_gas_read_counts, e lookup_gas_is_out_of_gas_l2_counts, e lookup_gas_is_out_of_gas_da_counts, e lookup_note_hash_tree_check_silo_poseidon2_counts, e lookup_note_hash_tree_check_read_first_nullifier_counts, e lookup_note_hash_tree_check_nonce_computation_poseidon2_counts, e lookup_note_hash_tree_check_unique_note_hash_poseidon2_counts, e lookup_note_hash_tree_check_merkle_check_counts, e lookup_note_hash_tree_check_write_note_hash_to_public_inputs_counts, e lookup_emit_notehash_notehash_tree_write_counts, e lookup_emit_nullifier_write_nullifier_counts, e lookup_external_call_is_l2_gas_left_gt_allocated_counts, e lookup_external_call_is_da_gas_left_gt_allocated_counts, e lookup_get_env_var_precomputed_info_counts, e lookup_get_env_var_read_from_public_inputs_col0_counts, e lookup_get_env_var_read_from_public_inputs_col1_counts, e lookup_l1_to_l2_message_exists_l1_to_l2_msg_leaf_index_in_range_counts, e lookup_l1_to_l2_message_exists_l1_to_l2_msg_read_counts, e lookup_notehash_exists_note_hash_leaf_index_in_range_counts, e lookup_notehash_exists_note_hash_read_counts, e lookup_nullifier_exists_nullifier_exists_check_counts, e lookup_send_l2_to_l1_msg_recipient_check_counts, e lookup_send_l2_to_l1_msg_write_l2_to_l1_msg_counts, e lookup_sload_storage_read_counts, e lookup_sstore_record_written_storage_slot_counts, e lookup_execution_bytecode_retrieval_result_counts, e lookup_execution_instruction_fetching_result_counts, e lookup_execution_instruction_fetching_body_counts, e lookup_execution_exec_spec_read_counts, e lookup_execution_dyn_l2_factor_bitwise_counts, e lookup_execution_check_radix_gt_256_counts, e lookup_execution_get_p_limbs_counts, e lookup_execution_get_max_limbs_counts, e lookup_execution_check_written_storage_slot_counts, e lookup_execution_dispatch_to_alu_counts, e lookup_execution_dispatch_to_bitwise_counts, e lookup_execution_dispatch_to_cast_counts, e lookup_execution_dispatch_to_set_counts, e lookup_calldata_hashing_get_calldata_field_0_counts, e lookup_calldata_hashing_get_calldata_field_1_counts, e lookup_calldata_hashing_get_calldata_field_2_counts, e lookup_calldata_hashing_poseidon2_hash_counts, e lookup_tx_context_public_inputs_note_hash_tree_counts, e lookup_tx_context_public_inputs_nullifier_tree_counts, e lookup_tx_context_public_inputs_public_data_tree_counts, e lookup_tx_context_public_inputs_l1_l2_tree_counts, e lookup_tx_context_public_inputs_gas_used_counts, e lookup_tx_context_public_inputs_read_gas_limit_counts, e lookup_tx_context_public_inputs_read_reverted_counts, e lookup_tx_context_restore_state_on_revert_counts, e lookup_tx_context_public_inputs_write_note_hash_count_counts, e lookup_tx_context_public_inputs_write_nullifier_count_counts, e lookup_tx_context_public_inputs_write_l2_to_l1_message_count_counts, e lookup_tx_context_public_inputs_write_public_log_count_counts, e lookup_tx_read_phase_spec_counts, e lookup_tx_read_phase_length_counts, e lookup_tx_read_public_call_request_phase_counts, e lookup_tx_read_tree_insert_value_counts, e lookup_tx_note_hash_append_counts, e lookup_tx_nullifier_append_counts, e lookup_tx_read_l2_l1_msg_counts, e lookup_tx_write_l2_l1_msg_counts, e lookup_tx_read_effective_fee_public_inputs_counts, e lookup_tx_read_fee_payer_public_inputs_counts, e lookup_tx_balance_slot_poseidon2_counts, e lookup_tx_balance_read_counts, e lookup_tx_balance_validation_counts, e lookup_tx_write_fee_public_inputs_counts, e bc_decomposition_bytes, e bc_decomposition_bytes_pc_plus_1, e bc_decomposition_bytes_pc_plus_10, e bc_decomposition_bytes_pc_plus_11, e bc_decomposition_bytes_pc_plus_12, e bc_decomposition_bytes_pc_plus_13, e bc_decomposition_bytes_pc_plus_14, e bc_decomposition_bytes_pc_plus_15, e bc_decomposition_bytes_pc_plus_16, e bc_decomposition_bytes_pc_plus_17, e bc_decomposition_bytes_pc_plus_18, e bc_decomposition_bytes_pc_plus_19, e bc_decomposition_bytes_pc_plus_2, e bc_decomposition_bytes_pc_plus_20, e bc_decomposition_bytes_pc_plus_21, e bc_decomposition_bytes_pc_plus_22, e bc_decomposition_bytes_pc_plus_23, e bc_decomposition_bytes_pc_plus_24, e bc_decomposition_bytes_pc_plus_25, e bc_decomposition_bytes_pc_plus_26, e bc_decomposition_bytes_pc_plus_27, e bc_decomposition_bytes_pc_plus_28, e bc_decomposition_bytes_pc_plus_29, e bc_decomposition_bytes_pc_plus_3, e bc_decomposition_bytes_pc_plus_30, e bc_decomposition_bytes_pc_plus_31, e bc_decomposition_bytes_pc_plus_32, e bc_decomposition_bytes_pc_plus_33, e bc_decomposition_bytes_pc_plus_34, e bc_decomposition_bytes_pc_plus_35, e bc_decomposition_bytes_pc_plus_4, e bc_decomposition_bytes_pc_plus_5, e bc_decomposition_bytes_pc_plus_6, e bc_decomposition_bytes_pc_plus_7, e bc_decomposition_bytes_pc_plus_8, e bc_decomposition_bytes_pc_plus_9, e bc_decomposition_bytes_remaining, e bc_decomposition_id, e bc_decomposition_next_packed_pc, e bc_decomposition_pc, e bc_decomposition_sel, e bc_decomposition_sel_windows_gt_remaining, e bc_decomposition_start, e bc_hashing_bytecode_id, e bc_hashing_padding, e bc_hashing_pc_index_1, e bc_hashing_rounds_rem, e bc_hashing_sel, e bc_hashing_sel_not_start, e bc_hashing_start, e bitwise_acc_ia, e bitwise_acc_ib, e bitwise_acc_ic, e bitwise_ctr, e bitwise_op_id, e bitwise_sel, e bitwise_start, e calldata_context_id, e calldata_hashing_calldata_size, e calldata_hashing_context_id, e calldata_hashing_index_0_, e calldata_hashing_output_hash, e calldata_hashing_rounds_rem, e calldata_hashing_sel, e calldata_hashing_start, e calldata_index, e calldata_sel, e data_copy_clk, e data_copy_copy_size, e data_copy_dst_addr, e data_copy_dst_context_id, e data_copy_padding, e data_copy_read_addr, e data_copy_reads_left, e data_copy_sel, e data_copy_sel_cd_copy, e data_copy_src_context_id, e data_copy_start, e emit_public_log_contract_address, e emit_public_log_correct_tag, e emit_public_log_error_out_of_bounds, e emit_public_log_error_tag_mismatch, e emit_public_log_execution_clk, e emit_public_log_is_write_contract_address, e emit_public_log_is_write_memory_value, e emit_public_log_log_address, e emit_public_log_public_inputs_index, e emit_public_log_remaining_rows, e emit_public_log_seen_wrong_tag, e emit_public_log_sel, e emit_public_log_sel_write_to_public_inputs, e emit_public_log_space_id, e emit_public_log_start, e execution_bytecode_id, e execution_clk, e execution_context_id, e execution_contract_address, e execution_da_gas_limit, e execution_discard, e execution_dying_context_id, e execution_enqueued_call_start, e execution_internal_call_id, e execution_internal_call_return_id, e execution_is_static, e execution_l1_l2_tree_root, e execution_l2_gas_limit, e execution_last_child_id, e execution_last_child_returndata_addr, e execution_last_child_returndata_size, e execution_last_child_success, e execution_msg_sender, e execution_next_context_id, e execution_next_internal_call_id, e execution_parent_calldata_addr, e execution_parent_calldata_size, e execution_parent_da_gas_limit, e execution_parent_da_gas_used, e execution_parent_id, e execution_parent_l2_gas_limit, e execution_parent_l2_gas_used, e execution_pc, e execution_prev_da_gas_used, e execution_prev_l2_gas_used, e execution_prev_note_hash_tree_root, e execution_prev_note_hash_tree_size, e execution_prev_nullifier_tree_root, e execution_prev_nullifier_tree_size, e execution_prev_num_l2_to_l1_messages, e execution_prev_num_note_hashes_emitted, e execution_prev_num_nullifiers_emitted, e execution_prev_num_public_log_fields, e execution_prev_public_data_tree_root, e execution_prev_public_data_tree_size, e execution_prev_retrieved_bytecodes_tree_root, e execution_prev_retrieved_bytecodes_tree_size, e execution_prev_written_public_data_slots_tree_root, e execution_prev_written_public_data_slots_tree_size, e execution_sel, e execution_sel_first_row_in_context, e execution_transaction_fee, e ff_gt_a_hi, e ff_gt_a_lo, e ff_gt_b_hi, e ff_gt_b_lo, e ff_gt_cmp_rng_ctr, e ff_gt_p_sub_a_hi, e ff_gt_p_sub_a_lo, e ff_gt_p_sub_b_hi, e ff_gt_p_sub_b_lo, e ff_gt_sel, e ff_gt_sel_dec, e ff_gt_sel_gt, e keccak_memory_addr, e keccak_memory_clk, e keccak_memory_ctr, e keccak_memory_rw, e keccak_memory_sel, e keccak_memory_space_id, e keccak_memory_start_read, e keccak_memory_start_write, e keccak_memory_tag_error, e keccak_memory_val_0_, e keccak_memory_val_10_, e keccak_memory_val_11_, e keccak_memory_val_12_, e keccak_memory_val_13_, e keccak_memory_val_14_, e keccak_memory_val_15_, e keccak_memory_val_16_, e keccak_memory_val_17_, e keccak_memory_val_18_, e keccak_memory_val_19_, e keccak_memory_val_1_, e keccak_memory_val_20_, e keccak_memory_val_21_, e keccak_memory_val_22_, e keccak_memory_val_23_, e keccak_memory_val_2_, e keccak_memory_val_3_, e keccak_memory_val_4_, e keccak_memory_val_5_, e keccak_memory_val_6_, e keccak_memory_val_7_, e keccak_memory_val_8_, e keccak_memory_val_9_, e keccakf1600_clk, e keccakf1600_dst_addr, e keccakf1600_round, e keccakf1600_sel, e keccakf1600_sel_no_error, e keccakf1600_space_id, e keccakf1600_start, e keccakf1600_state_in_00, e keccakf1600_state_in_01, e keccakf1600_state_in_02, e keccakf1600_state_in_03, e keccakf1600_state_in_04, e keccakf1600_state_in_10, e keccakf1600_state_in_11, e keccakf1600_state_in_12, e keccakf1600_state_in_13, e keccakf1600_state_in_14, e keccakf1600_state_in_20, e keccakf1600_state_in_21, e keccakf1600_state_in_22, e keccakf1600_state_in_23, e keccakf1600_state_in_24, e keccakf1600_state_in_30, e keccakf1600_state_in_31, e keccakf1600_state_in_32, e keccakf1600_state_in_33, e keccakf1600_state_in_34, e keccakf1600_state_in_40, e keccakf1600_state_in_41, e keccakf1600_state_in_42, e keccakf1600_state_in_43, e keccakf1600_state_in_44, e memory_address, e memory_clk, e memory_rw, e memory_sel, e memory_space_id, e memory_tag, e memory_value, e merkle_check_index, e merkle_check_merkle_hash_separator, e merkle_check_path_len, e merkle_check_read_node, e merkle_check_read_root, e merkle_check_sel, e merkle_check_start, e merkle_check_write, e merkle_check_write_node, e merkle_check_write_root, e poseidon2_hash_a_0, e poseidon2_hash_a_1, e poseidon2_hash_a_2, e poseidon2_hash_a_3, e poseidon2_hash_input_0, e poseidon2_hash_input_1, e poseidon2_hash_input_2, e poseidon2_hash_num_perm_rounds_rem, e poseidon2_hash_output, e poseidon2_hash_sel, e poseidon2_hash_start, e public_data_check_clk, e public_data_check_sel, e public_data_check_write_idx, e public_data_squash_clk, e public_data_squash_final_value, e public_data_squash_leaf_slot, e public_data_squash_sel, e public_data_squash_write_to_public_inputs, e scalar_mul_bit_idx, e scalar_mul_point_inf, e scalar_mul_point_x, e scalar_mul_point_y, e scalar_mul_res_inf, e scalar_mul_res_x, e scalar_mul_res_y, e scalar_mul_scalar, e scalar_mul_sel, e scalar_mul_start, e scalar_mul_temp_inf, e scalar_mul_temp_x, e scalar_mul_temp_y, e sha256_a, e sha256_b, e sha256_c, e sha256_d, e sha256_e, e sha256_execution_clk, e sha256_f, e sha256_g, e sha256_h, e sha256_helper_w0, e sha256_helper_w1, e sha256_helper_w10, e sha256_helper_w11, e sha256_helper_w12, e sha256_helper_w13, e sha256_helper_w14, e sha256_helper_w15, e sha256_helper_w2, e sha256_helper_w3, e sha256_helper_w4, e sha256_helper_w5, e sha256_helper_w6, e sha256_helper_w7, e sha256_helper_w8, e sha256_helper_w9, e sha256_init_a, e sha256_init_b, e sha256_init_c, e sha256_init_d, e sha256_init_e, e sha256_init_f, e sha256_init_g, e sha256_init_h, e sha256_input_addr, e sha256_input_rounds_rem, e sha256_output_addr, e sha256_rounds_remaining, e sha256_sel, e sha256_sel_invalid_input_tag_err, e sha256_space_id, e sha256_start, e to_radix_acc, e to_radix_acc_under_p, e to_radix_limb, e to_radix_limb_eq_p, e to_radix_limb_index, e to_radix_limb_lt_p, e to_radix_mem_dst_addr, e to_radix_mem_execution_clk, e to_radix_mem_is_output_bits, e to_radix_mem_num_limbs, e to_radix_mem_radix, e to_radix_mem_sel, e to_radix_mem_sel_should_decompose, e to_radix_mem_sel_should_write_mem, e to_radix_mem_space_id, e to_radix_mem_start, e to_radix_mem_value_to_decompose, e to_radix_not_padding_limb, e to_radix_power, e to_radix_radix, e to_radix_safe_limbs, e to_radix_sel, e to_radix_start, e to_radix_value, e tx_da_gas_limit, e tx_discard, e tx_fee, e tx_is_revertible, e tx_is_teardown, e tx_l1_l2_tree_root, e tx_l1_l2_tree_size, e tx_l2_gas_limit, e tx_next_context_id, e tx_phase_value, e tx_prev_da_gas_used, e tx_prev_l2_gas_used, e tx_prev_note_hash_tree_root, e tx_prev_note_hash_tree_size, e tx_prev_nullifier_tree_root, e tx_prev_nullifier_tree_size, e tx_prev_num_l2_to_l1_messages, e tx_prev_num_note_hashes_emitted, e tx_prev_num_nullifiers_emitted, e tx_prev_num_public_log_fields, e tx_prev_public_data_tree_root, e tx_prev_public_data_tree_size, e tx_prev_retrieved_bytecodes_tree_root, e tx_prev_retrieved_bytecodes_tree_size, e tx_prev_written_public_data_slots_tree_root, e tx_prev_written_public_data_slots_tree_size, e tx_read_pi_offset, e tx_remaining_phase_counter, e tx_reverted, e tx_sel, e tx_start_phase, e tx_start_tx, e tx_tx_reverted +#define AVM2_PRECOMPUTED_ENTITIES_E(e) e precomputed_addressing_gas, e precomputed_bitwise_input_a, e precomputed_bitwise_input_b, e precomputed_bitwise_output_and, e precomputed_bitwise_output_or, e precomputed_bitwise_output_xor, e precomputed_dyn_gas_id, e precomputed_envvar_pi_row_idx, e precomputed_exec_opcode, e precomputed_exec_opcode_base_da_gas, e precomputed_exec_opcode_dynamic_da_gas, e precomputed_exec_opcode_dynamic_l2_gas, e precomputed_exec_opcode_opcode_gas, e precomputed_expected_tag_reg_0_, e precomputed_expected_tag_reg_1_, e precomputed_expected_tag_reg_2_, e precomputed_expected_tag_reg_3_, e precomputed_expected_tag_reg_4_, e precomputed_expected_tag_reg_5_, e precomputed_first_row, e precomputed_idx, e precomputed_instr_size, e precomputed_invalid_envvar_enum, e precomputed_is_address, e precomputed_is_class_id, e precomputed_is_cleanup, e precomputed_is_collect_fee, e precomputed_is_dagasleft, e precomputed_is_deployer, e precomputed_is_init_hash, e precomputed_is_isstaticcall, e precomputed_is_l2gasleft, e precomputed_is_public_call_request, e precomputed_is_revertible, e precomputed_is_sender, e precomputed_is_teardown, e precomputed_is_transactionfee, e precomputed_is_tree_padding, e precomputed_is_valid_member_enum, e precomputed_keccak_round_constant, e precomputed_next_phase_on_revert, e precomputed_opcode_out_of_range, e precomputed_out_tag, e precomputed_p_decomposition_limb, e precomputed_p_decomposition_limb_index, e precomputed_p_decomposition_radix, e precomputed_power_of_2, e precomputed_read_pi_length_offset, e precomputed_read_pi_start_offset, e precomputed_rw_reg_0_, e precomputed_rw_reg_1_, e precomputed_rw_reg_2_, e precomputed_rw_reg_3_, e precomputed_rw_reg_4_, e precomputed_rw_reg_5_, e precomputed_sel_addressing_gas, e precomputed_sel_append_l2_l1_msg, e precomputed_sel_append_note_hash, e precomputed_sel_append_nullifier, e precomputed_sel_envvar_pi_lookup_col0, e precomputed_sel_envvar_pi_lookup_col1, e precomputed_sel_exec_spec, e precomputed_sel_has_tag, e precomputed_sel_keccak, e precomputed_sel_mem_op_reg_0_, e precomputed_sel_mem_op_reg_1_, e precomputed_sel_mem_op_reg_2_, e precomputed_sel_mem_op_reg_3_, e precomputed_sel_mem_op_reg_4_, e precomputed_sel_mem_op_reg_5_, e precomputed_sel_mem_tag_out_of_range, e precomputed_sel_op_dc_0, e precomputed_sel_op_dc_1, e precomputed_sel_op_dc_10, e precomputed_sel_op_dc_11, e precomputed_sel_op_dc_12, e precomputed_sel_op_dc_13, e precomputed_sel_op_dc_14, e precomputed_sel_op_dc_15, e precomputed_sel_op_dc_16, e precomputed_sel_op_dc_2, e precomputed_sel_op_dc_3, e precomputed_sel_op_dc_4, e precomputed_sel_op_dc_5, e precomputed_sel_op_dc_6, e precomputed_sel_op_dc_7, e precomputed_sel_op_dc_8, e precomputed_sel_op_dc_9, e precomputed_sel_op_is_address_0_, e precomputed_sel_op_is_address_1_, e precomputed_sel_op_is_address_2_, e precomputed_sel_op_is_address_3_, e precomputed_sel_op_is_address_4_, e precomputed_sel_op_is_address_5_, e precomputed_sel_op_is_address_6_, e precomputed_sel_p_decomposition, e precomputed_sel_phase, e precomputed_sel_range_16, e precomputed_sel_range_8, e precomputed_sel_sha256_compression, e precomputed_sel_tag_check_reg_0_, e precomputed_sel_tag_check_reg_1_, e precomputed_sel_tag_check_reg_2_, e precomputed_sel_tag_check_reg_3_, e precomputed_sel_tag_check_reg_4_, e precomputed_sel_tag_check_reg_5_, e precomputed_sel_tag_is_op2, e precomputed_sel_tag_parameters, e precomputed_sel_to_radix_p_limb_counts, e precomputed_sha256_compression_round_constant, e precomputed_subtrace_id, e precomputed_subtrace_operation_id, e precomputed_tag_byte_length, e precomputed_tag_max_bits, e precomputed_tag_max_value, e precomputed_to_radix_num_limbs_for_p, e precomputed_to_radix_safe_limbs, e precomputed_zero, e public_inputs_sel +#define AVM2_WIRE_ENTITIES_E(e) e public_inputs_cols_0_, e public_inputs_cols_1_, e public_inputs_cols_2_, e public_inputs_cols_3_, e address_derivation_address, e address_derivation_address_y, e address_derivation_class_id, e address_derivation_const_four, e address_derivation_const_thirteen, e address_derivation_const_three, e address_derivation_const_two, e address_derivation_deployer_addr, e address_derivation_g1_x, e address_derivation_g1_y, e address_derivation_incoming_viewing_key_x, e address_derivation_incoming_viewing_key_y, e address_derivation_init_hash, e address_derivation_nullifier_key_x, e address_derivation_nullifier_key_y, e address_derivation_outgoing_viewing_key_x, e address_derivation_outgoing_viewing_key_y, e address_derivation_partial_address, e address_derivation_partial_address_domain_separator, e address_derivation_preaddress, e address_derivation_preaddress_domain_separator, e address_derivation_preaddress_public_key_x, e address_derivation_preaddress_public_key_y, e address_derivation_public_keys_hash, e address_derivation_public_keys_hash_domain_separator, e address_derivation_salt, e address_derivation_salted_init_hash, e address_derivation_salted_init_hash_domain_separator, e address_derivation_sel, e address_derivation_tagging_key_x, e address_derivation_tagging_key_y, e alu_a_hi, e alu_a_hi_bits, e alu_a_lo, e alu_a_lo_bits, e alu_ab_diff_inv, e alu_ab_tags_diff_inv, e alu_b_hi, e alu_b_inv, e alu_b_lo, e alu_c_hi, e alu_cf, e alu_constant_64, e alu_gt_input_a, e alu_gt_input_b, e alu_gt_result_c, e alu_helper1, e alu_ia, e alu_ia_tag, e alu_ib, e alu_ib_tag, e alu_ic, e alu_ic_tag, e alu_max_bits, e alu_max_value, e alu_mid, e alu_mid_bits, e alu_op_id, e alu_sel, e alu_sel_ab_tag_mismatch, e alu_sel_decompose_a, e alu_sel_div_0_err, e alu_sel_div_no_err, e alu_sel_err, e alu_sel_ff_gt, e alu_sel_int_gt, e alu_sel_is_ff, e alu_sel_is_u128, e alu_sel_mul_div_u128, e alu_sel_mul_no_err_non_ff, e alu_sel_op_add, e alu_sel_op_div, e alu_sel_op_eq, e alu_sel_op_fdiv, e alu_sel_op_lt, e alu_sel_op_lte, e alu_sel_op_mul, e alu_sel_op_not, e alu_sel_op_shl, e alu_sel_op_shr, e alu_sel_op_sub, e alu_sel_op_truncate, e alu_sel_shift_ops_no_overflow, e alu_sel_tag_err, e alu_sel_trunc_gte_128, e alu_sel_trunc_lt_128, e alu_sel_trunc_non_trivial, e alu_sel_trunc_trivial, e alu_shift_lo_bits, e alu_tag_ff_diff_inv, e alu_tag_u128_diff_inv, e alu_two_pow_shift_lo_bits, e bc_decomposition_bytes_pc_plus_36, e bc_decomposition_bytes_rem_inv, e bc_decomposition_bytes_rem_min_one_inv, e bc_decomposition_bytes_to_read, e bc_decomposition_last_of_contract, e bc_decomposition_next_packed_pc_min_pc_inv, e bc_decomposition_packed_field, e bc_decomposition_sel_packed, e bc_decomposition_sel_packed_read_0_, e bc_decomposition_sel_packed_read_1_, e bc_decomposition_sel_packed_read_2_, e bc_decomposition_sel_windows_eq_remaining, e bc_decomposition_windows_min_remaining_inv, e bc_hashing_end, e bc_hashing_input_len, e bc_hashing_packed_fields_0, e bc_hashing_packed_fields_1, e bc_hashing_packed_fields_2, e bc_hashing_pc_index, e bc_hashing_pc_index_2, e bc_hashing_sel_not_padding_1, e bc_hashing_sel_not_padding_2, e bc_hashing_size_in_bytes, e bc_retrieval_address, e bc_retrieval_artifact_hash, e bc_retrieval_bytecode_id, e bc_retrieval_current_class_id, e bc_retrieval_error, e bc_retrieval_instance_exists, e bc_retrieval_is_new_class, e bc_retrieval_next_retrieved_bytecodes_tree_root, e bc_retrieval_next_retrieved_bytecodes_tree_size, e bc_retrieval_no_remaining_bytecodes, e bc_retrieval_nullifier_tree_root, e bc_retrieval_prev_retrieved_bytecodes_tree_root, e bc_retrieval_prev_retrieved_bytecodes_tree_size, e bc_retrieval_private_functions_root, e bc_retrieval_public_data_tree_root, e bc_retrieval_remaining_bytecodes_inv, e bc_retrieval_retrieved_bytecodes_merkle_separator, e bc_retrieval_retrieved_bytecodes_tree_height, e bc_retrieval_sel, e bc_retrieval_should_retrieve, e bitwise_ctr_min_one_inv, e bitwise_end, e bitwise_err, e bitwise_ia_byte, e bitwise_ib_byte, e bitwise_ic_byte, e bitwise_output_and, e bitwise_output_or, e bitwise_output_xor, e bitwise_sel_and, e bitwise_sel_compute, e bitwise_sel_get_ctr, e bitwise_sel_or, e bitwise_sel_tag_ff_err, e bitwise_sel_tag_mismatch_err, e bitwise_sel_xor, e bitwise_start_keccak, e bitwise_start_sha256, e bitwise_tag_a, e bitwise_tag_a_inv, e bitwise_tag_ab_diff_inv, e bitwise_tag_b, e bitwise_tag_c, e calldata_end, e calldata_hashing_end, e calldata_hashing_index_1_, e calldata_hashing_index_2_, e calldata_hashing_input_0_, e calldata_hashing_input_1_, e calldata_hashing_input_2_, e calldata_hashing_input_len, e calldata_hashing_sel_end_not_empty, e calldata_hashing_sel_not_padding_1, e calldata_hashing_sel_not_padding_2, e calldata_hashing_sel_not_start, e calldata_value, e class_id_derivation_artifact_hash, e class_id_derivation_class_id, e class_id_derivation_const_four, e class_id_derivation_gen_index_contract_class_id, e class_id_derivation_private_functions_root, e class_id_derivation_public_bytecode_commitment, e class_id_derivation_sel, e context_stack_bytecode_id, e context_stack_context_id, e context_stack_contract_address, e context_stack_entered_context_id, e context_stack_internal_call_id, e context_stack_internal_call_return_id, e context_stack_is_static, e context_stack_msg_sender, e context_stack_next_internal_call_id, e context_stack_next_pc, e context_stack_note_hash_tree_root, e context_stack_note_hash_tree_size, e context_stack_nullifier_tree_root, e context_stack_nullifier_tree_size, e context_stack_num_l2_to_l1_messages, e context_stack_num_note_hashes_emitted, e context_stack_num_nullifiers_emitted, e context_stack_num_public_log_fields, e context_stack_parent_calldata_addr, e context_stack_parent_calldata_size, e context_stack_parent_da_gas_limit, e context_stack_parent_da_gas_used, e context_stack_parent_id, e context_stack_parent_l2_gas_limit, e context_stack_parent_l2_gas_used, e context_stack_public_data_tree_root, e context_stack_public_data_tree_size, e context_stack_sel, e context_stack_written_public_data_slots_tree_root, e context_stack_written_public_data_slots_tree_size, e contract_instance_retrieval_address, e contract_instance_retrieval_address_sub_one, e contract_instance_retrieval_current_class_id, e contract_instance_retrieval_deployer_addr, e contract_instance_retrieval_deployer_protocol_contract_address, e contract_instance_retrieval_derived_address, e contract_instance_retrieval_derived_address_pi_index, e contract_instance_retrieval_exists, e contract_instance_retrieval_incoming_viewing_key_x, e contract_instance_retrieval_incoming_viewing_key_y, e contract_instance_retrieval_init_hash, e contract_instance_retrieval_is_protocol_contract, e contract_instance_retrieval_max_protocol_contracts, e contract_instance_retrieval_nullifier_key_x, e contract_instance_retrieval_nullifier_key_y, e contract_instance_retrieval_nullifier_merkle_separator, e contract_instance_retrieval_nullifier_tree_height, e contract_instance_retrieval_nullifier_tree_root, e contract_instance_retrieval_original_class_id, e contract_instance_retrieval_outgoing_viewing_key_x, e contract_instance_retrieval_outgoing_viewing_key_y, e contract_instance_retrieval_protocol_contract_derived_address_inv, e contract_instance_retrieval_public_data_tree_root, e contract_instance_retrieval_salt, e contract_instance_retrieval_sel, e contract_instance_retrieval_should_check_for_update, e contract_instance_retrieval_should_check_nullifier, e contract_instance_retrieval_siloing_separator, e contract_instance_retrieval_tagging_key_x, e contract_instance_retrieval_tagging_key_y, e data_copy_cd_copy_col_read, e data_copy_clamped_read_index_upper_bound, e data_copy_dst_out_of_range_err, e data_copy_end, e data_copy_is_top_level, e data_copy_mem_size, e data_copy_offset, e data_copy_offset_plus_size, e data_copy_offset_plus_size_is_gt, e data_copy_parent_id_inv, e data_copy_read_addr_plus_one, e data_copy_read_addr_upper_bound, e data_copy_reads_left_inv, e data_copy_sel_cd_copy_start, e data_copy_sel_has_reads, e data_copy_sel_mem_read, e data_copy_sel_mem_write, e data_copy_sel_rd_copy_start, e data_copy_sel_write_count_is_zero, e data_copy_src_addr, e data_copy_src_data_size, e data_copy_src_reads_exceed_mem, e data_copy_start_no_err, e data_copy_tag, e data_copy_value, e data_copy_write_addr_upper_bound, e data_copy_write_count_minus_one_inv, e data_copy_write_count_zero_inv, e ecc_add_mem_dst_addr_0_, e ecc_add_mem_dst_addr_1_, e ecc_add_mem_dst_addr_2_, e ecc_add_mem_err, e ecc_add_mem_execution_clk, e ecc_add_mem_max_mem_addr, e ecc_add_mem_p_is_inf, e ecc_add_mem_p_is_on_curve_eqn, e ecc_add_mem_p_is_on_curve_eqn_inv, e ecc_add_mem_p_x, e ecc_add_mem_p_x_n, e ecc_add_mem_p_y, e ecc_add_mem_p_y_n, e ecc_add_mem_q_is_inf, e ecc_add_mem_q_is_on_curve_eqn, e ecc_add_mem_q_is_on_curve_eqn_inv, e ecc_add_mem_q_x, e ecc_add_mem_q_x_n, e ecc_add_mem_q_y, e ecc_add_mem_q_y_n, e ecc_add_mem_res_is_inf, e ecc_add_mem_res_x, e ecc_add_mem_res_y, e ecc_add_mem_sel, e ecc_add_mem_sel_dst_out_of_range_err, e ecc_add_mem_sel_p_not_on_curve_err, e ecc_add_mem_sel_q_not_on_curve_err, e ecc_add_mem_sel_should_exec, e ecc_add_mem_space_id, e ecc_add_op, e ecc_double_op, e ecc_inv_2_p_y, e ecc_inv_x_diff, e ecc_inv_y_diff, e ecc_lambda, e ecc_p_is_inf, e ecc_p_x, e ecc_p_y, e ecc_q_is_inf, e ecc_q_x, e ecc_q_y, e ecc_r_is_inf, e ecc_r_x, e ecc_r_y, e ecc_result_infinity, e ecc_sel, e ecc_use_computed_result, e ecc_x_match, e ecc_y_match, e emit_public_log_discard, e emit_public_log_end, e emit_public_log_end_log_address_upper_bound, e emit_public_log_error, e emit_public_log_error_too_many_log_fields, e emit_public_log_expected_next_log_fields, e emit_public_log_is_static, e emit_public_log_log_size, e emit_public_log_max_mem_size, e emit_public_log_max_public_logs_payload_length, e emit_public_log_next_num_public_log_fields, e emit_public_log_prev_num_public_log_fields, e emit_public_log_public_inputs_value, e emit_public_log_remaining_rows_inv, e emit_public_log_sel_read_memory, e emit_public_log_tag, e emit_public_log_tag_inv, e emit_public_log_value, e execution_addressing_error_collection_inv, e execution_addressing_gas, e execution_addressing_mode, e execution_base_address_tag, e execution_base_address_tag_diff_inv, e execution_base_address_val, e execution_base_da_gas, e execution_batched_tags_diff_inv, e execution_batched_tags_diff_inv_reg, e execution_da_gas_left, e execution_da_gas_used, e execution_dying_context_diff_inv, e execution_dying_context_id_inv, e execution_dyn_gas_id, e execution_dynamic_da_gas, e execution_dynamic_da_gas_factor, e execution_dynamic_l2_gas, e execution_dynamic_l2_gas_factor, e execution_enqueued_call_end, e execution_envvar_pi_row_idx, e execution_exec_opcode, e execution_expected_tag_reg_0_, e execution_expected_tag_reg_1_, e execution_expected_tag_reg_2_, e execution_expected_tag_reg_3_, e execution_expected_tag_reg_4_, e execution_expected_tag_reg_5_, e execution_has_parent_ctx, e execution_highest_address, e execution_instr_size, e execution_internal_call_return_id_inv, e execution_is_address, e execution_is_da_gas_left_gt_allocated, e execution_is_dagasleft, e execution_is_dying_context, e execution_is_isstaticcall, e execution_is_l2_gas_left_gt_allocated, e execution_is_l2gasleft, e execution_is_parent_id_inv, e execution_is_sender, e execution_is_transactionfee, e execution_l1_to_l2_msg_leaf_in_range, e execution_l1_to_l2_msg_tree_leaf_count, e execution_l2_gas_left, e execution_l2_gas_used, e execution_max_data_writes_reached, e execution_max_eth_address_value, e execution_mem_tag_reg_0_, e execution_mem_tag_reg_1_, e execution_mem_tag_reg_2_, e execution_mem_tag_reg_3_, e execution_mem_tag_reg_4_, e execution_mem_tag_reg_5_, e execution_nested_failure, e execution_nested_return, e execution_next_pc, e execution_note_hash_leaf_in_range, e execution_note_hash_tree_leaf_count, e execution_note_hash_tree_root, e execution_note_hash_tree_size, e execution_nullifier_merkle_separator, e execution_nullifier_pi_offset, e execution_nullifier_siloing_separator, e execution_nullifier_tree_height, e execution_nullifier_tree_root, e execution_nullifier_tree_size, e execution_num_l2_to_l1_messages, e execution_num_note_hashes_emitted, e execution_num_nullifiers_emitted, e execution_num_p_limbs, e execution_num_public_log_fields, e execution_num_relative_operands_inv, e execution_op_0_, e execution_op_1_, e execution_op_2_, e execution_op_3_, e execution_op_4_, e execution_op_5_, e execution_op_6_, e execution_op_after_relative_0_, e execution_op_after_relative_1_, e execution_op_after_relative_2_, e execution_op_after_relative_3_, e execution_op_after_relative_4_, e execution_op_after_relative_5_, e execution_op_after_relative_6_, e execution_opcode_gas, e execution_out_of_gas_da, e execution_out_of_gas_l2, e execution_public_data_tree_root, e execution_public_data_tree_size, e execution_public_inputs_index, e execution_register_0_, e execution_register_1_, e execution_register_2_, e execution_register_3_, e execution_register_4_, e execution_register_5_, e execution_remaining_data_writes_inv, e execution_remaining_l2_to_l1_msgs_inv, e execution_remaining_note_hashes_inv, e execution_remaining_nullifiers_inv, e execution_retrieved_bytecodes_tree_root, e execution_retrieved_bytecodes_tree_size, e execution_rop_0_, e execution_rop_1_, e execution_rop_2_, e execution_rop_3_, e execution_rop_4_, e execution_rop_5_, e execution_rop_6_, e execution_rop_tag_0_, e execution_rop_tag_1_, e execution_rop_tag_2_, e execution_rop_tag_3_, e execution_rop_tag_4_, e execution_rop_tag_5_, e execution_rop_tag_6_, e execution_rw_reg_0_, e execution_rw_reg_1_, e execution_rw_reg_2_, e execution_rw_reg_3_, e execution_rw_reg_4_, e execution_rw_reg_5_, e execution_sel_addressing_error, e execution_sel_apply_indirection_0_, e execution_sel_apply_indirection_1_, e execution_sel_apply_indirection_2_, e execution_sel_apply_indirection_3_, e execution_sel_apply_indirection_4_, e execution_sel_apply_indirection_5_, e execution_sel_apply_indirection_6_, e execution_sel_base_address_failure, e execution_sel_bytecode_retrieval_failure, e execution_sel_bytecode_retrieval_success, e execution_sel_check_gas, e execution_sel_do_base_check, e execution_sel_enter_call, e execution_sel_envvar_pi_lookup_col0, e execution_sel_envvar_pi_lookup_col1, e execution_sel_error, e execution_sel_exec_dispatch_alu, e execution_sel_exec_dispatch_bitwise, e execution_sel_exec_dispatch_calldata_copy, e execution_sel_exec_dispatch_cast, e execution_sel_exec_dispatch_ecc_add, e execution_sel_exec_dispatch_emit_public_log, e execution_sel_exec_dispatch_execution, e execution_sel_exec_dispatch_get_contract_instance, e execution_sel_exec_dispatch_keccakf1600, e execution_sel_exec_dispatch_poseidon2_perm, e execution_sel_exec_dispatch_returndata_copy, e execution_sel_exec_dispatch_set, e execution_sel_exec_dispatch_sha256_compression, e execution_sel_exec_dispatch_to_radix, e execution_sel_execute_call, e execution_sel_execute_debug_log, e execution_sel_execute_emit_notehash, e execution_sel_execute_emit_nullifier, e execution_sel_execute_get_env_var, e execution_sel_execute_internal_call, e execution_sel_execute_internal_return, e execution_sel_execute_jump, e execution_sel_execute_jumpi, e execution_sel_execute_l1_to_l2_message_exists, e execution_sel_execute_mov, e execution_sel_execute_notehash_exists, e execution_sel_execute_nullifier_exists, e execution_sel_execute_opcode, e execution_sel_execute_return, e execution_sel_execute_returndata_size, e execution_sel_execute_revert, e execution_sel_execute_send_l2_to_l1_msg, e execution_sel_execute_sload, e execution_sel_execute_sstore, e execution_sel_execute_static_call, e execution_sel_execute_success_copy, e execution_sel_exit_call, e execution_sel_failure, e execution_sel_gas_bitwise, e execution_sel_gas_calldata_copy, e execution_sel_gas_emit_public_log, e execution_sel_gas_returndata_copy, e execution_sel_gas_sstore, e execution_sel_gas_to_radix, e execution_sel_instruction_fetching_failure, e execution_sel_instruction_fetching_success, e execution_sel_l2_to_l1_msg_limit_error, e execution_sel_lookup_num_p_limbs, e execution_sel_mem_op_reg_0_, e execution_sel_mem_op_reg_1_, e execution_sel_mem_op_reg_2_, e execution_sel_mem_op_reg_3_, e execution_sel_mem_op_reg_4_, e execution_sel_mem_op_reg_5_, e execution_sel_op_do_overflow_check_0_, e execution_sel_op_do_overflow_check_1_, e execution_sel_op_do_overflow_check_2_, e execution_sel_op_do_overflow_check_3_, e execution_sel_op_do_overflow_check_4_, e execution_sel_op_do_overflow_check_5_, e execution_sel_op_do_overflow_check_6_, e execution_sel_op_is_address_0_, e execution_sel_op_is_address_1_, e execution_sel_op_is_address_2_, e execution_sel_op_is_address_3_, e execution_sel_op_is_address_4_, e execution_sel_op_is_address_5_, e execution_sel_op_is_address_6_, e execution_sel_op_is_indirect_wire_0_, e execution_sel_op_is_indirect_wire_1_, e execution_sel_op_is_indirect_wire_2_, e execution_sel_op_is_indirect_wire_3_, e execution_sel_op_is_indirect_wire_4_, e execution_sel_op_is_indirect_wire_5_, e execution_sel_op_is_indirect_wire_6_, e execution_sel_op_is_indirect_wire_7_, e execution_sel_op_is_relative_wire_0_, e execution_sel_op_is_relative_wire_1_, e execution_sel_op_is_relative_wire_2_, e execution_sel_op_is_relative_wire_3_, e execution_sel_op_is_relative_wire_4_, e execution_sel_op_is_relative_wire_5_, e execution_sel_op_is_relative_wire_6_, e execution_sel_op_is_relative_wire_7_, e execution_sel_op_reg_effective_0_, e execution_sel_op_reg_effective_1_, e execution_sel_op_reg_effective_2_, e execution_sel_op_reg_effective_3_, e execution_sel_op_reg_effective_4_, e execution_sel_op_reg_effective_5_, e execution_sel_opcode_error, e execution_sel_out_of_gas, e execution_sel_radix_gt_256, e execution_sel_reached_max_note_hashes, e execution_sel_reached_max_nullifiers, e execution_sel_read_registers, e execution_sel_read_unwind_call_stack, e execution_sel_register_read_error, e execution_sel_relative_overflow_0_, e execution_sel_relative_overflow_1_, e execution_sel_relative_overflow_2_, e execution_sel_relative_overflow_3_, e execution_sel_relative_overflow_4_, e execution_sel_relative_overflow_5_, e execution_sel_relative_overflow_6_, e execution_sel_some_final_check_failed, e execution_sel_tag_check_reg_0_, e execution_sel_tag_check_reg_1_, e execution_sel_tag_check_reg_2_, e execution_sel_tag_check_reg_3_, e execution_sel_tag_check_reg_4_, e execution_sel_tag_check_reg_5_, e execution_sel_too_large_recipient_error, e execution_sel_use_num_limbs, e execution_sel_write_l2_to_l1_msg, e execution_sel_write_note_hash, e execution_sel_write_nullifier, e execution_sel_write_public_data, e execution_sel_write_registers, e execution_subtrace_id, e execution_subtrace_operation_id, e execution_total_gas_da, e execution_total_gas_l2, e execution_two_five_six, e execution_value_from_pi, e execution_written_public_data_slots_tree_root, e execution_written_public_data_slots_tree_size, e execution_written_slots_merkle_separator, e execution_written_slots_tree_height, e execution_written_slots_tree_siloing_separator, e ff_gt_a, e ff_gt_b, e ff_gt_borrow, e ff_gt_constant_128, e ff_gt_end, e ff_gt_p_a_borrow, e ff_gt_p_b_borrow, e ff_gt_res_hi, e ff_gt_res_lo, e ff_gt_result, e get_contract_instance_clk, e get_contract_instance_contract_address, e get_contract_instance_dst_offset, e get_contract_instance_dst_offset_diff_max_inv, e get_contract_instance_exists_tag, e get_contract_instance_instance_exists, e get_contract_instance_is_class_id, e get_contract_instance_is_deployer, e get_contract_instance_is_init_hash, e get_contract_instance_is_valid_member_enum, e get_contract_instance_is_valid_writes_in_bounds, e get_contract_instance_member_enum, e get_contract_instance_member_tag, e get_contract_instance_member_write_offset, e get_contract_instance_nullifier_tree_root, e get_contract_instance_public_data_tree_root, e get_contract_instance_retrieved_class_id, e get_contract_instance_retrieved_deployer_addr, e get_contract_instance_retrieved_init_hash, e get_contract_instance_sel, e get_contract_instance_sel_error, e get_contract_instance_selected_member, e get_contract_instance_space_id, e gt_abs_diff, e gt_input_a, e gt_input_b, e gt_num_bits, e gt_res, e gt_sel, e gt_sel_addressing, e gt_sel_alu, e gt_sel_gas, e gt_sel_others, e gt_sel_sha256, e indexed_tree_check_address, e indexed_tree_check_const_three, e indexed_tree_check_discard, e indexed_tree_check_exists, e indexed_tree_check_intermediate_root, e indexed_tree_check_low_leaf_hash, e indexed_tree_check_low_leaf_index, e indexed_tree_check_low_leaf_next_index, e indexed_tree_check_low_leaf_next_value, e indexed_tree_check_low_leaf_value, e indexed_tree_check_merkle_hash_separator, e indexed_tree_check_new_leaf_hash, e indexed_tree_check_next_value_inv, e indexed_tree_check_next_value_is_nonzero, e indexed_tree_check_not_exists, e indexed_tree_check_public_inputs_index, e indexed_tree_check_root, e indexed_tree_check_sel, e indexed_tree_check_sel_insert, e indexed_tree_check_sel_silo, e indexed_tree_check_sel_write_to_public_inputs, e indexed_tree_check_siloed_value, e indexed_tree_check_siloing_separator, e indexed_tree_check_tree_height, e indexed_tree_check_tree_size_after_write, e indexed_tree_check_tree_size_before_write, e indexed_tree_check_updated_low_leaf_hash, e indexed_tree_check_updated_low_leaf_next_index, e indexed_tree_check_updated_low_leaf_next_value, e indexed_tree_check_value, e indexed_tree_check_value_low_leaf_value_diff_inv, e indexed_tree_check_write, e indexed_tree_check_write_root, e instr_fetching_addressing_mode, e instr_fetching_bd0, e instr_fetching_bd1, e instr_fetching_bd10, e instr_fetching_bd11, e instr_fetching_bd12, e instr_fetching_bd13, e instr_fetching_bd14, e instr_fetching_bd15, e instr_fetching_bd16, e instr_fetching_bd17, e instr_fetching_bd18, e instr_fetching_bd19, e instr_fetching_bd2, e instr_fetching_bd20, e instr_fetching_bd21, e instr_fetching_bd22, e instr_fetching_bd23, e instr_fetching_bd24, e instr_fetching_bd25, e instr_fetching_bd26, e instr_fetching_bd27, e instr_fetching_bd28, e instr_fetching_bd29, e instr_fetching_bd3, e instr_fetching_bd30, e instr_fetching_bd31, e instr_fetching_bd32, e instr_fetching_bd33, e instr_fetching_bd34, e instr_fetching_bd35, e instr_fetching_bd36, e instr_fetching_bd4, e instr_fetching_bd5, e instr_fetching_bd6, e instr_fetching_bd7, e instr_fetching_bd8, e instr_fetching_bd9, e instr_fetching_bytecode_id, e instr_fetching_bytecode_size, e instr_fetching_bytes_to_read, e instr_fetching_exec_opcode, e instr_fetching_instr_abs_diff, e instr_fetching_instr_out_of_range, e instr_fetching_instr_size, e instr_fetching_op1, e instr_fetching_op2, e instr_fetching_op3, e instr_fetching_op4, e instr_fetching_op5, e instr_fetching_op6, e instr_fetching_op7, e instr_fetching_opcode_out_of_range, e instr_fetching_pc, e instr_fetching_pc_abs_diff, e instr_fetching_pc_out_of_range, e instr_fetching_pc_size_in_bits, e instr_fetching_sel, e instr_fetching_sel_has_tag, e instr_fetching_sel_op_dc_0, e instr_fetching_sel_op_dc_1, e instr_fetching_sel_op_dc_10, e instr_fetching_sel_op_dc_11, e instr_fetching_sel_op_dc_12, e instr_fetching_sel_op_dc_13, e instr_fetching_sel_op_dc_14, e instr_fetching_sel_op_dc_15, e instr_fetching_sel_op_dc_16, e instr_fetching_sel_op_dc_2, e instr_fetching_sel_op_dc_3, e instr_fetching_sel_op_dc_4, e instr_fetching_sel_op_dc_5, e instr_fetching_sel_op_dc_6, e instr_fetching_sel_op_dc_7, e instr_fetching_sel_op_dc_8, e instr_fetching_sel_op_dc_9, e instr_fetching_sel_parsing_err, e instr_fetching_sel_pc_in_range, e instr_fetching_sel_tag_is_op2, e instr_fetching_tag_out_of_range, e instr_fetching_tag_value, e internal_call_stack_call_id, e internal_call_stack_context_id, e internal_call_stack_entered_call_id, e internal_call_stack_return_call_id, e internal_call_stack_return_pc, e internal_call_stack_sel, e keccak_memory_ctr_end, e keccak_memory_end, e keccak_memory_single_tag_error, e keccak_memory_state_size_min_ctr_inv, e keccak_memory_tag, e keccak_memory_tag_min_u64_inv, e keccak_memory_val_24_, e keccakf1600_bitwise_and_op_id, e keccakf1600_bitwise_xor_op_id, e keccakf1600_dst_out_of_range_error, e keccakf1600_end, e keccakf1600_error, e keccakf1600_highest_slice_address, e keccakf1600_rot_64_min_len_01, e keccakf1600_rot_64_min_len_03, e keccakf1600_rot_64_min_len_11, e keccakf1600_rot_64_min_len_13, e keccakf1600_rot_64_min_len_20, e keccakf1600_rot_64_min_len_22, e keccakf1600_rot_64_min_len_24, e keccakf1600_rot_64_min_len_31, e keccakf1600_rot_64_min_len_34, e keccakf1600_rot_64_min_len_42, e keccakf1600_rot_len_02, e keccakf1600_rot_len_04, e keccakf1600_rot_len_10, e keccakf1600_rot_len_12, e keccakf1600_rot_len_14, e keccakf1600_rot_len_21, e keccakf1600_rot_len_23, e keccakf1600_rot_len_30, e keccakf1600_rot_len_32, e keccakf1600_rot_len_33, e keccakf1600_rot_len_40, e keccakf1600_rot_len_41, e keccakf1600_rot_len_43, e keccakf1600_rot_len_44, e keccakf1600_round_cst, e keccakf1600_sel_slice_read, e keccakf1600_sel_slice_write, e keccakf1600_src_addr, e keccakf1600_src_out_of_range_error, e keccakf1600_state_chi_00, e keccakf1600_state_chi_01, e keccakf1600_state_chi_02, e keccakf1600_state_chi_03, e keccakf1600_state_chi_04, e keccakf1600_state_chi_10, e keccakf1600_state_chi_11, e keccakf1600_state_chi_12, e keccakf1600_state_chi_13, e keccakf1600_state_chi_14, e keccakf1600_state_chi_20, e keccakf1600_state_chi_21, e keccakf1600_state_chi_22, e keccakf1600_state_chi_23, e keccakf1600_state_chi_24, e keccakf1600_state_chi_30, e keccakf1600_state_chi_31, e keccakf1600_state_chi_32, e keccakf1600_state_chi_33, e keccakf1600_state_chi_34, e keccakf1600_state_chi_40, e keccakf1600_state_chi_41, e keccakf1600_state_chi_42, e keccakf1600_state_chi_43, e keccakf1600_state_chi_44, e keccakf1600_state_iota_00, e keccakf1600_state_pi_and_00, e keccakf1600_state_pi_and_01, e keccakf1600_state_pi_and_02, e keccakf1600_state_pi_and_03, e keccakf1600_state_pi_and_04, e keccakf1600_state_pi_and_10, e keccakf1600_state_pi_and_11, e keccakf1600_state_pi_and_12, e keccakf1600_state_pi_and_13, e keccakf1600_state_pi_and_14, e keccakf1600_state_pi_and_20, e keccakf1600_state_pi_and_21, e keccakf1600_state_pi_and_22, e keccakf1600_state_pi_and_23, e keccakf1600_state_pi_and_24, e keccakf1600_state_pi_and_30, e keccakf1600_state_pi_and_31, e keccakf1600_state_pi_and_32, e keccakf1600_state_pi_and_33, e keccakf1600_state_pi_and_34, e keccakf1600_state_pi_and_40, e keccakf1600_state_pi_and_41, e keccakf1600_state_pi_and_42, e keccakf1600_state_pi_and_43, e keccakf1600_state_pi_and_44, e keccakf1600_state_pi_not_00, e keccakf1600_state_pi_not_01, e keccakf1600_state_pi_not_02, e keccakf1600_state_pi_not_03, e keccakf1600_state_pi_not_04, e keccakf1600_state_pi_not_10, e keccakf1600_state_pi_not_11, e keccakf1600_state_pi_not_12, e keccakf1600_state_pi_not_13, e keccakf1600_state_pi_not_14, e keccakf1600_state_pi_not_20, e keccakf1600_state_pi_not_21, e keccakf1600_state_pi_not_22, e keccakf1600_state_pi_not_23, e keccakf1600_state_pi_not_24, e keccakf1600_state_pi_not_30, e keccakf1600_state_pi_not_31, e keccakf1600_state_pi_not_32, e keccakf1600_state_pi_not_33, e keccakf1600_state_pi_not_34, e keccakf1600_state_pi_not_40, e keccakf1600_state_pi_not_41, e keccakf1600_state_pi_not_42, e keccakf1600_state_pi_not_43, e keccakf1600_state_pi_not_44, e keccakf1600_state_rho_01, e keccakf1600_state_rho_02, e keccakf1600_state_rho_03, e keccakf1600_state_rho_04, e keccakf1600_state_rho_10, e keccakf1600_state_rho_11, e keccakf1600_state_rho_12, e keccakf1600_state_rho_13, e keccakf1600_state_rho_14, e keccakf1600_state_rho_20, e keccakf1600_state_rho_21, e keccakf1600_state_rho_22, e keccakf1600_state_rho_23, e keccakf1600_state_rho_24, e keccakf1600_state_rho_30, e keccakf1600_state_rho_31, e keccakf1600_state_rho_32, e keccakf1600_state_rho_33, e keccakf1600_state_rho_34, e keccakf1600_state_rho_40, e keccakf1600_state_rho_41, e keccakf1600_state_rho_42, e keccakf1600_state_rho_43, e keccakf1600_state_rho_44, e keccakf1600_state_theta_00, e keccakf1600_state_theta_01, e keccakf1600_state_theta_02, e keccakf1600_state_theta_03, e keccakf1600_state_theta_04, e keccakf1600_state_theta_10, e keccakf1600_state_theta_11, e keccakf1600_state_theta_12, e keccakf1600_state_theta_13, e keccakf1600_state_theta_14, e keccakf1600_state_theta_20, e keccakf1600_state_theta_21, e keccakf1600_state_theta_22, e keccakf1600_state_theta_23, e keccakf1600_state_theta_24, e keccakf1600_state_theta_30, e keccakf1600_state_theta_31, e keccakf1600_state_theta_32, e keccakf1600_state_theta_33, e keccakf1600_state_theta_34, e keccakf1600_state_theta_40, e keccakf1600_state_theta_41, e keccakf1600_state_theta_42, e keccakf1600_state_theta_43, e keccakf1600_state_theta_44, e keccakf1600_state_theta_hi_02, e keccakf1600_state_theta_hi_04, e keccakf1600_state_theta_hi_10, e keccakf1600_state_theta_hi_12, e keccakf1600_state_theta_hi_14, e keccakf1600_state_theta_hi_21, e keccakf1600_state_theta_hi_23, e keccakf1600_state_theta_hi_30, e keccakf1600_state_theta_hi_32, e keccakf1600_state_theta_hi_33, e keccakf1600_state_theta_hi_40, e keccakf1600_state_theta_hi_41, e keccakf1600_state_theta_hi_43, e keccakf1600_state_theta_hi_44, e keccakf1600_state_theta_low_01, e keccakf1600_state_theta_low_03, e keccakf1600_state_theta_low_11, e keccakf1600_state_theta_low_13, e keccakf1600_state_theta_low_20, e keccakf1600_state_theta_low_22, e keccakf1600_state_theta_low_24, e keccakf1600_state_theta_low_31, e keccakf1600_state_theta_low_34, e keccakf1600_state_theta_low_42, e keccakf1600_tag_error, e keccakf1600_tag_u64, e keccakf1600_theta_combined_xor_0, e keccakf1600_theta_combined_xor_1, e keccakf1600_theta_combined_xor_2, e keccakf1600_theta_combined_xor_3, e keccakf1600_theta_combined_xor_4, e keccakf1600_theta_xor_01, e keccakf1600_theta_xor_02, e keccakf1600_theta_xor_03, e keccakf1600_theta_xor_11, e keccakf1600_theta_xor_12, e keccakf1600_theta_xor_13, e keccakf1600_theta_xor_21, e keccakf1600_theta_xor_22, e keccakf1600_theta_xor_23, e keccakf1600_theta_xor_31, e keccakf1600_theta_xor_32, e keccakf1600_theta_xor_33, e keccakf1600_theta_xor_41, e keccakf1600_theta_xor_42, e keccakf1600_theta_xor_43, e keccakf1600_theta_xor_row_0, e keccakf1600_theta_xor_row_1, e keccakf1600_theta_xor_row_2, e keccakf1600_theta_xor_row_3, e keccakf1600_theta_xor_row_4, e keccakf1600_theta_xor_row_msb_0, e keccakf1600_theta_xor_row_msb_1, e keccakf1600_theta_xor_row_msb_2, e keccakf1600_theta_xor_row_msb_3, e keccakf1600_theta_xor_row_msb_4, e keccakf1600_theta_xor_row_rotl1_0, e keccakf1600_theta_xor_row_rotl1_1, e keccakf1600_theta_xor_row_rotl1_2, e keccakf1600_theta_xor_row_rotl1_3, e keccakf1600_theta_xor_row_rotl1_4, e l1_to_l2_message_tree_check_exists, e l1_to_l2_message_tree_check_l1_to_l2_message_tree_height, e l1_to_l2_message_tree_check_leaf_index, e l1_to_l2_message_tree_check_leaf_value, e l1_to_l2_message_tree_check_leaf_value_msg_hash_diff_inv, e l1_to_l2_message_tree_check_merkle_hash_separator, e l1_to_l2_message_tree_check_msg_hash, e l1_to_l2_message_tree_check_root, e l1_to_l2_message_tree_check_sel, e memory_diff, e memory_glob_addr_diff_inv, e memory_last_access, e memory_limb_0_, e memory_limb_1_, e memory_limb_2_, e memory_max_bits, e memory_sel_addressing_base, e memory_sel_addressing_indirect_0_, e memory_sel_addressing_indirect_1_, e memory_sel_addressing_indirect_2_, e memory_sel_addressing_indirect_3_, e memory_sel_addressing_indirect_4_, e memory_sel_addressing_indirect_5_, e memory_sel_addressing_indirect_6_, e memory_sel_data_copy_read, e memory_sel_data_copy_write, e memory_sel_ecc_write_0_, e memory_sel_ecc_write_1_, e memory_sel_ecc_write_2_, e memory_sel_get_contract_instance_exists_write, e memory_sel_get_contract_instance_member_write, e memory_sel_keccak, e memory_sel_poseidon2_read_0_, e memory_sel_poseidon2_read_1_, e memory_sel_poseidon2_read_2_, e memory_sel_poseidon2_read_3_, e memory_sel_poseidon2_write_0_, e memory_sel_poseidon2_write_1_, e memory_sel_poseidon2_write_2_, e memory_sel_poseidon2_write_3_, e memory_sel_public_log_read, e memory_sel_register_op_0_, e memory_sel_register_op_1_, e memory_sel_register_op_2_, e memory_sel_register_op_3_, e memory_sel_register_op_4_, e memory_sel_register_op_5_, e memory_sel_rng_chk, e memory_sel_rng_write, e memory_sel_sha256_op_0_, e memory_sel_sha256_op_1_, e memory_sel_sha256_op_2_, e memory_sel_sha256_op_3_, e memory_sel_sha256_op_4_, e memory_sel_sha256_op_5_, e memory_sel_sha256_op_6_, e memory_sel_sha256_op_7_, e memory_sel_sha256_read, e memory_sel_tag_is_ff, e memory_sel_to_radix_write, e memory_tag_ff_diff_inv, e merkle_check_const_three, e merkle_check_end, e merkle_check_index_is_even, e merkle_check_path_len_min_one_inv, e merkle_check_read_left_node, e merkle_check_read_output_hash, e merkle_check_read_right_node, e merkle_check_sibling, e merkle_check_write_left_node, e merkle_check_write_output_hash, e merkle_check_write_right_node, e note_hash_tree_check_address, e note_hash_tree_check_const_three, e note_hash_tree_check_discard, e note_hash_tree_check_exists, e note_hash_tree_check_first_nullifier, e note_hash_tree_check_first_nullifier_pi_index, e note_hash_tree_check_leaf_index, e note_hash_tree_check_merkle_hash_separator, e note_hash_tree_check_next_leaf_value, e note_hash_tree_check_next_root, e note_hash_tree_check_nonce, e note_hash_tree_check_nonce_separator, e note_hash_tree_check_note_hash, e note_hash_tree_check_note_hash_index, e note_hash_tree_check_note_hash_tree_height, e note_hash_tree_check_prev_leaf_value, e note_hash_tree_check_prev_leaf_value_unique_note_hash_diff_inv, e note_hash_tree_check_prev_root, e note_hash_tree_check_public_inputs_index, e note_hash_tree_check_sel, e note_hash_tree_check_sel_silo, e note_hash_tree_check_sel_unique, e note_hash_tree_check_sel_write_to_public_inputs, e note_hash_tree_check_siloed_note_hash, e note_hash_tree_check_siloing_separator, e note_hash_tree_check_unique_note_hash, e note_hash_tree_check_unique_note_hash_separator, e note_hash_tree_check_write, e poseidon2_hash_b_0, e poseidon2_hash_b_1, e poseidon2_hash_b_2, e poseidon2_hash_b_3, e poseidon2_hash_end, e poseidon2_hash_input_len, e poseidon2_hash_num_perm_rounds_rem_min_one_inv, e poseidon2_hash_padding, e poseidon2_perm_B_10_0, e poseidon2_perm_B_10_1, e poseidon2_perm_B_10_2, e poseidon2_perm_B_10_3, e poseidon2_perm_B_11_0, e poseidon2_perm_B_11_1, e poseidon2_perm_B_11_2, e poseidon2_perm_B_11_3, e poseidon2_perm_B_12_0, e poseidon2_perm_B_12_1, e poseidon2_perm_B_12_2, e poseidon2_perm_B_12_3, e poseidon2_perm_B_13_0, e poseidon2_perm_B_13_1, e poseidon2_perm_B_13_2, e poseidon2_perm_B_13_3, e poseidon2_perm_B_14_0, e poseidon2_perm_B_14_1, e poseidon2_perm_B_14_2, e poseidon2_perm_B_14_3, e poseidon2_perm_B_15_0, e poseidon2_perm_B_15_1, e poseidon2_perm_B_15_2, e poseidon2_perm_B_15_3, e poseidon2_perm_B_16_0, e poseidon2_perm_B_16_1, e poseidon2_perm_B_16_2, e poseidon2_perm_B_16_3, e poseidon2_perm_B_17_0, e poseidon2_perm_B_17_1, e poseidon2_perm_B_17_2, e poseidon2_perm_B_17_3, e poseidon2_perm_B_18_0, e poseidon2_perm_B_18_1, e poseidon2_perm_B_18_2, e poseidon2_perm_B_18_3, e poseidon2_perm_B_19_0, e poseidon2_perm_B_19_1, e poseidon2_perm_B_19_2, e poseidon2_perm_B_19_3, e poseidon2_perm_B_20_0, e poseidon2_perm_B_20_1, e poseidon2_perm_B_20_2, e poseidon2_perm_B_20_3, e poseidon2_perm_B_21_0, e poseidon2_perm_B_21_1, e poseidon2_perm_B_21_2, e poseidon2_perm_B_21_3, e poseidon2_perm_B_22_0, e poseidon2_perm_B_22_1, e poseidon2_perm_B_22_2, e poseidon2_perm_B_22_3, e poseidon2_perm_B_23_0, e poseidon2_perm_B_23_1, e poseidon2_perm_B_23_2, e poseidon2_perm_B_23_3, e poseidon2_perm_B_24_0, e poseidon2_perm_B_24_1, e poseidon2_perm_B_24_2, e poseidon2_perm_B_24_3, e poseidon2_perm_B_25_0, e poseidon2_perm_B_25_1, e poseidon2_perm_B_25_2, e poseidon2_perm_B_25_3, e poseidon2_perm_B_26_0, e poseidon2_perm_B_26_1, e poseidon2_perm_B_26_2, e poseidon2_perm_B_26_3, e poseidon2_perm_B_27_0, e poseidon2_perm_B_27_1, e poseidon2_perm_B_27_2, e poseidon2_perm_B_27_3, e poseidon2_perm_B_28_0, e poseidon2_perm_B_28_1, e poseidon2_perm_B_28_2, e poseidon2_perm_B_28_3, e poseidon2_perm_B_29_0, e poseidon2_perm_B_29_1, e poseidon2_perm_B_29_2, e poseidon2_perm_B_29_3, e poseidon2_perm_B_30_0, e poseidon2_perm_B_30_1, e poseidon2_perm_B_30_2, e poseidon2_perm_B_30_3, e poseidon2_perm_B_31_0, e poseidon2_perm_B_31_1, e poseidon2_perm_B_31_2, e poseidon2_perm_B_31_3, e poseidon2_perm_B_32_0, e poseidon2_perm_B_32_1, e poseidon2_perm_B_32_2, e poseidon2_perm_B_32_3, e poseidon2_perm_B_33_0, e poseidon2_perm_B_33_1, e poseidon2_perm_B_33_2, e poseidon2_perm_B_33_3, e poseidon2_perm_B_34_0, e poseidon2_perm_B_34_1, e poseidon2_perm_B_34_2, e poseidon2_perm_B_34_3, e poseidon2_perm_B_35_0, e poseidon2_perm_B_35_1, e poseidon2_perm_B_35_2, e poseidon2_perm_B_35_3, e poseidon2_perm_B_36_0, e poseidon2_perm_B_36_1, e poseidon2_perm_B_36_2, e poseidon2_perm_B_36_3, e poseidon2_perm_B_37_0, e poseidon2_perm_B_37_1, e poseidon2_perm_B_37_2, e poseidon2_perm_B_37_3, e poseidon2_perm_B_38_0, e poseidon2_perm_B_38_1, e poseidon2_perm_B_38_2, e poseidon2_perm_B_38_3, e poseidon2_perm_B_39_0, e poseidon2_perm_B_39_1, e poseidon2_perm_B_39_2, e poseidon2_perm_B_39_3, e poseidon2_perm_B_40_0, e poseidon2_perm_B_40_1, e poseidon2_perm_B_40_2, e poseidon2_perm_B_40_3, e poseidon2_perm_B_41_0, e poseidon2_perm_B_41_1, e poseidon2_perm_B_41_2, e poseidon2_perm_B_41_3, e poseidon2_perm_B_42_0, e poseidon2_perm_B_42_1, e poseidon2_perm_B_42_2, e poseidon2_perm_B_42_3, e poseidon2_perm_B_43_0, e poseidon2_perm_B_43_1, e poseidon2_perm_B_43_2, e poseidon2_perm_B_43_3, e poseidon2_perm_B_44_0, e poseidon2_perm_B_44_1, e poseidon2_perm_B_44_2, e poseidon2_perm_B_44_3, e poseidon2_perm_B_45_0, e poseidon2_perm_B_45_1, e poseidon2_perm_B_45_2, e poseidon2_perm_B_45_3, e poseidon2_perm_B_46_0, e poseidon2_perm_B_46_1, e poseidon2_perm_B_46_2, e poseidon2_perm_B_46_3, e poseidon2_perm_B_47_0, e poseidon2_perm_B_47_1, e poseidon2_perm_B_47_2, e poseidon2_perm_B_47_3, e poseidon2_perm_B_48_0, e poseidon2_perm_B_48_1, e poseidon2_perm_B_48_2, e poseidon2_perm_B_48_3, e poseidon2_perm_B_49_0, e poseidon2_perm_B_49_1, e poseidon2_perm_B_49_2, e poseidon2_perm_B_49_3, e poseidon2_perm_B_4_0, e poseidon2_perm_B_4_1, e poseidon2_perm_B_4_2, e poseidon2_perm_B_4_3, e poseidon2_perm_B_50_0, e poseidon2_perm_B_50_1, e poseidon2_perm_B_50_2, e poseidon2_perm_B_50_3, e poseidon2_perm_B_51_0, e poseidon2_perm_B_51_1, e poseidon2_perm_B_51_2, e poseidon2_perm_B_51_3, e poseidon2_perm_B_52_0, e poseidon2_perm_B_52_1, e poseidon2_perm_B_52_2, e poseidon2_perm_B_52_3, e poseidon2_perm_B_53_0, e poseidon2_perm_B_53_1, e poseidon2_perm_B_53_2, e poseidon2_perm_B_53_3, e poseidon2_perm_B_54_0, e poseidon2_perm_B_54_1, e poseidon2_perm_B_54_2, e poseidon2_perm_B_54_3, e poseidon2_perm_B_55_0, e poseidon2_perm_B_55_1, e poseidon2_perm_B_55_2, e poseidon2_perm_B_55_3, e poseidon2_perm_B_56_0, e poseidon2_perm_B_56_1, e poseidon2_perm_B_56_2, e poseidon2_perm_B_56_3, e poseidon2_perm_B_57_0, e poseidon2_perm_B_57_1, e poseidon2_perm_B_57_2, e poseidon2_perm_B_57_3, e poseidon2_perm_B_58_0, e poseidon2_perm_B_58_1, e poseidon2_perm_B_58_2, e poseidon2_perm_B_58_3, e poseidon2_perm_B_59_0, e poseidon2_perm_B_59_1, e poseidon2_perm_B_59_2, e poseidon2_perm_B_59_3, e poseidon2_perm_B_5_0, e poseidon2_perm_B_5_1, e poseidon2_perm_B_5_2, e poseidon2_perm_B_5_3, e poseidon2_perm_B_6_0, e poseidon2_perm_B_6_1, e poseidon2_perm_B_6_2, e poseidon2_perm_B_6_3, e poseidon2_perm_B_7_0, e poseidon2_perm_B_7_1, e poseidon2_perm_B_7_2, e poseidon2_perm_B_7_3, e poseidon2_perm_B_8_0, e poseidon2_perm_B_8_1, e poseidon2_perm_B_8_2, e poseidon2_perm_B_8_3, e poseidon2_perm_B_9_0, e poseidon2_perm_B_9_1, e poseidon2_perm_B_9_2, e poseidon2_perm_B_9_3, e poseidon2_perm_EXT_LAYER_4, e poseidon2_perm_EXT_LAYER_5, e poseidon2_perm_EXT_LAYER_6, e poseidon2_perm_EXT_LAYER_7, e poseidon2_perm_T_0_4, e poseidon2_perm_T_0_5, e poseidon2_perm_T_0_6, e poseidon2_perm_T_0_7, e poseidon2_perm_T_1_4, e poseidon2_perm_T_1_5, e poseidon2_perm_T_1_6, e poseidon2_perm_T_1_7, e poseidon2_perm_T_2_4, e poseidon2_perm_T_2_5, e poseidon2_perm_T_2_6, e poseidon2_perm_T_2_7, e poseidon2_perm_T_3_4, e poseidon2_perm_T_3_5, e poseidon2_perm_T_3_6, e poseidon2_perm_T_3_7, e poseidon2_perm_T_60_4, e poseidon2_perm_T_60_5, e poseidon2_perm_T_60_6, e poseidon2_perm_T_60_7, e poseidon2_perm_T_61_4, e poseidon2_perm_T_61_5, e poseidon2_perm_T_61_6, e poseidon2_perm_T_61_7, e poseidon2_perm_T_62_4, e poseidon2_perm_T_62_5, e poseidon2_perm_T_62_6, e poseidon2_perm_T_62_7, e poseidon2_perm_T_63_4, e poseidon2_perm_T_63_5, e poseidon2_perm_T_63_6, e poseidon2_perm_T_63_7, e poseidon2_perm_a_0, e poseidon2_perm_a_1, e poseidon2_perm_a_2, e poseidon2_perm_a_3, e poseidon2_perm_b_0, e poseidon2_perm_b_1, e poseidon2_perm_b_2, e poseidon2_perm_b_3, e poseidon2_perm_mem_batch_tag_inv, e poseidon2_perm_mem_err, e poseidon2_perm_mem_execution_clk, e poseidon2_perm_mem_input_0_, e poseidon2_perm_mem_input_1_, e poseidon2_perm_mem_input_2_, e poseidon2_perm_mem_input_3_, e poseidon2_perm_mem_input_tag_0_, e poseidon2_perm_mem_input_tag_1_, e poseidon2_perm_mem_input_tag_2_, e poseidon2_perm_mem_input_tag_3_, e poseidon2_perm_mem_max_mem_addr, e poseidon2_perm_mem_output_0_, e poseidon2_perm_mem_output_1_, e poseidon2_perm_mem_output_2_, e poseidon2_perm_mem_output_3_, e poseidon2_perm_mem_read_address_0_, e poseidon2_perm_mem_read_address_1_, e poseidon2_perm_mem_read_address_2_, e poseidon2_perm_mem_read_address_3_, e poseidon2_perm_mem_sel, e poseidon2_perm_mem_sel_dst_out_of_range_err, e poseidon2_perm_mem_sel_invalid_tag_err, e poseidon2_perm_mem_sel_should_exec, e poseidon2_perm_mem_sel_should_read_mem, e poseidon2_perm_mem_sel_src_out_of_range_err, e poseidon2_perm_mem_space_id, e poseidon2_perm_mem_write_address_0_, e poseidon2_perm_mem_write_address_1_, e poseidon2_perm_mem_write_address_2_, e poseidon2_perm_mem_write_address_3_, e poseidon2_perm_sel, e public_data_check_address, e public_data_check_clk_diff_hi, e public_data_check_clk_diff_lo, e public_data_check_const_four, e public_data_check_const_three, e public_data_check_discard, e public_data_check_end, e public_data_check_final_value, e public_data_check_intermediate_root, e public_data_check_leaf_not_exists, e public_data_check_leaf_slot, e public_data_check_leaf_slot_low_leaf_slot_diff_inv, e public_data_check_length_pi_idx, e public_data_check_low_leaf_hash, e public_data_check_low_leaf_index, e public_data_check_low_leaf_next_index, e public_data_check_low_leaf_next_slot, e public_data_check_low_leaf_slot, e public_data_check_low_leaf_value, e public_data_check_merkle_hash_separator, e public_data_check_new_leaf_hash, e public_data_check_next_slot_inv, e public_data_check_next_slot_is_nonzero, e public_data_check_non_discarded_write, e public_data_check_non_protocol_write, e public_data_check_not_end, e public_data_check_protocol_write, e public_data_check_public_data_writes_length, e public_data_check_root, e public_data_check_sel_write_to_public_inputs, e public_data_check_should_insert, e public_data_check_siloing_separator, e public_data_check_slot, e public_data_check_tree_height, e public_data_check_tree_size_after_write, e public_data_check_tree_size_before_write, e public_data_check_updated_low_leaf_hash, e public_data_check_updated_low_leaf_next_index, e public_data_check_updated_low_leaf_next_slot, e public_data_check_updated_low_leaf_value, e public_data_check_value, e public_data_check_write, e public_data_check_write_root, e public_data_squash_check_clock, e public_data_squash_clk_diff_hi, e public_data_squash_clk_diff_lo, e public_data_squash_leaf_slot_increase, e public_data_squash_value, e range_check_dyn_diff, e range_check_dyn_rng_chk_bits, e range_check_dyn_rng_chk_pow_2, e range_check_is_lte_u112, e range_check_is_lte_u128, e range_check_is_lte_u16, e range_check_is_lte_u32, e range_check_is_lte_u48, e range_check_is_lte_u64, e range_check_is_lte_u80, e range_check_is_lte_u96, e range_check_rng_chk_bits, e range_check_sel, e range_check_sel_alu, e range_check_sel_gt, e range_check_sel_keccak, e range_check_sel_memory, e range_check_sel_r0_16_bit_rng_lookup, e range_check_sel_r1_16_bit_rng_lookup, e range_check_sel_r2_16_bit_rng_lookup, e range_check_sel_r3_16_bit_rng_lookup, e range_check_sel_r4_16_bit_rng_lookup, e range_check_sel_r5_16_bit_rng_lookup, e range_check_sel_r6_16_bit_rng_lookup, e range_check_u16_r0, e range_check_u16_r1, e range_check_u16_r2, e range_check_u16_r3, e range_check_u16_r4, e range_check_u16_r5, e range_check_u16_r6, e range_check_u16_r7, e range_check_value, e scalar_mul_bit, e scalar_mul_const_two, e scalar_mul_end, e scalar_mul_sel_not_end, e scalar_mul_should_add, e sha256_a_and_b, e sha256_a_and_b_xor_a_and_c, e sha256_a_and_c, e sha256_a_rotr_13, e sha256_a_rotr_2, e sha256_a_rotr_22, e sha256_a_rotr_2_xor_a_rotr_13, e sha256_and_op_id, e sha256_b_and_c, e sha256_batch_tag_inv, e sha256_ch, e sha256_computed_w_lhs, e sha256_computed_w_rhs, e sha256_e_and_f, e sha256_e_rotr_11, e sha256_e_rotr_25, e sha256_e_rotr_6, e sha256_e_rotr_6_xor_e_rotr_11, e sha256_end, e sha256_err, e sha256_input, e sha256_input_rounds_rem_inv, e sha256_input_tag, e sha256_input_tag_diff_inv, e sha256_last, e sha256_lhs_w_10, e sha256_lhs_w_3, e sha256_maj, e sha256_max_input_addr, e sha256_max_mem_addr, e sha256_max_output_addr, e sha256_max_state_addr, e sha256_mem_out_of_range_err, e sha256_memory_address_0_, e sha256_memory_address_1_, e sha256_memory_address_2_, e sha256_memory_address_3_, e sha256_memory_address_4_, e sha256_memory_address_5_, e sha256_memory_address_6_, e sha256_memory_address_7_, e sha256_memory_register_0_, e sha256_memory_register_1_, e sha256_memory_register_2_, e sha256_memory_register_3_, e sha256_memory_register_4_, e sha256_memory_register_5_, e sha256_memory_register_6_, e sha256_memory_register_7_, e sha256_memory_tag_0_, e sha256_memory_tag_1_, e sha256_memory_tag_2_, e sha256_memory_tag_3_, e sha256_memory_tag_4_, e sha256_memory_tag_5_, e sha256_memory_tag_6_, e sha256_memory_tag_7_, e sha256_next_a_lhs, e sha256_next_a_rhs, e sha256_next_e_lhs, e sha256_next_e_rhs, e sha256_not_e, e sha256_not_e_and_g, e sha256_output_a_lhs, e sha256_output_a_rhs, e sha256_output_b_lhs, e sha256_output_b_rhs, e sha256_output_c_lhs, e sha256_output_c_rhs, e sha256_output_d_lhs, e sha256_output_d_rhs, e sha256_output_e_lhs, e sha256_output_e_rhs, e sha256_output_f_lhs, e sha256_output_f_rhs, e sha256_output_g_lhs, e sha256_output_g_rhs, e sha256_output_h_lhs, e sha256_output_h_rhs, e sha256_perform_round, e sha256_rhs_a_13, e sha256_rhs_a_2, e sha256_rhs_a_22, e sha256_rhs_e_11, e sha256_rhs_e_25, e sha256_rhs_e_6, e sha256_rhs_w_10, e sha256_rhs_w_17, e sha256_rhs_w_18, e sha256_rhs_w_19, e sha256_rhs_w_3, e sha256_rhs_w_7, e sha256_round_constant, e sha256_round_count, e sha256_rounds_remaining_inv, e sha256_rw, e sha256_s_0, e sha256_s_1, e sha256_sel_compute_w, e sha256_sel_input_out_of_range_err, e sha256_sel_invalid_input_row_tag_err, e sha256_sel_invalid_state_tag_err, e sha256_sel_is_input_round, e sha256_sel_mem_state_or_output, e sha256_sel_output_out_of_range_err, e sha256_sel_read_input_from_memory, e sha256_sel_state_out_of_range_err, e sha256_state_addr, e sha256_two_pow_10, e sha256_two_pow_11, e sha256_two_pow_13, e sha256_two_pow_17, e sha256_two_pow_18, e sha256_two_pow_19, e sha256_two_pow_2, e sha256_two_pow_22, e sha256_two_pow_25, e sha256_two_pow_3, e sha256_two_pow_32, e sha256_two_pow_6, e sha256_two_pow_7, e sha256_u32_tag, e sha256_w, e sha256_w_15_rotr_18, e sha256_w_15_rotr_7, e sha256_w_15_rotr_7_xor_w_15_rotr_18, e sha256_w_2_rotr_17, e sha256_w_2_rotr_17_xor_w_2_rotr_19, e sha256_w_2_rotr_19, e sha256_w_s_0, e sha256_w_s_1, e sha256_xor_op_id, e to_radix_end, e to_radix_found, e to_radix_is_unsafe_limb, e to_radix_limb_p_diff, e to_radix_limb_radix_diff, e to_radix_mem_err, e to_radix_mem_input_validation_error, e to_radix_mem_last, e to_radix_mem_limb_index_to_lookup, e to_radix_mem_limb_value, e to_radix_mem_max_mem_size, e to_radix_mem_num_limbs_inv, e to_radix_mem_num_limbs_minus_one_inv, e to_radix_mem_output_tag, e to_radix_mem_radix_min_two_inv, e to_radix_mem_sel_dst_out_of_range_err, e to_radix_mem_sel_invalid_bitwise_radix, e to_radix_mem_sel_num_limbs_is_zero, e to_radix_mem_sel_radix_eq_2, e to_radix_mem_sel_radix_gt_256_err, e to_radix_mem_sel_radix_lt_2_err, e to_radix_mem_sel_value_is_zero, e to_radix_mem_two, e to_radix_mem_two_five_six, e to_radix_mem_value_found, e to_radix_mem_value_inv, e to_radix_mem_write_addr_upper_bound, e to_radix_p_limb, e to_radix_rem_inverse, e to_radix_safety_diff_inverse, e tx_array_length_l2_to_l1_messages_pi_offset, e tx_array_length_note_hashes_pi_offset, e tx_array_length_nullifiers_pi_offset, e tx_calldata_hash, e tx_calldata_size, e tx_const_three, e tx_contract_addr, e tx_dom_sep_public_storage_map_slot, e tx_effective_fee_per_da_gas, e tx_effective_fee_per_l2_gas, e tx_end_phase, e tx_fee_juice_balance_slot, e tx_fee_juice_balances_slot_constant, e tx_fee_juice_contract_address, e tx_fee_payer, e tx_fee_payer_balance, e tx_fee_payer_new_balance, e tx_fee_payer_pi_offset, e tx_fields_length_public_logs_pi_offset, e tx_gas_limit_pi_offset, e tx_gas_used_pi_offset, e tx_is_cleanup, e tx_is_collect_fee, e tx_is_padded, e tx_is_public_call_request, e tx_is_static, e tx_is_tree_insert_phase, e tx_is_tree_padding, e tx_l1_l2_pi_offset, e tx_l2_l1_msg_content, e tx_l2_l1_msg_contract_address, e tx_l2_l1_msg_recipient, e tx_leaf_value, e tx_msg_sender, e tx_next_da_gas_used, e tx_next_da_gas_used_sent_to_enqueued_call, e tx_next_l2_gas_used, e tx_next_l2_gas_used_sent_to_enqueued_call, e tx_next_note_hash_tree_root, e tx_next_note_hash_tree_size, e tx_next_nullifier_tree_root, e tx_next_nullifier_tree_size, e tx_next_num_l2_to_l1_messages, e tx_next_num_note_hashes_emitted, e tx_next_num_nullifiers_emitted, e tx_next_num_public_log_fields, e tx_next_phase_on_revert, e tx_next_public_data_tree_root, e tx_next_public_data_tree_size, e tx_next_retrieved_bytecodes_tree_root, e tx_next_retrieved_bytecodes_tree_size, e tx_next_written_public_data_slots_tree_root, e tx_next_written_public_data_slots_tree_size, e tx_note_hash_pi_offset, e tx_nullifier_limit_error, e tx_nullifier_merkle_separator, e tx_nullifier_pi_offset, e tx_nullifier_tree_height, e tx_prev_da_gas_used_sent_to_enqueued_call, e tx_prev_l2_gas_used_sent_to_enqueued_call, e tx_public_data_pi_offset, e tx_read_pi_length_offset, e tx_read_pi_start_offset, e tx_remaining_phase_inv, e tx_remaining_phase_minus_one_inv, e tx_remaining_side_effects_inv, e tx_reverted_pi_offset, e tx_sel_append_l2_l1_msg, e tx_sel_append_note_hash, e tx_sel_append_nullifier, e tx_sel_l2_l1_msg_append, e tx_sel_note_hash_append, e tx_sel_nullifier_append, e tx_sel_process_call_request, e tx_sel_read_phase_length, e tx_sel_read_trees_and_gas_used, e tx_sel_try_l2_l1_msg_append, e tx_sel_try_note_hash_append, e tx_sel_try_nullifier_append, e tx_setup_phase_value, e tx_should_read_gas_limit, e tx_uint32_max, e tx_write_nullifier_pi_offset, e tx_write_pi_offset, e update_check_address, e update_check_const_three, e update_check_contract_instance_registry_address, e update_check_current_class_id, e update_check_delayed_public_mutable_hash_slot, e update_check_delayed_public_mutable_slot, e update_check_dom_sep_public_storage_map_slot, e update_check_hash_not_zero, e update_check_original_class_id, e update_check_public_data_tree_root, e update_check_sel, e update_check_timestamp, e update_check_timestamp_is_lt_timestamp_of_change, e update_check_timestamp_of_change, e update_check_timestamp_of_change_bit_size, e update_check_timestamp_pi_offset, e update_check_update_hash, e update_check_update_hash_inv, e update_check_update_hi_metadata, e update_check_update_hi_metadata_bit_size, e update_check_update_post_class_id_is_zero, e update_check_update_post_class_inv, e update_check_update_pre_class_id_is_zero, e update_check_update_pre_class_inv, e update_check_update_preimage_metadata, e update_check_update_preimage_post_class_id, e update_check_update_preimage_pre_class_id, e update_check_updated_class_ids_slot, e lookup_range_check_dyn_rng_chk_pow_2_counts, e lookup_range_check_dyn_diff_is_u16_counts, e lookup_range_check_r0_is_u16_counts, e lookup_range_check_r1_is_u16_counts, e lookup_range_check_r2_is_u16_counts, e lookup_range_check_r3_is_u16_counts, e lookup_range_check_r4_is_u16_counts, e lookup_range_check_r5_is_u16_counts, e lookup_range_check_r6_is_u16_counts, e lookup_range_check_r7_is_u16_counts, e lookup_ff_gt_a_lo_range_counts, e lookup_ff_gt_a_hi_range_counts, e lookup_gt_gt_range_counts, e lookup_alu_tag_max_bits_value_counts, e lookup_alu_range_check_decomposition_a_lo_counts, e lookup_alu_range_check_decomposition_a_hi_counts, e lookup_alu_range_check_decomposition_b_lo_counts, e lookup_alu_range_check_decomposition_b_hi_counts, e lookup_alu_range_check_mul_c_hi_counts, e lookup_alu_range_check_div_remainder_counts, e lookup_alu_ff_gt_counts, e lookup_alu_int_gt_counts, e lookup_alu_shifts_two_pow_counts, e lookup_alu_large_trunc_canonical_dec_counts, e lookup_alu_range_check_trunc_mid_counts, e lookup_bitwise_integral_tag_length_counts, e lookup_bitwise_byte_operations_counts, e lookup_memory_range_check_limb_0_counts, e lookup_memory_range_check_limb_1_counts, e lookup_memory_range_check_limb_2_counts, e lookup_memory_tag_max_bits_counts, e lookup_memory_range_check_write_tagged_value_counts, e lookup_data_copy_offset_plus_size_is_gt_data_size_counts, e lookup_data_copy_check_src_addr_in_range_counts, e lookup_data_copy_check_dst_addr_in_range_counts, e lookup_data_copy_sel_has_reads_counts, e lookup_data_copy_col_read_counts, e lookup_ecc_mem_check_dst_addr_in_range_counts, e lookup_ecc_mem_input_output_ecc_add_counts, e lookup_keccakf1600_theta_xor_01_counts, e lookup_keccakf1600_theta_xor_02_counts, e lookup_keccakf1600_theta_xor_03_counts, e lookup_keccakf1600_theta_xor_row_0_counts, e lookup_keccakf1600_theta_xor_11_counts, e lookup_keccakf1600_theta_xor_12_counts, e lookup_keccakf1600_theta_xor_13_counts, e lookup_keccakf1600_theta_xor_row_1_counts, e lookup_keccakf1600_theta_xor_21_counts, e lookup_keccakf1600_theta_xor_22_counts, e lookup_keccakf1600_theta_xor_23_counts, e lookup_keccakf1600_theta_xor_row_2_counts, e lookup_keccakf1600_theta_xor_31_counts, e lookup_keccakf1600_theta_xor_32_counts, e lookup_keccakf1600_theta_xor_33_counts, e lookup_keccakf1600_theta_xor_row_3_counts, e lookup_keccakf1600_theta_xor_41_counts, e lookup_keccakf1600_theta_xor_42_counts, e lookup_keccakf1600_theta_xor_43_counts, e lookup_keccakf1600_theta_xor_row_4_counts, e lookup_keccakf1600_theta_combined_xor_0_counts, e lookup_keccakf1600_theta_combined_xor_1_counts, e lookup_keccakf1600_theta_combined_xor_2_counts, e lookup_keccakf1600_theta_combined_xor_3_counts, e lookup_keccakf1600_theta_combined_xor_4_counts, e lookup_keccakf1600_state_theta_00_counts, e lookup_keccakf1600_state_theta_01_counts, e lookup_keccakf1600_state_theta_02_counts, e lookup_keccakf1600_state_theta_03_counts, e lookup_keccakf1600_state_theta_04_counts, e lookup_keccakf1600_state_theta_10_counts, e lookup_keccakf1600_state_theta_11_counts, e lookup_keccakf1600_state_theta_12_counts, e lookup_keccakf1600_state_theta_13_counts, e lookup_keccakf1600_state_theta_14_counts, e lookup_keccakf1600_state_theta_20_counts, e lookup_keccakf1600_state_theta_21_counts, e lookup_keccakf1600_state_theta_22_counts, e lookup_keccakf1600_state_theta_23_counts, e lookup_keccakf1600_state_theta_24_counts, e lookup_keccakf1600_state_theta_30_counts, e lookup_keccakf1600_state_theta_31_counts, e lookup_keccakf1600_state_theta_32_counts, e lookup_keccakf1600_state_theta_33_counts, e lookup_keccakf1600_state_theta_34_counts, e lookup_keccakf1600_state_theta_40_counts, e lookup_keccakf1600_state_theta_41_counts, e lookup_keccakf1600_state_theta_42_counts, e lookup_keccakf1600_state_theta_43_counts, e lookup_keccakf1600_state_theta_44_counts, e lookup_keccakf1600_theta_limb_02_range_counts, e lookup_keccakf1600_theta_limb_04_range_counts, e lookup_keccakf1600_theta_limb_10_range_counts, e lookup_keccakf1600_theta_limb_12_range_counts, e lookup_keccakf1600_theta_limb_14_range_counts, e lookup_keccakf1600_theta_limb_21_range_counts, e lookup_keccakf1600_theta_limb_23_range_counts, e lookup_keccakf1600_theta_limb_30_range_counts, e lookup_keccakf1600_theta_limb_32_range_counts, e lookup_keccakf1600_theta_limb_33_range_counts, e lookup_keccakf1600_theta_limb_40_range_counts, e lookup_keccakf1600_theta_limb_41_range_counts, e lookup_keccakf1600_theta_limb_43_range_counts, e lookup_keccakf1600_theta_limb_44_range_counts, e lookup_keccakf1600_theta_limb_01_range_counts, e lookup_keccakf1600_theta_limb_03_range_counts, e lookup_keccakf1600_theta_limb_11_range_counts, e lookup_keccakf1600_theta_limb_13_range_counts, e lookup_keccakf1600_theta_limb_20_range_counts, e lookup_keccakf1600_theta_limb_22_range_counts, e lookup_keccakf1600_theta_limb_24_range_counts, e lookup_keccakf1600_theta_limb_31_range_counts, e lookup_keccakf1600_theta_limb_34_range_counts, e lookup_keccakf1600_theta_limb_42_range_counts, e lookup_keccakf1600_state_pi_and_00_counts, e lookup_keccakf1600_state_pi_and_01_counts, e lookup_keccakf1600_state_pi_and_02_counts, e lookup_keccakf1600_state_pi_and_03_counts, e lookup_keccakf1600_state_pi_and_04_counts, e lookup_keccakf1600_state_pi_and_10_counts, e lookup_keccakf1600_state_pi_and_11_counts, e lookup_keccakf1600_state_pi_and_12_counts, e lookup_keccakf1600_state_pi_and_13_counts, e lookup_keccakf1600_state_pi_and_14_counts, e lookup_keccakf1600_state_pi_and_20_counts, e lookup_keccakf1600_state_pi_and_21_counts, e lookup_keccakf1600_state_pi_and_22_counts, e lookup_keccakf1600_state_pi_and_23_counts, e lookup_keccakf1600_state_pi_and_24_counts, e lookup_keccakf1600_state_pi_and_30_counts, e lookup_keccakf1600_state_pi_and_31_counts, e lookup_keccakf1600_state_pi_and_32_counts, e lookup_keccakf1600_state_pi_and_33_counts, e lookup_keccakf1600_state_pi_and_34_counts, e lookup_keccakf1600_state_pi_and_40_counts, e lookup_keccakf1600_state_pi_and_41_counts, e lookup_keccakf1600_state_pi_and_42_counts, e lookup_keccakf1600_state_pi_and_43_counts, e lookup_keccakf1600_state_pi_and_44_counts, e lookup_keccakf1600_state_chi_00_counts, e lookup_keccakf1600_state_chi_01_counts, e lookup_keccakf1600_state_chi_02_counts, e lookup_keccakf1600_state_chi_03_counts, e lookup_keccakf1600_state_chi_04_counts, e lookup_keccakf1600_state_chi_10_counts, e lookup_keccakf1600_state_chi_11_counts, e lookup_keccakf1600_state_chi_12_counts, e lookup_keccakf1600_state_chi_13_counts, e lookup_keccakf1600_state_chi_14_counts, e lookup_keccakf1600_state_chi_20_counts, e lookup_keccakf1600_state_chi_21_counts, e lookup_keccakf1600_state_chi_22_counts, e lookup_keccakf1600_state_chi_23_counts, e lookup_keccakf1600_state_chi_24_counts, e lookup_keccakf1600_state_chi_30_counts, e lookup_keccakf1600_state_chi_31_counts, e lookup_keccakf1600_state_chi_32_counts, e lookup_keccakf1600_state_chi_33_counts, e lookup_keccakf1600_state_chi_34_counts, e lookup_keccakf1600_state_chi_40_counts, e lookup_keccakf1600_state_chi_41_counts, e lookup_keccakf1600_state_chi_42_counts, e lookup_keccakf1600_state_chi_43_counts, e lookup_keccakf1600_state_chi_44_counts, e lookup_keccakf1600_round_cst_counts, e lookup_keccakf1600_state_iota_00_counts, e lookup_keccakf1600_src_out_of_range_toggle_counts, e lookup_keccakf1600_dst_out_of_range_toggle_counts, e lookup_poseidon2_mem_check_src_addr_in_range_counts, e lookup_poseidon2_mem_check_dst_addr_in_range_counts, e lookup_poseidon2_mem_input_output_poseidon2_perm_counts, e lookup_to_radix_limb_range_counts, e lookup_to_radix_limb_less_than_radix_range_counts, e lookup_to_radix_fetch_safe_limbs_counts, e lookup_to_radix_fetch_p_limb_counts, e lookup_to_radix_limb_p_diff_range_counts, e lookup_scalar_mul_to_radix_counts, e lookup_scalar_mul_double_counts, e lookup_scalar_mul_add_counts, e lookup_sha256_range_comp_w_lhs_counts, e lookup_sha256_range_comp_w_rhs_counts, e lookup_sha256_range_rhs_w_7_counts, e lookup_sha256_range_rhs_w_18_counts, e lookup_sha256_range_rhs_w_3_counts, e lookup_sha256_w_s_0_xor_0_counts, e lookup_sha256_w_s_0_xor_1_counts, e lookup_sha256_range_rhs_w_17_counts, e lookup_sha256_range_rhs_w_19_counts, e lookup_sha256_range_rhs_w_10_counts, e lookup_sha256_w_s_1_xor_0_counts, e lookup_sha256_w_s_1_xor_1_counts, e lookup_sha256_range_rhs_e_6_counts, e lookup_sha256_range_rhs_e_11_counts, e lookup_sha256_range_rhs_e_25_counts, e lookup_sha256_s_1_xor_0_counts, e lookup_sha256_s_1_xor_1_counts, e lookup_sha256_ch_and_0_counts, e lookup_sha256_ch_and_1_counts, e lookup_sha256_ch_xor_counts, e lookup_sha256_round_constant_counts, e lookup_sha256_range_rhs_a_2_counts, e lookup_sha256_range_rhs_a_13_counts, e lookup_sha256_range_rhs_a_22_counts, e lookup_sha256_s_0_xor_0_counts, e lookup_sha256_s_0_xor_1_counts, e lookup_sha256_maj_and_0_counts, e lookup_sha256_maj_and_1_counts, e lookup_sha256_maj_and_2_counts, e lookup_sha256_maj_xor_0_counts, e lookup_sha256_maj_xor_1_counts, e lookup_sha256_range_comp_next_a_lhs_counts, e lookup_sha256_range_comp_next_a_rhs_counts, e lookup_sha256_range_comp_next_e_lhs_counts, e lookup_sha256_range_comp_next_e_rhs_counts, e lookup_sha256_range_comp_a_rhs_counts, e lookup_sha256_range_comp_b_rhs_counts, e lookup_sha256_range_comp_c_rhs_counts, e lookup_sha256_range_comp_d_rhs_counts, e lookup_sha256_range_comp_e_rhs_counts, e lookup_sha256_range_comp_f_rhs_counts, e lookup_sha256_range_comp_g_rhs_counts, e lookup_sha256_range_comp_h_rhs_counts, e lookup_sha256_mem_check_state_addr_in_range_counts, e lookup_sha256_mem_check_input_addr_in_range_counts, e lookup_sha256_mem_check_output_addr_in_range_counts, e lookup_to_radix_mem_check_dst_addr_in_range_counts, e lookup_to_radix_mem_check_radix_lt_2_counts, e lookup_to_radix_mem_check_radix_gt_256_counts, e lookup_to_radix_mem_input_output_to_radix_counts, e lookup_poseidon2_hash_poseidon2_perm_counts, e lookup_address_derivation_salted_initialization_hash_poseidon2_0_counts, e lookup_address_derivation_salted_initialization_hash_poseidon2_1_counts, e lookup_address_derivation_partial_address_poseidon2_counts, e lookup_address_derivation_public_keys_hash_poseidon2_0_counts, e lookup_address_derivation_public_keys_hash_poseidon2_1_counts, e lookup_address_derivation_public_keys_hash_poseidon2_2_counts, e lookup_address_derivation_public_keys_hash_poseidon2_3_counts, e lookup_address_derivation_public_keys_hash_poseidon2_4_counts, e lookup_address_derivation_preaddress_poseidon2_counts, e lookup_address_derivation_preaddress_scalar_mul_counts, e lookup_address_derivation_address_ecadd_counts, e lookup_bc_decomposition_bytes_are_bytes_counts, e lookup_bc_hashing_poseidon2_hash_counts, e lookup_merkle_check_merkle_poseidon2_read_counts, e lookup_merkle_check_merkle_poseidon2_write_counts, e lookup_indexed_tree_check_silo_poseidon2_counts, e lookup_indexed_tree_check_low_leaf_value_validation_counts, e lookup_indexed_tree_check_low_leaf_next_value_validation_counts, e lookup_indexed_tree_check_low_leaf_poseidon2_counts, e lookup_indexed_tree_check_updated_low_leaf_poseidon2_counts, e lookup_indexed_tree_check_low_leaf_merkle_check_counts, e lookup_indexed_tree_check_new_leaf_poseidon2_counts, e lookup_indexed_tree_check_new_leaf_merkle_check_counts, e lookup_indexed_tree_check_write_value_to_public_inputs_counts, e lookup_public_data_squash_leaf_slot_increase_ff_gt_counts, e lookup_public_data_squash_clk_diff_range_lo_counts, e lookup_public_data_squash_clk_diff_range_hi_counts, e lookup_public_data_check_clk_diff_range_lo_counts, e lookup_public_data_check_clk_diff_range_hi_counts, e lookup_public_data_check_silo_poseidon2_counts, e lookup_public_data_check_low_leaf_slot_validation_counts, e lookup_public_data_check_low_leaf_next_slot_validation_counts, e lookup_public_data_check_low_leaf_poseidon2_0_counts, e lookup_public_data_check_low_leaf_poseidon2_1_counts, e lookup_public_data_check_updated_low_leaf_poseidon2_0_counts, e lookup_public_data_check_updated_low_leaf_poseidon2_1_counts, e lookup_public_data_check_low_leaf_merkle_check_counts, e lookup_public_data_check_new_leaf_poseidon2_0_counts, e lookup_public_data_check_new_leaf_poseidon2_1_counts, e lookup_public_data_check_new_leaf_merkle_check_counts, e lookup_public_data_check_write_public_data_to_public_inputs_counts, e lookup_public_data_check_write_writes_length_to_public_inputs_counts, e lookup_update_check_timestamp_from_public_inputs_counts, e lookup_update_check_delayed_public_mutable_slot_poseidon2_counts, e lookup_update_check_update_hash_public_data_read_counts, e lookup_update_check_update_hash_poseidon2_counts, e lookup_update_check_update_hi_metadata_range_counts, e lookup_update_check_update_lo_metadata_range_counts, e lookup_update_check_timestamp_is_lt_timestamp_of_change_counts, e lookup_contract_instance_retrieval_check_protocol_address_range_counts, e lookup_contract_instance_retrieval_read_derived_address_from_public_inputs_counts, e lookup_contract_instance_retrieval_deployment_nullifier_read_counts, e lookup_contract_instance_retrieval_address_derivation_counts, e lookup_contract_instance_retrieval_update_check_counts, e lookup_class_id_derivation_class_id_poseidon2_0_counts, e lookup_class_id_derivation_class_id_poseidon2_1_counts, e lookup_bc_retrieval_contract_instance_retrieval_counts, e lookup_bc_retrieval_class_id_derivation_counts, e lookup_bc_retrieval_is_new_class_check_counts, e lookup_bc_retrieval_retrieved_bytecodes_insertion_counts, e lookup_instr_fetching_pc_abs_diff_positive_counts, e lookup_instr_fetching_instr_abs_diff_positive_counts, e lookup_instr_fetching_tag_value_validation_counts, e lookup_instr_fetching_bytecode_size_from_bc_dec_counts, e lookup_instr_fetching_bytes_from_bc_dec_counts, e lookup_instr_fetching_wire_instruction_info_counts, e lookup_emit_public_log_check_memory_out_of_bounds_counts, e lookup_emit_public_log_check_log_fields_count_counts, e lookup_emit_public_log_write_data_to_public_inputs_counts, e lookup_get_contract_instance_precomputed_info_counts, e lookup_get_contract_instance_contract_instance_retrieval_counts, e lookup_l1_to_l2_message_tree_check_merkle_check_counts, e lookup_internal_call_unwind_call_stack_counts, e lookup_context_ctx_stack_rollback_counts, e lookup_context_ctx_stack_return_counts, e lookup_addressing_relative_overflow_result_0_counts, e lookup_addressing_relative_overflow_result_1_counts, e lookup_addressing_relative_overflow_result_2_counts, e lookup_addressing_relative_overflow_result_3_counts, e lookup_addressing_relative_overflow_result_4_counts, e lookup_addressing_relative_overflow_result_5_counts, e lookup_addressing_relative_overflow_result_6_counts, e lookup_gas_addressing_gas_read_counts, e lookup_gas_is_out_of_gas_l2_counts, e lookup_gas_is_out_of_gas_da_counts, e lookup_note_hash_tree_check_silo_poseidon2_counts, e lookup_note_hash_tree_check_read_first_nullifier_counts, e lookup_note_hash_tree_check_nonce_computation_poseidon2_counts, e lookup_note_hash_tree_check_unique_note_hash_poseidon2_counts, e lookup_note_hash_tree_check_merkle_check_counts, e lookup_note_hash_tree_check_write_note_hash_to_public_inputs_counts, e lookup_emit_notehash_notehash_tree_write_counts, e lookup_emit_nullifier_write_nullifier_counts, e lookup_external_call_is_l2_gas_left_gt_allocated_counts, e lookup_external_call_is_da_gas_left_gt_allocated_counts, e lookup_get_env_var_precomputed_info_counts, e lookup_get_env_var_read_from_public_inputs_col0_counts, e lookup_get_env_var_read_from_public_inputs_col1_counts, e lookup_l1_to_l2_message_exists_l1_to_l2_msg_leaf_index_in_range_counts, e lookup_l1_to_l2_message_exists_l1_to_l2_msg_read_counts, e lookup_notehash_exists_note_hash_leaf_index_in_range_counts, e lookup_notehash_exists_note_hash_read_counts, e lookup_nullifier_exists_nullifier_exists_check_counts, e lookup_send_l2_to_l1_msg_recipient_check_counts, e lookup_send_l2_to_l1_msg_write_l2_to_l1_msg_counts, e lookup_sload_storage_read_counts, e lookup_sstore_record_written_storage_slot_counts, e lookup_execution_bytecode_retrieval_result_counts, e lookup_execution_instruction_fetching_result_counts, e lookup_execution_instruction_fetching_body_counts, e lookup_execution_exec_spec_read_counts, e lookup_execution_dyn_l2_factor_bitwise_counts, e lookup_execution_check_radix_gt_256_counts, e lookup_execution_get_p_limbs_counts, e lookup_execution_get_max_limbs_counts, e lookup_execution_check_written_storage_slot_counts, e lookup_execution_dispatch_to_alu_counts, e lookup_execution_dispatch_to_bitwise_counts, e lookup_execution_dispatch_to_cast_counts, e lookup_execution_dispatch_to_set_counts, e lookup_calldata_hashing_get_calldata_field_0_counts, e lookup_calldata_hashing_get_calldata_field_1_counts, e lookup_calldata_hashing_get_calldata_field_2_counts, e lookup_calldata_hashing_poseidon2_hash_counts, e lookup_tx_context_public_inputs_note_hash_tree_counts, e lookup_tx_context_public_inputs_nullifier_tree_counts, e lookup_tx_context_public_inputs_public_data_tree_counts, e lookup_tx_context_public_inputs_l1_l2_tree_counts, e lookup_tx_context_public_inputs_gas_used_counts, e lookup_tx_context_public_inputs_read_gas_limit_counts, e lookup_tx_context_public_inputs_read_reverted_counts, e lookup_tx_context_restore_state_on_revert_counts, e lookup_tx_context_public_inputs_write_note_hash_count_counts, e lookup_tx_context_public_inputs_write_nullifier_count_counts, e lookup_tx_context_public_inputs_write_l2_to_l1_message_count_counts, e lookup_tx_context_public_inputs_write_public_log_count_counts, e lookup_tx_read_phase_spec_counts, e lookup_tx_read_phase_length_counts, e lookup_tx_read_public_call_request_phase_counts, e lookup_tx_read_tree_insert_value_counts, e lookup_tx_note_hash_append_counts, e lookup_tx_nullifier_append_counts, e lookup_tx_read_l2_l1_msg_counts, e lookup_tx_write_l2_l1_msg_counts, e lookup_tx_read_effective_fee_public_inputs_counts, e lookup_tx_read_fee_payer_public_inputs_counts, e lookup_tx_balance_slot_poseidon2_counts, e lookup_tx_balance_read_counts, e lookup_tx_balance_validation_counts, e lookup_tx_write_fee_public_inputs_counts, e bc_decomposition_bytes, e bc_decomposition_bytes_pc_plus_1, e bc_decomposition_bytes_pc_plus_10, e bc_decomposition_bytes_pc_plus_11, e bc_decomposition_bytes_pc_plus_12, e bc_decomposition_bytes_pc_plus_13, e bc_decomposition_bytes_pc_plus_14, e bc_decomposition_bytes_pc_plus_15, e bc_decomposition_bytes_pc_plus_16, e bc_decomposition_bytes_pc_plus_17, e bc_decomposition_bytes_pc_plus_18, e bc_decomposition_bytes_pc_plus_19, e bc_decomposition_bytes_pc_plus_2, e bc_decomposition_bytes_pc_plus_20, e bc_decomposition_bytes_pc_plus_21, e bc_decomposition_bytes_pc_plus_22, e bc_decomposition_bytes_pc_plus_23, e bc_decomposition_bytes_pc_plus_24, e bc_decomposition_bytes_pc_plus_25, e bc_decomposition_bytes_pc_plus_26, e bc_decomposition_bytes_pc_plus_27, e bc_decomposition_bytes_pc_plus_28, e bc_decomposition_bytes_pc_plus_29, e bc_decomposition_bytes_pc_plus_3, e bc_decomposition_bytes_pc_plus_30, e bc_decomposition_bytes_pc_plus_31, e bc_decomposition_bytes_pc_plus_32, e bc_decomposition_bytes_pc_plus_33, e bc_decomposition_bytes_pc_plus_34, e bc_decomposition_bytes_pc_plus_35, e bc_decomposition_bytes_pc_plus_4, e bc_decomposition_bytes_pc_plus_5, e bc_decomposition_bytes_pc_plus_6, e bc_decomposition_bytes_pc_plus_7, e bc_decomposition_bytes_pc_plus_8, e bc_decomposition_bytes_pc_plus_9, e bc_decomposition_bytes_remaining, e bc_decomposition_id, e bc_decomposition_next_packed_pc, e bc_decomposition_pc, e bc_decomposition_sel, e bc_decomposition_sel_windows_gt_remaining, e bc_decomposition_start, e bc_hashing_bytecode_id, e bc_hashing_padding, e bc_hashing_pc_index_1, e bc_hashing_rounds_rem, e bc_hashing_sel, e bc_hashing_sel_not_start, e bc_hashing_start, e bitwise_acc_ia, e bitwise_acc_ib, e bitwise_acc_ic, e bitwise_ctr, e bitwise_op_id, e bitwise_sel, e bitwise_start, e calldata_context_id, e calldata_hashing_calldata_size, e calldata_hashing_context_id, e calldata_hashing_index_0_, e calldata_hashing_output_hash, e calldata_hashing_rounds_rem, e calldata_hashing_sel, e calldata_hashing_start, e calldata_index, e calldata_sel, e data_copy_clk, e data_copy_copy_size, e data_copy_dst_addr, e data_copy_dst_context_id, e data_copy_padding, e data_copy_read_addr, e data_copy_reads_left, e data_copy_sel, e data_copy_sel_cd_copy, e data_copy_src_context_id, e data_copy_start, e emit_public_log_contract_address, e emit_public_log_correct_tag, e emit_public_log_error_out_of_bounds, e emit_public_log_error_tag_mismatch, e emit_public_log_execution_clk, e emit_public_log_is_write_contract_address, e emit_public_log_is_write_memory_value, e emit_public_log_log_address, e emit_public_log_public_inputs_index, e emit_public_log_remaining_rows, e emit_public_log_seen_wrong_tag, e emit_public_log_sel, e emit_public_log_sel_write_to_public_inputs, e emit_public_log_space_id, e emit_public_log_start, e execution_bytecode_id, e execution_clk, e execution_context_id, e execution_contract_address, e execution_da_gas_limit, e execution_discard, e execution_dying_context_id, e execution_enqueued_call_start, e execution_internal_call_id, e execution_internal_call_return_id, e execution_is_static, e execution_l1_l2_tree_root, e execution_l2_gas_limit, e execution_last_child_id, e execution_last_child_returndata_addr, e execution_last_child_returndata_size, e execution_last_child_success, e execution_msg_sender, e execution_next_context_id, e execution_next_internal_call_id, e execution_parent_calldata_addr, e execution_parent_calldata_size, e execution_parent_da_gas_limit, e execution_parent_da_gas_used, e execution_parent_id, e execution_parent_l2_gas_limit, e execution_parent_l2_gas_used, e execution_pc, e execution_prev_da_gas_used, e execution_prev_l2_gas_used, e execution_prev_note_hash_tree_root, e execution_prev_note_hash_tree_size, e execution_prev_nullifier_tree_root, e execution_prev_nullifier_tree_size, e execution_prev_num_l2_to_l1_messages, e execution_prev_num_note_hashes_emitted, e execution_prev_num_nullifiers_emitted, e execution_prev_num_public_log_fields, e execution_prev_public_data_tree_root, e execution_prev_public_data_tree_size, e execution_prev_retrieved_bytecodes_tree_root, e execution_prev_retrieved_bytecodes_tree_size, e execution_prev_written_public_data_slots_tree_root, e execution_prev_written_public_data_slots_tree_size, e execution_sel, e execution_sel_first_row_in_context, e execution_transaction_fee, e ff_gt_a_hi, e ff_gt_a_lo, e ff_gt_b_hi, e ff_gt_b_lo, e ff_gt_cmp_rng_ctr, e ff_gt_p_sub_a_hi, e ff_gt_p_sub_a_lo, e ff_gt_p_sub_b_hi, e ff_gt_p_sub_b_lo, e ff_gt_sel, e ff_gt_sel_dec, e ff_gt_sel_gt, e keccak_memory_addr, e keccak_memory_clk, e keccak_memory_ctr, e keccak_memory_rw, e keccak_memory_sel, e keccak_memory_space_id, e keccak_memory_start_read, e keccak_memory_start_write, e keccak_memory_tag_error, e keccak_memory_val_0_, e keccak_memory_val_10_, e keccak_memory_val_11_, e keccak_memory_val_12_, e keccak_memory_val_13_, e keccak_memory_val_14_, e keccak_memory_val_15_, e keccak_memory_val_16_, e keccak_memory_val_17_, e keccak_memory_val_18_, e keccak_memory_val_19_, e keccak_memory_val_1_, e keccak_memory_val_20_, e keccak_memory_val_21_, e keccak_memory_val_22_, e keccak_memory_val_23_, e keccak_memory_val_2_, e keccak_memory_val_3_, e keccak_memory_val_4_, e keccak_memory_val_5_, e keccak_memory_val_6_, e keccak_memory_val_7_, e keccak_memory_val_8_, e keccak_memory_val_9_, e keccakf1600_clk, e keccakf1600_dst_addr, e keccakf1600_round, e keccakf1600_sel, e keccakf1600_sel_no_error, e keccakf1600_space_id, e keccakf1600_start, e keccakf1600_state_in_00, e keccakf1600_state_in_01, e keccakf1600_state_in_02, e keccakf1600_state_in_03, e keccakf1600_state_in_04, e keccakf1600_state_in_10, e keccakf1600_state_in_11, e keccakf1600_state_in_12, e keccakf1600_state_in_13, e keccakf1600_state_in_14, e keccakf1600_state_in_20, e keccakf1600_state_in_21, e keccakf1600_state_in_22, e keccakf1600_state_in_23, e keccakf1600_state_in_24, e keccakf1600_state_in_30, e keccakf1600_state_in_31, e keccakf1600_state_in_32, e keccakf1600_state_in_33, e keccakf1600_state_in_34, e keccakf1600_state_in_40, e keccakf1600_state_in_41, e keccakf1600_state_in_42, e keccakf1600_state_in_43, e keccakf1600_state_in_44, e memory_address, e memory_clk, e memory_rw, e memory_sel, e memory_space_id, e memory_tag, e memory_value, e merkle_check_index, e merkle_check_merkle_hash_separator, e merkle_check_path_len, e merkle_check_read_node, e merkle_check_read_root, e merkle_check_sel, e merkle_check_start, e merkle_check_write, e merkle_check_write_node, e merkle_check_write_root, e poseidon2_hash_a_0, e poseidon2_hash_a_1, e poseidon2_hash_a_2, e poseidon2_hash_a_3, e poseidon2_hash_input_0, e poseidon2_hash_input_1, e poseidon2_hash_input_2, e poseidon2_hash_num_perm_rounds_rem, e poseidon2_hash_output, e poseidon2_hash_sel, e poseidon2_hash_start, e public_data_check_clk, e public_data_check_sel, e public_data_check_write_idx, e public_data_squash_clk, e public_data_squash_final_value, e public_data_squash_leaf_slot, e public_data_squash_sel, e public_data_squash_write_to_public_inputs, e scalar_mul_bit_idx, e scalar_mul_point_inf, e scalar_mul_point_x, e scalar_mul_point_y, e scalar_mul_res_inf, e scalar_mul_res_x, e scalar_mul_res_y, e scalar_mul_scalar, e scalar_mul_sel, e scalar_mul_start, e scalar_mul_temp_inf, e scalar_mul_temp_x, e scalar_mul_temp_y, e sha256_a, e sha256_b, e sha256_c, e sha256_d, e sha256_e, e sha256_execution_clk, e sha256_f, e sha256_g, e sha256_h, e sha256_helper_w0, e sha256_helper_w1, e sha256_helper_w10, e sha256_helper_w11, e sha256_helper_w12, e sha256_helper_w13, e sha256_helper_w14, e sha256_helper_w15, e sha256_helper_w2, e sha256_helper_w3, e sha256_helper_w4, e sha256_helper_w5, e sha256_helper_w6, e sha256_helper_w7, e sha256_helper_w8, e sha256_helper_w9, e sha256_init_a, e sha256_init_b, e sha256_init_c, e sha256_init_d, e sha256_init_e, e sha256_init_f, e sha256_init_g, e sha256_init_h, e sha256_input_addr, e sha256_input_rounds_rem, e sha256_output_addr, e sha256_rounds_remaining, e sha256_sel, e sha256_sel_invalid_input_tag_err, e sha256_space_id, e sha256_start, e to_radix_acc, e to_radix_acc_under_p, e to_radix_limb, e to_radix_limb_eq_p, e to_radix_limb_index, e to_radix_limb_lt_p, e to_radix_mem_dst_addr, e to_radix_mem_execution_clk, e to_radix_mem_is_output_bits, e to_radix_mem_num_limbs, e to_radix_mem_radix, e to_radix_mem_sel, e to_radix_mem_sel_should_decompose, e to_radix_mem_sel_should_write_mem, e to_radix_mem_space_id, e to_radix_mem_start, e to_radix_mem_value_to_decompose, e to_radix_not_padding_limb, e to_radix_power, e to_radix_radix, e to_radix_safe_limbs, e to_radix_sel, e to_radix_start, e to_radix_value, e tx_da_gas_limit, e tx_discard, e tx_fee, e tx_is_revertible, e tx_is_teardown, e tx_l1_l2_tree_root, e tx_l1_l2_tree_size, e tx_l2_gas_limit, e tx_next_context_id, e tx_phase_value, e tx_prev_da_gas_used, e tx_prev_l2_gas_used, e tx_prev_note_hash_tree_root, e tx_prev_note_hash_tree_size, e tx_prev_nullifier_tree_root, e tx_prev_nullifier_tree_size, e tx_prev_num_l2_to_l1_messages, e tx_prev_num_note_hashes_emitted, e tx_prev_num_nullifiers_emitted, e tx_prev_num_public_log_fields, e tx_prev_public_data_tree_root, e tx_prev_public_data_tree_size, e tx_prev_retrieved_bytecodes_tree_root, e tx_prev_retrieved_bytecodes_tree_size, e tx_prev_written_public_data_slots_tree_root, e tx_prev_written_public_data_slots_tree_size, e tx_read_pi_offset, e tx_remaining_phase_counter, e tx_reverted, e tx_sel, e tx_start_phase, e tx_start_tx, e tx_tx_reverted #define AVM2_DERIVED_WITNESS_ENTITIES_E(e) e perm_data_copy_mem_write_inv, e perm_data_copy_mem_read_inv, e perm_ecc_mem_write_mem_0_inv, e perm_ecc_mem_write_mem_1_inv, e perm_ecc_mem_write_mem_2_inv, e perm_keccak_memory_slice_to_mem_inv, e perm_keccakf1600_read_to_slice_inv, e perm_keccakf1600_write_to_slice_inv, e perm_poseidon2_mem_pos_read_mem_0_inv, e perm_poseidon2_mem_pos_read_mem_1_inv, e perm_poseidon2_mem_pos_read_mem_2_inv, e perm_poseidon2_mem_pos_read_mem_3_inv, e perm_poseidon2_mem_pos_write_mem_0_inv, e perm_poseidon2_mem_pos_write_mem_1_inv, e perm_poseidon2_mem_pos_write_mem_2_inv, e perm_poseidon2_mem_pos_write_mem_3_inv, e perm_sha256_mem_mem_op_0_inv, e perm_sha256_mem_mem_op_1_inv, e perm_sha256_mem_mem_op_2_inv, e perm_sha256_mem_mem_op_3_inv, e perm_sha256_mem_mem_op_4_inv, e perm_sha256_mem_mem_op_5_inv, e perm_sha256_mem_mem_op_6_inv, e perm_sha256_mem_mem_op_7_inv, e perm_sha256_mem_mem_input_read_inv, e perm_to_radix_mem_write_mem_inv, e perm_bc_hashing_bytecode_length_bytes_inv, e perm_bc_hashing_get_packed_field_0_inv, e perm_bc_hashing_get_packed_field_1_inv, e perm_bc_hashing_get_packed_field_2_inv, e perm_public_data_check_squashing_inv, e perm_emit_public_log_read_mem_inv, e perm_get_contract_instance_mem_write_contract_instance_exists_inv, e perm_get_contract_instance_mem_write_contract_instance_member_inv, e perm_internal_call_push_call_stack_inv, e perm_context_ctx_stack_call_inv, e perm_addressing_base_address_from_memory_inv, e perm_addressing_indirect_from_memory_0_inv, e perm_addressing_indirect_from_memory_1_inv, e perm_addressing_indirect_from_memory_2_inv, e perm_addressing_indirect_from_memory_3_inv, e perm_addressing_indirect_from_memory_4_inv, e perm_addressing_indirect_from_memory_5_inv, e perm_addressing_indirect_from_memory_6_inv, e perm_registers_mem_op_0_inv, e perm_registers_mem_op_1_inv, e perm_registers_mem_op_2_inv, e perm_registers_mem_op_3_inv, e perm_registers_mem_op_4_inv, e perm_registers_mem_op_5_inv, e perm_sstore_storage_write_inv, e perm_execution_dispatch_to_cd_copy_inv, e perm_execution_dispatch_to_rd_copy_inv, e perm_execution_dispatch_to_get_contract_instance_inv, e perm_execution_dispatch_to_emit_public_log_inv, e perm_execution_dispatch_to_poseidon2_perm_inv, e perm_execution_dispatch_to_sha256_compression_inv, e perm_execution_dispatch_to_keccakf1600_inv, e perm_execution_dispatch_to_ecc_add_inv, e perm_execution_dispatch_to_to_radix_inv, e perm_calldata_hashing_check_final_size_inv, e perm_tx_read_calldata_hash_inv, e perm_tx_dispatch_exec_start_inv, e perm_tx_dispatch_exec_end_inv, e perm_tx_balance_update_inv, e lookup_range_check_dyn_rng_chk_pow_2_inv, e lookup_range_check_dyn_diff_is_u16_inv, e lookup_range_check_r0_is_u16_inv, e lookup_range_check_r1_is_u16_inv, e lookup_range_check_r2_is_u16_inv, e lookup_range_check_r3_is_u16_inv, e lookup_range_check_r4_is_u16_inv, e lookup_range_check_r5_is_u16_inv, e lookup_range_check_r6_is_u16_inv, e lookup_range_check_r7_is_u16_inv, e lookup_ff_gt_a_lo_range_inv, e lookup_ff_gt_a_hi_range_inv, e lookup_gt_gt_range_inv, e lookup_alu_tag_max_bits_value_inv, e lookup_alu_range_check_decomposition_a_lo_inv, e lookup_alu_range_check_decomposition_a_hi_inv, e lookup_alu_range_check_decomposition_b_lo_inv, e lookup_alu_range_check_decomposition_b_hi_inv, e lookup_alu_range_check_mul_c_hi_inv, e lookup_alu_range_check_div_remainder_inv, e lookup_alu_ff_gt_inv, e lookup_alu_int_gt_inv, e lookup_alu_shifts_two_pow_inv, e lookup_alu_large_trunc_canonical_dec_inv, e lookup_alu_range_check_trunc_mid_inv, e lookup_bitwise_integral_tag_length_inv, e lookup_bitwise_byte_operations_inv, e lookup_memory_range_check_limb_0_inv, e lookup_memory_range_check_limb_1_inv, e lookup_memory_range_check_limb_2_inv, e lookup_memory_tag_max_bits_inv, e lookup_memory_range_check_write_tagged_value_inv, e lookup_data_copy_offset_plus_size_is_gt_data_size_inv, e lookup_data_copy_check_src_addr_in_range_inv, e lookup_data_copy_check_dst_addr_in_range_inv, e lookup_data_copy_sel_has_reads_inv, e lookup_data_copy_col_read_inv, e lookup_ecc_mem_check_dst_addr_in_range_inv, e lookup_ecc_mem_input_output_ecc_add_inv, e lookup_keccakf1600_theta_xor_01_inv, e lookup_keccakf1600_theta_xor_02_inv, e lookup_keccakf1600_theta_xor_03_inv, e lookup_keccakf1600_theta_xor_row_0_inv, e lookup_keccakf1600_theta_xor_11_inv, e lookup_keccakf1600_theta_xor_12_inv, e lookup_keccakf1600_theta_xor_13_inv, e lookup_keccakf1600_theta_xor_row_1_inv, e lookup_keccakf1600_theta_xor_21_inv, e lookup_keccakf1600_theta_xor_22_inv, e lookup_keccakf1600_theta_xor_23_inv, e lookup_keccakf1600_theta_xor_row_2_inv, e lookup_keccakf1600_theta_xor_31_inv, e lookup_keccakf1600_theta_xor_32_inv, e lookup_keccakf1600_theta_xor_33_inv, e lookup_keccakf1600_theta_xor_row_3_inv, e lookup_keccakf1600_theta_xor_41_inv, e lookup_keccakf1600_theta_xor_42_inv, e lookup_keccakf1600_theta_xor_43_inv, e lookup_keccakf1600_theta_xor_row_4_inv, e lookup_keccakf1600_theta_combined_xor_0_inv, e lookup_keccakf1600_theta_combined_xor_1_inv, e lookup_keccakf1600_theta_combined_xor_2_inv, e lookup_keccakf1600_theta_combined_xor_3_inv, e lookup_keccakf1600_theta_combined_xor_4_inv, e lookup_keccakf1600_state_theta_00_inv, e lookup_keccakf1600_state_theta_01_inv, e lookup_keccakf1600_state_theta_02_inv, e lookup_keccakf1600_state_theta_03_inv, e lookup_keccakf1600_state_theta_04_inv, e lookup_keccakf1600_state_theta_10_inv, e lookup_keccakf1600_state_theta_11_inv, e lookup_keccakf1600_state_theta_12_inv, e lookup_keccakf1600_state_theta_13_inv, e lookup_keccakf1600_state_theta_14_inv, e lookup_keccakf1600_state_theta_20_inv, e lookup_keccakf1600_state_theta_21_inv, e lookup_keccakf1600_state_theta_22_inv, e lookup_keccakf1600_state_theta_23_inv, e lookup_keccakf1600_state_theta_24_inv, e lookup_keccakf1600_state_theta_30_inv, e lookup_keccakf1600_state_theta_31_inv, e lookup_keccakf1600_state_theta_32_inv, e lookup_keccakf1600_state_theta_33_inv, e lookup_keccakf1600_state_theta_34_inv, e lookup_keccakf1600_state_theta_40_inv, e lookup_keccakf1600_state_theta_41_inv, e lookup_keccakf1600_state_theta_42_inv, e lookup_keccakf1600_state_theta_43_inv, e lookup_keccakf1600_state_theta_44_inv, e lookup_keccakf1600_theta_limb_02_range_inv, e lookup_keccakf1600_theta_limb_04_range_inv, e lookup_keccakf1600_theta_limb_10_range_inv, e lookup_keccakf1600_theta_limb_12_range_inv, e lookup_keccakf1600_theta_limb_14_range_inv, e lookup_keccakf1600_theta_limb_21_range_inv, e lookup_keccakf1600_theta_limb_23_range_inv, e lookup_keccakf1600_theta_limb_30_range_inv, e lookup_keccakf1600_theta_limb_32_range_inv, e lookup_keccakf1600_theta_limb_33_range_inv, e lookup_keccakf1600_theta_limb_40_range_inv, e lookup_keccakf1600_theta_limb_41_range_inv, e lookup_keccakf1600_theta_limb_43_range_inv, e lookup_keccakf1600_theta_limb_44_range_inv, e lookup_keccakf1600_theta_limb_01_range_inv, e lookup_keccakf1600_theta_limb_03_range_inv, e lookup_keccakf1600_theta_limb_11_range_inv, e lookup_keccakf1600_theta_limb_13_range_inv, e lookup_keccakf1600_theta_limb_20_range_inv, e lookup_keccakf1600_theta_limb_22_range_inv, e lookup_keccakf1600_theta_limb_24_range_inv, e lookup_keccakf1600_theta_limb_31_range_inv, e lookup_keccakf1600_theta_limb_34_range_inv, e lookup_keccakf1600_theta_limb_42_range_inv, e lookup_keccakf1600_state_pi_and_00_inv, e lookup_keccakf1600_state_pi_and_01_inv, e lookup_keccakf1600_state_pi_and_02_inv, e lookup_keccakf1600_state_pi_and_03_inv, e lookup_keccakf1600_state_pi_and_04_inv, e lookup_keccakf1600_state_pi_and_10_inv, e lookup_keccakf1600_state_pi_and_11_inv, e lookup_keccakf1600_state_pi_and_12_inv, e lookup_keccakf1600_state_pi_and_13_inv, e lookup_keccakf1600_state_pi_and_14_inv, e lookup_keccakf1600_state_pi_and_20_inv, e lookup_keccakf1600_state_pi_and_21_inv, e lookup_keccakf1600_state_pi_and_22_inv, e lookup_keccakf1600_state_pi_and_23_inv, e lookup_keccakf1600_state_pi_and_24_inv, e lookup_keccakf1600_state_pi_and_30_inv, e lookup_keccakf1600_state_pi_and_31_inv, e lookup_keccakf1600_state_pi_and_32_inv, e lookup_keccakf1600_state_pi_and_33_inv, e lookup_keccakf1600_state_pi_and_34_inv, e lookup_keccakf1600_state_pi_and_40_inv, e lookup_keccakf1600_state_pi_and_41_inv, e lookup_keccakf1600_state_pi_and_42_inv, e lookup_keccakf1600_state_pi_and_43_inv, e lookup_keccakf1600_state_pi_and_44_inv, e lookup_keccakf1600_state_chi_00_inv, e lookup_keccakf1600_state_chi_01_inv, e lookup_keccakf1600_state_chi_02_inv, e lookup_keccakf1600_state_chi_03_inv, e lookup_keccakf1600_state_chi_04_inv, e lookup_keccakf1600_state_chi_10_inv, e lookup_keccakf1600_state_chi_11_inv, e lookup_keccakf1600_state_chi_12_inv, e lookup_keccakf1600_state_chi_13_inv, e lookup_keccakf1600_state_chi_14_inv, e lookup_keccakf1600_state_chi_20_inv, e lookup_keccakf1600_state_chi_21_inv, e lookup_keccakf1600_state_chi_22_inv, e lookup_keccakf1600_state_chi_23_inv, e lookup_keccakf1600_state_chi_24_inv, e lookup_keccakf1600_state_chi_30_inv, e lookup_keccakf1600_state_chi_31_inv, e lookup_keccakf1600_state_chi_32_inv, e lookup_keccakf1600_state_chi_33_inv, e lookup_keccakf1600_state_chi_34_inv, e lookup_keccakf1600_state_chi_40_inv, e lookup_keccakf1600_state_chi_41_inv, e lookup_keccakf1600_state_chi_42_inv, e lookup_keccakf1600_state_chi_43_inv, e lookup_keccakf1600_state_chi_44_inv, e lookup_keccakf1600_round_cst_inv, e lookup_keccakf1600_state_iota_00_inv, e lookup_keccakf1600_src_out_of_range_toggle_inv, e lookup_keccakf1600_dst_out_of_range_toggle_inv, e lookup_poseidon2_mem_check_src_addr_in_range_inv, e lookup_poseidon2_mem_check_dst_addr_in_range_inv, e lookup_poseidon2_mem_input_output_poseidon2_perm_inv, e lookup_to_radix_limb_range_inv, e lookup_to_radix_limb_less_than_radix_range_inv, e lookup_to_radix_fetch_safe_limbs_inv, e lookup_to_radix_fetch_p_limb_inv, e lookup_to_radix_limb_p_diff_range_inv, e lookup_scalar_mul_to_radix_inv, e lookup_scalar_mul_double_inv, e lookup_scalar_mul_add_inv, e lookup_sha256_range_comp_w_lhs_inv, e lookup_sha256_range_comp_w_rhs_inv, e lookup_sha256_range_rhs_w_7_inv, e lookup_sha256_range_rhs_w_18_inv, e lookup_sha256_range_rhs_w_3_inv, e lookup_sha256_w_s_0_xor_0_inv, e lookup_sha256_w_s_0_xor_1_inv, e lookup_sha256_range_rhs_w_17_inv, e lookup_sha256_range_rhs_w_19_inv, e lookup_sha256_range_rhs_w_10_inv, e lookup_sha256_w_s_1_xor_0_inv, e lookup_sha256_w_s_1_xor_1_inv, e lookup_sha256_range_rhs_e_6_inv, e lookup_sha256_range_rhs_e_11_inv, e lookup_sha256_range_rhs_e_25_inv, e lookup_sha256_s_1_xor_0_inv, e lookup_sha256_s_1_xor_1_inv, e lookup_sha256_ch_and_0_inv, e lookup_sha256_ch_and_1_inv, e lookup_sha256_ch_xor_inv, e lookup_sha256_round_constant_inv, e lookup_sha256_range_rhs_a_2_inv, e lookup_sha256_range_rhs_a_13_inv, e lookup_sha256_range_rhs_a_22_inv, e lookup_sha256_s_0_xor_0_inv, e lookup_sha256_s_0_xor_1_inv, e lookup_sha256_maj_and_0_inv, e lookup_sha256_maj_and_1_inv, e lookup_sha256_maj_and_2_inv, e lookup_sha256_maj_xor_0_inv, e lookup_sha256_maj_xor_1_inv, e lookup_sha256_range_comp_next_a_lhs_inv, e lookup_sha256_range_comp_next_a_rhs_inv, e lookup_sha256_range_comp_next_e_lhs_inv, e lookup_sha256_range_comp_next_e_rhs_inv, e lookup_sha256_range_comp_a_rhs_inv, e lookup_sha256_range_comp_b_rhs_inv, e lookup_sha256_range_comp_c_rhs_inv, e lookup_sha256_range_comp_d_rhs_inv, e lookup_sha256_range_comp_e_rhs_inv, e lookup_sha256_range_comp_f_rhs_inv, e lookup_sha256_range_comp_g_rhs_inv, e lookup_sha256_range_comp_h_rhs_inv, e lookup_sha256_mem_check_state_addr_in_range_inv, e lookup_sha256_mem_check_input_addr_in_range_inv, e lookup_sha256_mem_check_output_addr_in_range_inv, e lookup_to_radix_mem_check_dst_addr_in_range_inv, e lookup_to_radix_mem_check_radix_lt_2_inv, e lookup_to_radix_mem_check_radix_gt_256_inv, e lookup_to_radix_mem_input_output_to_radix_inv, e lookup_poseidon2_hash_poseidon2_perm_inv, e lookup_address_derivation_salted_initialization_hash_poseidon2_0_inv, e lookup_address_derivation_salted_initialization_hash_poseidon2_1_inv, e lookup_address_derivation_partial_address_poseidon2_inv, e lookup_address_derivation_public_keys_hash_poseidon2_0_inv, e lookup_address_derivation_public_keys_hash_poseidon2_1_inv, e lookup_address_derivation_public_keys_hash_poseidon2_2_inv, e lookup_address_derivation_public_keys_hash_poseidon2_3_inv, e lookup_address_derivation_public_keys_hash_poseidon2_4_inv, e lookup_address_derivation_preaddress_poseidon2_inv, e lookup_address_derivation_preaddress_scalar_mul_inv, e lookup_address_derivation_address_ecadd_inv, e lookup_bc_decomposition_bytes_are_bytes_inv, e lookup_bc_hashing_poseidon2_hash_inv, e lookup_merkle_check_merkle_poseidon2_read_inv, e lookup_merkle_check_merkle_poseidon2_write_inv, e lookup_indexed_tree_check_silo_poseidon2_inv, e lookup_indexed_tree_check_low_leaf_value_validation_inv, e lookup_indexed_tree_check_low_leaf_next_value_validation_inv, e lookup_indexed_tree_check_low_leaf_poseidon2_inv, e lookup_indexed_tree_check_updated_low_leaf_poseidon2_inv, e lookup_indexed_tree_check_low_leaf_merkle_check_inv, e lookup_indexed_tree_check_new_leaf_poseidon2_inv, e lookup_indexed_tree_check_new_leaf_merkle_check_inv, e lookup_indexed_tree_check_write_value_to_public_inputs_inv, e lookup_public_data_squash_leaf_slot_increase_ff_gt_inv, e lookup_public_data_squash_clk_diff_range_lo_inv, e lookup_public_data_squash_clk_diff_range_hi_inv, e lookup_public_data_check_clk_diff_range_lo_inv, e lookup_public_data_check_clk_diff_range_hi_inv, e lookup_public_data_check_silo_poseidon2_inv, e lookup_public_data_check_low_leaf_slot_validation_inv, e lookup_public_data_check_low_leaf_next_slot_validation_inv, e lookup_public_data_check_low_leaf_poseidon2_0_inv, e lookup_public_data_check_low_leaf_poseidon2_1_inv, e lookup_public_data_check_updated_low_leaf_poseidon2_0_inv, e lookup_public_data_check_updated_low_leaf_poseidon2_1_inv, e lookup_public_data_check_low_leaf_merkle_check_inv, e lookup_public_data_check_new_leaf_poseidon2_0_inv, e lookup_public_data_check_new_leaf_poseidon2_1_inv, e lookup_public_data_check_new_leaf_merkle_check_inv, e lookup_public_data_check_write_public_data_to_public_inputs_inv, e lookup_public_data_check_write_writes_length_to_public_inputs_inv, e lookup_update_check_timestamp_from_public_inputs_inv, e lookup_update_check_delayed_public_mutable_slot_poseidon2_inv, e lookup_update_check_update_hash_public_data_read_inv, e lookup_update_check_update_hash_poseidon2_inv, e lookup_update_check_update_hi_metadata_range_inv, e lookup_update_check_update_lo_metadata_range_inv, e lookup_update_check_timestamp_is_lt_timestamp_of_change_inv, e lookup_contract_instance_retrieval_check_protocol_address_range_inv, e lookup_contract_instance_retrieval_read_derived_address_from_public_inputs_inv, e lookup_contract_instance_retrieval_deployment_nullifier_read_inv, e lookup_contract_instance_retrieval_address_derivation_inv, e lookup_contract_instance_retrieval_update_check_inv, e lookup_class_id_derivation_class_id_poseidon2_0_inv, e lookup_class_id_derivation_class_id_poseidon2_1_inv, e lookup_bc_retrieval_contract_instance_retrieval_inv, e lookup_bc_retrieval_class_id_derivation_inv, e lookup_bc_retrieval_is_new_class_check_inv, e lookup_bc_retrieval_retrieved_bytecodes_insertion_inv, e lookup_instr_fetching_pc_abs_diff_positive_inv, e lookup_instr_fetching_instr_abs_diff_positive_inv, e lookup_instr_fetching_tag_value_validation_inv, e lookup_instr_fetching_bytecode_size_from_bc_dec_inv, e lookup_instr_fetching_bytes_from_bc_dec_inv, e lookup_instr_fetching_wire_instruction_info_inv, e lookup_emit_public_log_check_memory_out_of_bounds_inv, e lookup_emit_public_log_check_log_fields_count_inv, e lookup_emit_public_log_write_data_to_public_inputs_inv, e lookup_get_contract_instance_precomputed_info_inv, e lookup_get_contract_instance_contract_instance_retrieval_inv, e lookup_l1_to_l2_message_tree_check_merkle_check_inv, e lookup_internal_call_unwind_call_stack_inv, e lookup_context_ctx_stack_rollback_inv, e lookup_context_ctx_stack_return_inv, e lookup_addressing_relative_overflow_result_0_inv, e lookup_addressing_relative_overflow_result_1_inv, e lookup_addressing_relative_overflow_result_2_inv, e lookup_addressing_relative_overflow_result_3_inv, e lookup_addressing_relative_overflow_result_4_inv, e lookup_addressing_relative_overflow_result_5_inv, e lookup_addressing_relative_overflow_result_6_inv, e lookup_gas_addressing_gas_read_inv, e lookup_gas_is_out_of_gas_l2_inv, e lookup_gas_is_out_of_gas_da_inv, e lookup_note_hash_tree_check_silo_poseidon2_inv, e lookup_note_hash_tree_check_read_first_nullifier_inv, e lookup_note_hash_tree_check_nonce_computation_poseidon2_inv, e lookup_note_hash_tree_check_unique_note_hash_poseidon2_inv, e lookup_note_hash_tree_check_merkle_check_inv, e lookup_note_hash_tree_check_write_note_hash_to_public_inputs_inv, e lookup_emit_notehash_notehash_tree_write_inv, e lookup_emit_nullifier_write_nullifier_inv, e lookup_external_call_is_l2_gas_left_gt_allocated_inv, e lookup_external_call_is_da_gas_left_gt_allocated_inv, e lookup_get_env_var_precomputed_info_inv, e lookup_get_env_var_read_from_public_inputs_col0_inv, e lookup_get_env_var_read_from_public_inputs_col1_inv, e lookup_l1_to_l2_message_exists_l1_to_l2_msg_leaf_index_in_range_inv, e lookup_l1_to_l2_message_exists_l1_to_l2_msg_read_inv, e lookup_notehash_exists_note_hash_leaf_index_in_range_inv, e lookup_notehash_exists_note_hash_read_inv, e lookup_nullifier_exists_nullifier_exists_check_inv, e lookup_send_l2_to_l1_msg_recipient_check_inv, e lookup_send_l2_to_l1_msg_write_l2_to_l1_msg_inv, e lookup_sload_storage_read_inv, e lookup_sstore_record_written_storage_slot_inv, e lookup_execution_bytecode_retrieval_result_inv, e lookup_execution_instruction_fetching_result_inv, e lookup_execution_instruction_fetching_body_inv, e lookup_execution_exec_spec_read_inv, e lookup_execution_dyn_l2_factor_bitwise_inv, e lookup_execution_check_radix_gt_256_inv, e lookup_execution_get_p_limbs_inv, e lookup_execution_get_max_limbs_inv, e lookup_execution_check_written_storage_slot_inv, e lookup_execution_dispatch_to_alu_inv, e lookup_execution_dispatch_to_bitwise_inv, e lookup_execution_dispatch_to_cast_inv, e lookup_execution_dispatch_to_set_inv, e lookup_calldata_hashing_get_calldata_field_0_inv, e lookup_calldata_hashing_get_calldata_field_1_inv, e lookup_calldata_hashing_get_calldata_field_2_inv, e lookup_calldata_hashing_poseidon2_hash_inv, e lookup_tx_context_public_inputs_note_hash_tree_inv, e lookup_tx_context_public_inputs_nullifier_tree_inv, e lookup_tx_context_public_inputs_public_data_tree_inv, e lookup_tx_context_public_inputs_l1_l2_tree_inv, e lookup_tx_context_public_inputs_gas_used_inv, e lookup_tx_context_public_inputs_read_gas_limit_inv, e lookup_tx_context_public_inputs_read_reverted_inv, e lookup_tx_context_restore_state_on_revert_inv, e lookup_tx_context_public_inputs_write_note_hash_count_inv, e lookup_tx_context_public_inputs_write_nullifier_count_inv, e lookup_tx_context_public_inputs_write_l2_to_l1_message_count_inv, e lookup_tx_context_public_inputs_write_public_log_count_inv, e lookup_tx_read_phase_spec_inv, e lookup_tx_read_phase_length_inv, e lookup_tx_read_public_call_request_phase_inv, e lookup_tx_read_tree_insert_value_inv, e lookup_tx_note_hash_append_inv, e lookup_tx_nullifier_append_inv, e lookup_tx_read_l2_l1_msg_inv, e lookup_tx_write_l2_l1_msg_inv, e lookup_tx_read_effective_fee_public_inputs_inv, e lookup_tx_read_fee_payer_public_inputs_inv, e lookup_tx_balance_slot_poseidon2_inv, e lookup_tx_balance_read_inv, e lookup_tx_balance_validation_inv, e lookup_tx_write_fee_public_inputs_inv #define AVM2_SHIFTED_ENTITIES_E(e) e bc_decomposition_bytes_shift, e bc_decomposition_bytes_pc_plus_1_shift, e bc_decomposition_bytes_pc_plus_10_shift, e bc_decomposition_bytes_pc_plus_11_shift, e bc_decomposition_bytes_pc_plus_12_shift, e bc_decomposition_bytes_pc_plus_13_shift, e bc_decomposition_bytes_pc_plus_14_shift, e bc_decomposition_bytes_pc_plus_15_shift, e bc_decomposition_bytes_pc_plus_16_shift, e bc_decomposition_bytes_pc_plus_17_shift, e bc_decomposition_bytes_pc_plus_18_shift, e bc_decomposition_bytes_pc_plus_19_shift, e bc_decomposition_bytes_pc_plus_2_shift, e bc_decomposition_bytes_pc_plus_20_shift, e bc_decomposition_bytes_pc_plus_21_shift, e bc_decomposition_bytes_pc_plus_22_shift, e bc_decomposition_bytes_pc_plus_23_shift, e bc_decomposition_bytes_pc_plus_24_shift, e bc_decomposition_bytes_pc_plus_25_shift, e bc_decomposition_bytes_pc_plus_26_shift, e bc_decomposition_bytes_pc_plus_27_shift, e bc_decomposition_bytes_pc_plus_28_shift, e bc_decomposition_bytes_pc_plus_29_shift, e bc_decomposition_bytes_pc_plus_3_shift, e bc_decomposition_bytes_pc_plus_30_shift, e bc_decomposition_bytes_pc_plus_31_shift, e bc_decomposition_bytes_pc_plus_32_shift, e bc_decomposition_bytes_pc_plus_33_shift, e bc_decomposition_bytes_pc_plus_34_shift, e bc_decomposition_bytes_pc_plus_35_shift, e bc_decomposition_bytes_pc_plus_4_shift, e bc_decomposition_bytes_pc_plus_5_shift, e bc_decomposition_bytes_pc_plus_6_shift, e bc_decomposition_bytes_pc_plus_7_shift, e bc_decomposition_bytes_pc_plus_8_shift, e bc_decomposition_bytes_pc_plus_9_shift, e bc_decomposition_bytes_remaining_shift, e bc_decomposition_id_shift, e bc_decomposition_next_packed_pc_shift, e bc_decomposition_pc_shift, e bc_decomposition_sel_shift, e bc_decomposition_sel_windows_gt_remaining_shift, e bc_decomposition_start_shift, e bc_hashing_bytecode_id_shift, e bc_hashing_padding_shift, e bc_hashing_pc_index_1_shift, e bc_hashing_rounds_rem_shift, e bc_hashing_sel_shift, e bc_hashing_sel_not_start_shift, e bc_hashing_start_shift, e bitwise_acc_ia_shift, e bitwise_acc_ib_shift, e bitwise_acc_ic_shift, e bitwise_ctr_shift, e bitwise_op_id_shift, e bitwise_sel_shift, e bitwise_start_shift, e calldata_context_id_shift, e calldata_hashing_calldata_size_shift, e calldata_hashing_context_id_shift, e calldata_hashing_index_0__shift, e calldata_hashing_output_hash_shift, e calldata_hashing_rounds_rem_shift, e calldata_hashing_sel_shift, e calldata_hashing_start_shift, e calldata_index_shift, e calldata_sel_shift, e data_copy_clk_shift, e data_copy_copy_size_shift, e data_copy_dst_addr_shift, e data_copy_dst_context_id_shift, e data_copy_padding_shift, e data_copy_read_addr_shift, e data_copy_reads_left_shift, e data_copy_sel_shift, e data_copy_sel_cd_copy_shift, e data_copy_src_context_id_shift, e data_copy_start_shift, e emit_public_log_contract_address_shift, e emit_public_log_correct_tag_shift, e emit_public_log_error_out_of_bounds_shift, e emit_public_log_error_tag_mismatch_shift, e emit_public_log_execution_clk_shift, e emit_public_log_is_write_contract_address_shift, e emit_public_log_is_write_memory_value_shift, e emit_public_log_log_address_shift, e emit_public_log_public_inputs_index_shift, e emit_public_log_remaining_rows_shift, e emit_public_log_seen_wrong_tag_shift, e emit_public_log_sel_shift, e emit_public_log_sel_write_to_public_inputs_shift, e emit_public_log_space_id_shift, e emit_public_log_start_shift, e execution_bytecode_id_shift, e execution_clk_shift, e execution_context_id_shift, e execution_contract_address_shift, e execution_da_gas_limit_shift, e execution_discard_shift, e execution_dying_context_id_shift, e execution_enqueued_call_start_shift, e execution_internal_call_id_shift, e execution_internal_call_return_id_shift, e execution_is_static_shift, e execution_l1_l2_tree_root_shift, e execution_l2_gas_limit_shift, e execution_last_child_id_shift, e execution_last_child_returndata_addr_shift, e execution_last_child_returndata_size_shift, e execution_last_child_success_shift, e execution_msg_sender_shift, e execution_next_context_id_shift, e execution_next_internal_call_id_shift, e execution_parent_calldata_addr_shift, e execution_parent_calldata_size_shift, e execution_parent_da_gas_limit_shift, e execution_parent_da_gas_used_shift, e execution_parent_id_shift, e execution_parent_l2_gas_limit_shift, e execution_parent_l2_gas_used_shift, e execution_pc_shift, e execution_prev_da_gas_used_shift, e execution_prev_l2_gas_used_shift, e execution_prev_note_hash_tree_root_shift, e execution_prev_note_hash_tree_size_shift, e execution_prev_nullifier_tree_root_shift, e execution_prev_nullifier_tree_size_shift, e execution_prev_num_l2_to_l1_messages_shift, e execution_prev_num_note_hashes_emitted_shift, e execution_prev_num_nullifiers_emitted_shift, e execution_prev_num_public_log_fields_shift, e execution_prev_public_data_tree_root_shift, e execution_prev_public_data_tree_size_shift, e execution_prev_retrieved_bytecodes_tree_root_shift, e execution_prev_retrieved_bytecodes_tree_size_shift, e execution_prev_written_public_data_slots_tree_root_shift, e execution_prev_written_public_data_slots_tree_size_shift, e execution_sel_shift, e execution_sel_first_row_in_context_shift, e execution_transaction_fee_shift, e ff_gt_a_hi_shift, e ff_gt_a_lo_shift, e ff_gt_b_hi_shift, e ff_gt_b_lo_shift, e ff_gt_cmp_rng_ctr_shift, e ff_gt_p_sub_a_hi_shift, e ff_gt_p_sub_a_lo_shift, e ff_gt_p_sub_b_hi_shift, e ff_gt_p_sub_b_lo_shift, e ff_gt_sel_shift, e ff_gt_sel_dec_shift, e ff_gt_sel_gt_shift, e keccak_memory_addr_shift, e keccak_memory_clk_shift, e keccak_memory_ctr_shift, e keccak_memory_rw_shift, e keccak_memory_sel_shift, e keccak_memory_space_id_shift, e keccak_memory_start_read_shift, e keccak_memory_start_write_shift, e keccak_memory_tag_error_shift, e keccak_memory_val_0__shift, e keccak_memory_val_10__shift, e keccak_memory_val_11__shift, e keccak_memory_val_12__shift, e keccak_memory_val_13__shift, e keccak_memory_val_14__shift, e keccak_memory_val_15__shift, e keccak_memory_val_16__shift, e keccak_memory_val_17__shift, e keccak_memory_val_18__shift, e keccak_memory_val_19__shift, e keccak_memory_val_1__shift, e keccak_memory_val_20__shift, e keccak_memory_val_21__shift, e keccak_memory_val_22__shift, e keccak_memory_val_23__shift, e keccak_memory_val_2__shift, e keccak_memory_val_3__shift, e keccak_memory_val_4__shift, e keccak_memory_val_5__shift, e keccak_memory_val_6__shift, e keccak_memory_val_7__shift, e keccak_memory_val_8__shift, e keccak_memory_val_9__shift, e keccakf1600_clk_shift, e keccakf1600_dst_addr_shift, e keccakf1600_round_shift, e keccakf1600_sel_shift, e keccakf1600_sel_no_error_shift, e keccakf1600_space_id_shift, e keccakf1600_start_shift, e keccakf1600_state_in_00_shift, e keccakf1600_state_in_01_shift, e keccakf1600_state_in_02_shift, e keccakf1600_state_in_03_shift, e keccakf1600_state_in_04_shift, e keccakf1600_state_in_10_shift, e keccakf1600_state_in_11_shift, e keccakf1600_state_in_12_shift, e keccakf1600_state_in_13_shift, e keccakf1600_state_in_14_shift, e keccakf1600_state_in_20_shift, e keccakf1600_state_in_21_shift, e keccakf1600_state_in_22_shift, e keccakf1600_state_in_23_shift, e keccakf1600_state_in_24_shift, e keccakf1600_state_in_30_shift, e keccakf1600_state_in_31_shift, e keccakf1600_state_in_32_shift, e keccakf1600_state_in_33_shift, e keccakf1600_state_in_34_shift, e keccakf1600_state_in_40_shift, e keccakf1600_state_in_41_shift, e keccakf1600_state_in_42_shift, e keccakf1600_state_in_43_shift, e keccakf1600_state_in_44_shift, e memory_address_shift, e memory_clk_shift, e memory_rw_shift, e memory_sel_shift, e memory_space_id_shift, e memory_tag_shift, e memory_value_shift, e merkle_check_index_shift, e merkle_check_merkle_hash_separator_shift, e merkle_check_path_len_shift, e merkle_check_read_node_shift, e merkle_check_read_root_shift, e merkle_check_sel_shift, e merkle_check_start_shift, e merkle_check_write_shift, e merkle_check_write_node_shift, e merkle_check_write_root_shift, e poseidon2_hash_a_0_shift, e poseidon2_hash_a_1_shift, e poseidon2_hash_a_2_shift, e poseidon2_hash_a_3_shift, e poseidon2_hash_input_0_shift, e poseidon2_hash_input_1_shift, e poseidon2_hash_input_2_shift, e poseidon2_hash_num_perm_rounds_rem_shift, e poseidon2_hash_output_shift, e poseidon2_hash_sel_shift, e poseidon2_hash_start_shift, e public_data_check_clk_shift, e public_data_check_sel_shift, e public_data_check_write_idx_shift, e public_data_squash_clk_shift, e public_data_squash_final_value_shift, e public_data_squash_leaf_slot_shift, e public_data_squash_sel_shift, e public_data_squash_write_to_public_inputs_shift, e scalar_mul_bit_idx_shift, e scalar_mul_point_inf_shift, e scalar_mul_point_x_shift, e scalar_mul_point_y_shift, e scalar_mul_res_inf_shift, e scalar_mul_res_x_shift, e scalar_mul_res_y_shift, e scalar_mul_scalar_shift, e scalar_mul_sel_shift, e scalar_mul_start_shift, e scalar_mul_temp_inf_shift, e scalar_mul_temp_x_shift, e scalar_mul_temp_y_shift, e sha256_a_shift, e sha256_b_shift, e sha256_c_shift, e sha256_d_shift, e sha256_e_shift, e sha256_execution_clk_shift, e sha256_f_shift, e sha256_g_shift, e sha256_h_shift, e sha256_helper_w0_shift, e sha256_helper_w1_shift, e sha256_helper_w10_shift, e sha256_helper_w11_shift, e sha256_helper_w12_shift, e sha256_helper_w13_shift, e sha256_helper_w14_shift, e sha256_helper_w15_shift, e sha256_helper_w2_shift, e sha256_helper_w3_shift, e sha256_helper_w4_shift, e sha256_helper_w5_shift, e sha256_helper_w6_shift, e sha256_helper_w7_shift, e sha256_helper_w8_shift, e sha256_helper_w9_shift, e sha256_init_a_shift, e sha256_init_b_shift, e sha256_init_c_shift, e sha256_init_d_shift, e sha256_init_e_shift, e sha256_init_f_shift, e sha256_init_g_shift, e sha256_init_h_shift, e sha256_input_addr_shift, e sha256_input_rounds_rem_shift, e sha256_output_addr_shift, e sha256_rounds_remaining_shift, e sha256_sel_shift, e sha256_sel_invalid_input_tag_err_shift, e sha256_space_id_shift, e sha256_start_shift, e to_radix_acc_shift, e to_radix_acc_under_p_shift, e to_radix_limb_shift, e to_radix_limb_eq_p_shift, e to_radix_limb_index_shift, e to_radix_limb_lt_p_shift, e to_radix_mem_dst_addr_shift, e to_radix_mem_execution_clk_shift, e to_radix_mem_is_output_bits_shift, e to_radix_mem_num_limbs_shift, e to_radix_mem_radix_shift, e to_radix_mem_sel_shift, e to_radix_mem_sel_should_decompose_shift, e to_radix_mem_sel_should_write_mem_shift, e to_radix_mem_space_id_shift, e to_radix_mem_start_shift, e to_radix_mem_value_to_decompose_shift, e to_radix_not_padding_limb_shift, e to_radix_power_shift, e to_radix_radix_shift, e to_radix_safe_limbs_shift, e to_radix_sel_shift, e to_radix_start_shift, e to_radix_value_shift, e tx_da_gas_limit_shift, e tx_discard_shift, e tx_fee_shift, e tx_is_revertible_shift, e tx_is_teardown_shift, e tx_l1_l2_tree_root_shift, e tx_l1_l2_tree_size_shift, e tx_l2_gas_limit_shift, e tx_next_context_id_shift, e tx_phase_value_shift, e tx_prev_da_gas_used_shift, e tx_prev_l2_gas_used_shift, e tx_prev_note_hash_tree_root_shift, e tx_prev_note_hash_tree_size_shift, e tx_prev_nullifier_tree_root_shift, e tx_prev_nullifier_tree_size_shift, e tx_prev_num_l2_to_l1_messages_shift, e tx_prev_num_note_hashes_emitted_shift, e tx_prev_num_nullifiers_emitted_shift, e tx_prev_num_public_log_fields_shift, e tx_prev_public_data_tree_root_shift, e tx_prev_public_data_tree_size_shift, e tx_prev_retrieved_bytecodes_tree_root_shift, e tx_prev_retrieved_bytecodes_tree_size_shift, e tx_prev_written_public_data_slots_tree_root_shift, e tx_prev_written_public_data_slots_tree_size_shift, e tx_read_pi_offset_shift, e tx_remaining_phase_counter_shift, e tx_reverted_shift, e tx_sel_shift, e tx_start_phase_shift, e tx_start_tx_shift, e tx_tx_reverted_shift #define AVM2_TO_BE_SHIFTED_E(e) e bc_decomposition_bytes, e bc_decomposition_bytes_pc_plus_1, e bc_decomposition_bytes_pc_plus_10, e bc_decomposition_bytes_pc_plus_11, e bc_decomposition_bytes_pc_plus_12, e bc_decomposition_bytes_pc_plus_13, e bc_decomposition_bytes_pc_plus_14, e bc_decomposition_bytes_pc_plus_15, e bc_decomposition_bytes_pc_plus_16, e bc_decomposition_bytes_pc_plus_17, e bc_decomposition_bytes_pc_plus_18, e bc_decomposition_bytes_pc_plus_19, e bc_decomposition_bytes_pc_plus_2, e bc_decomposition_bytes_pc_plus_20, e bc_decomposition_bytes_pc_plus_21, e bc_decomposition_bytes_pc_plus_22, e bc_decomposition_bytes_pc_plus_23, e bc_decomposition_bytes_pc_plus_24, e bc_decomposition_bytes_pc_plus_25, e bc_decomposition_bytes_pc_plus_26, e bc_decomposition_bytes_pc_plus_27, e bc_decomposition_bytes_pc_plus_28, e bc_decomposition_bytes_pc_plus_29, e bc_decomposition_bytes_pc_plus_3, e bc_decomposition_bytes_pc_plus_30, e bc_decomposition_bytes_pc_plus_31, e bc_decomposition_bytes_pc_plus_32, e bc_decomposition_bytes_pc_plus_33, e bc_decomposition_bytes_pc_plus_34, e bc_decomposition_bytes_pc_plus_35, e bc_decomposition_bytes_pc_plus_4, e bc_decomposition_bytes_pc_plus_5, e bc_decomposition_bytes_pc_plus_6, e bc_decomposition_bytes_pc_plus_7, e bc_decomposition_bytes_pc_plus_8, e bc_decomposition_bytes_pc_plus_9, e bc_decomposition_bytes_remaining, e bc_decomposition_id, e bc_decomposition_next_packed_pc, e bc_decomposition_pc, e bc_decomposition_sel, e bc_decomposition_sel_windows_gt_remaining, e bc_decomposition_start, e bc_hashing_bytecode_id, e bc_hashing_padding, e bc_hashing_pc_index_1, e bc_hashing_rounds_rem, e bc_hashing_sel, e bc_hashing_sel_not_start, e bc_hashing_start, e bitwise_acc_ia, e bitwise_acc_ib, e bitwise_acc_ic, e bitwise_ctr, e bitwise_op_id, e bitwise_sel, e bitwise_start, e calldata_context_id, e calldata_hashing_calldata_size, e calldata_hashing_context_id, e calldata_hashing_index_0_, e calldata_hashing_output_hash, e calldata_hashing_rounds_rem, e calldata_hashing_sel, e calldata_hashing_start, e calldata_index, e calldata_sel, e data_copy_clk, e data_copy_copy_size, e data_copy_dst_addr, e data_copy_dst_context_id, e data_copy_padding, e data_copy_read_addr, e data_copy_reads_left, e data_copy_sel, e data_copy_sel_cd_copy, e data_copy_src_context_id, e data_copy_start, e emit_public_log_contract_address, e emit_public_log_correct_tag, e emit_public_log_error_out_of_bounds, e emit_public_log_error_tag_mismatch, e emit_public_log_execution_clk, e emit_public_log_is_write_contract_address, e emit_public_log_is_write_memory_value, e emit_public_log_log_address, e emit_public_log_public_inputs_index, e emit_public_log_remaining_rows, e emit_public_log_seen_wrong_tag, e emit_public_log_sel, e emit_public_log_sel_write_to_public_inputs, e emit_public_log_space_id, e emit_public_log_start, e execution_bytecode_id, e execution_clk, e execution_context_id, e execution_contract_address, e execution_da_gas_limit, e execution_discard, e execution_dying_context_id, e execution_enqueued_call_start, e execution_internal_call_id, e execution_internal_call_return_id, e execution_is_static, e execution_l1_l2_tree_root, e execution_l2_gas_limit, e execution_last_child_id, e execution_last_child_returndata_addr, e execution_last_child_returndata_size, e execution_last_child_success, e execution_msg_sender, e execution_next_context_id, e execution_next_internal_call_id, e execution_parent_calldata_addr, e execution_parent_calldata_size, e execution_parent_da_gas_limit, e execution_parent_da_gas_used, e execution_parent_id, e execution_parent_l2_gas_limit, e execution_parent_l2_gas_used, e execution_pc, e execution_prev_da_gas_used, e execution_prev_l2_gas_used, e execution_prev_note_hash_tree_root, e execution_prev_note_hash_tree_size, e execution_prev_nullifier_tree_root, e execution_prev_nullifier_tree_size, e execution_prev_num_l2_to_l1_messages, e execution_prev_num_note_hashes_emitted, e execution_prev_num_nullifiers_emitted, e execution_prev_num_public_log_fields, e execution_prev_public_data_tree_root, e execution_prev_public_data_tree_size, e execution_prev_retrieved_bytecodes_tree_root, e execution_prev_retrieved_bytecodes_tree_size, e execution_prev_written_public_data_slots_tree_root, e execution_prev_written_public_data_slots_tree_size, e execution_sel, e execution_sel_first_row_in_context, e execution_transaction_fee, e ff_gt_a_hi, e ff_gt_a_lo, e ff_gt_b_hi, e ff_gt_b_lo, e ff_gt_cmp_rng_ctr, e ff_gt_p_sub_a_hi, e ff_gt_p_sub_a_lo, e ff_gt_p_sub_b_hi, e ff_gt_p_sub_b_lo, e ff_gt_sel, e ff_gt_sel_dec, e ff_gt_sel_gt, e keccak_memory_addr, e keccak_memory_clk, e keccak_memory_ctr, e keccak_memory_rw, e keccak_memory_sel, e keccak_memory_space_id, e keccak_memory_start_read, e keccak_memory_start_write, e keccak_memory_tag_error, e keccak_memory_val_0_, e keccak_memory_val_10_, e keccak_memory_val_11_, e keccak_memory_val_12_, e keccak_memory_val_13_, e keccak_memory_val_14_, e keccak_memory_val_15_, e keccak_memory_val_16_, e keccak_memory_val_17_, e keccak_memory_val_18_, e keccak_memory_val_19_, e keccak_memory_val_1_, e keccak_memory_val_20_, e keccak_memory_val_21_, e keccak_memory_val_22_, e keccak_memory_val_23_, e keccak_memory_val_2_, e keccak_memory_val_3_, e keccak_memory_val_4_, e keccak_memory_val_5_, e keccak_memory_val_6_, e keccak_memory_val_7_, e keccak_memory_val_8_, e keccak_memory_val_9_, e keccakf1600_clk, e keccakf1600_dst_addr, e keccakf1600_round, e keccakf1600_sel, e keccakf1600_sel_no_error, e keccakf1600_space_id, e keccakf1600_start, e keccakf1600_state_in_00, e keccakf1600_state_in_01, e keccakf1600_state_in_02, e keccakf1600_state_in_03, e keccakf1600_state_in_04, e keccakf1600_state_in_10, e keccakf1600_state_in_11, e keccakf1600_state_in_12, e keccakf1600_state_in_13, e keccakf1600_state_in_14, e keccakf1600_state_in_20, e keccakf1600_state_in_21, e keccakf1600_state_in_22, e keccakf1600_state_in_23, e keccakf1600_state_in_24, e keccakf1600_state_in_30, e keccakf1600_state_in_31, e keccakf1600_state_in_32, e keccakf1600_state_in_33, e keccakf1600_state_in_34, e keccakf1600_state_in_40, e keccakf1600_state_in_41, e keccakf1600_state_in_42, e keccakf1600_state_in_43, e keccakf1600_state_in_44, e memory_address, e memory_clk, e memory_rw, e memory_sel, e memory_space_id, e memory_tag, e memory_value, e merkle_check_index, e merkle_check_merkle_hash_separator, e merkle_check_path_len, e merkle_check_read_node, e merkle_check_read_root, e merkle_check_sel, e merkle_check_start, e merkle_check_write, e merkle_check_write_node, e merkle_check_write_root, e poseidon2_hash_a_0, e poseidon2_hash_a_1, e poseidon2_hash_a_2, e poseidon2_hash_a_3, e poseidon2_hash_input_0, e poseidon2_hash_input_1, e poseidon2_hash_input_2, e poseidon2_hash_num_perm_rounds_rem, e poseidon2_hash_output, e poseidon2_hash_sel, e poseidon2_hash_start, e public_data_check_clk, e public_data_check_sel, e public_data_check_write_idx, e public_data_squash_clk, e public_data_squash_final_value, e public_data_squash_leaf_slot, e public_data_squash_sel, e public_data_squash_write_to_public_inputs, e scalar_mul_bit_idx, e scalar_mul_point_inf, e scalar_mul_point_x, e scalar_mul_point_y, e scalar_mul_res_inf, e scalar_mul_res_x, e scalar_mul_res_y, e scalar_mul_scalar, e scalar_mul_sel, e scalar_mul_start, e scalar_mul_temp_inf, e scalar_mul_temp_x, e scalar_mul_temp_y, e sha256_a, e sha256_b, e sha256_c, e sha256_d, e sha256_e, e sha256_execution_clk, e sha256_f, e sha256_g, e sha256_h, e sha256_helper_w0, e sha256_helper_w1, e sha256_helper_w10, e sha256_helper_w11, e sha256_helper_w12, e sha256_helper_w13, e sha256_helper_w14, e sha256_helper_w15, e sha256_helper_w2, e sha256_helper_w3, e sha256_helper_w4, e sha256_helper_w5, e sha256_helper_w6, e sha256_helper_w7, e sha256_helper_w8, e sha256_helper_w9, e sha256_init_a, e sha256_init_b, e sha256_init_c, e sha256_init_d, e sha256_init_e, e sha256_init_f, e sha256_init_g, e sha256_init_h, e sha256_input_addr, e sha256_input_rounds_rem, e sha256_output_addr, e sha256_rounds_remaining, e sha256_sel, e sha256_sel_invalid_input_tag_err, e sha256_space_id, e sha256_start, e to_radix_acc, e to_radix_acc_under_p, e to_radix_limb, e to_radix_limb_eq_p, e to_radix_limb_index, e to_radix_limb_lt_p, e to_radix_mem_dst_addr, e to_radix_mem_execution_clk, e to_radix_mem_is_output_bits, e to_radix_mem_num_limbs, e to_radix_mem_radix, e to_radix_mem_sel, e to_radix_mem_sel_should_decompose, e to_radix_mem_sel_should_write_mem, e to_radix_mem_space_id, e to_radix_mem_start, e to_radix_mem_value_to_decompose, e to_radix_not_padding_limb, e to_radix_power, e to_radix_radix, e to_radix_safe_limbs, e to_radix_sel, e to_radix_start, e to_radix_value, e tx_da_gas_limit, e tx_discard, e tx_fee, e tx_is_revertible, e tx_is_teardown, e tx_l1_l2_tree_root, e tx_l1_l2_tree_size, e tx_l2_gas_limit, e tx_next_context_id, e tx_phase_value, e tx_prev_da_gas_used, e tx_prev_l2_gas_used, e tx_prev_note_hash_tree_root, e tx_prev_note_hash_tree_size, e tx_prev_nullifier_tree_root, e tx_prev_nullifier_tree_size, e tx_prev_num_l2_to_l1_messages, e tx_prev_num_note_hashes_emitted, e tx_prev_num_nullifiers_emitted, e tx_prev_num_public_log_fields, e tx_prev_public_data_tree_root, e tx_prev_public_data_tree_size, e tx_prev_retrieved_bytecodes_tree_root, e tx_prev_retrieved_bytecodes_tree_size, e tx_prev_written_public_data_slots_tree_root, e tx_prev_written_public_data_slots_tree_size, e tx_read_pi_offset, e tx_remaining_phase_counter, e tx_reverted, e tx_sel, e tx_start_phase, e tx_start_tx, e tx_tx_reverted @@ -36,16 +36,16 @@ enum class ColumnAndShifts { SENTINEL_DO_NOT_USE, }; -constexpr auto NUM_COLUMNS_WITH_SHIFTS = 3444; -constexpr auto NUM_COLUMNS_WITHOUT_SHIFTS = 3080; -constexpr auto NUM_PRECOMPUTED_ENTITIES = 122; -constexpr auto NUM_WIRE_ENTITIES = 2514; +constexpr auto NUM_COLUMNS_WITH_SHIFTS = 3438; +constexpr auto NUM_COLUMNS_WITHOUT_SHIFTS = 3074; +constexpr auto NUM_PRECOMPUTED_ENTITIES = 119; +constexpr auto NUM_WIRE_ENTITIES = 2511; constexpr auto NUM_DERIVED_ENTITIES = 444; constexpr auto NUM_WITNESS_ENTITIES = NUM_WIRE_ENTITIES + NUM_DERIVED_ENTITIES; constexpr auto NUM_WIRES_TO_BE_SHIFTED = 364; constexpr auto NUM_SHIFTED_ENTITIES = 364; constexpr auto NUM_UNSHIFTED_ENTITIES = NUM_COLUMNS_WITHOUT_SHIFTS; -constexpr auto NUM_ALL_ENTITIES = 3444; +constexpr auto NUM_ALL_ENTITIES = 3438; /* * Layout for all entities is: diff --git a/barretenberg/cpp/src/barretenberg/vm2/generated/flavor_variables.hpp b/barretenberg/cpp/src/barretenberg/vm2/generated/flavor_variables.hpp index 6f013f072dab..22c2e6bc3473 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/generated/flavor_variables.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/generated/flavor_variables.hpp @@ -140,11 +140,11 @@ namespace bb::avm2 { struct AvmFlavorVariables { - static constexpr size_t NUM_PRECOMPUTED_ENTITIES = 122; - static constexpr size_t NUM_WITNESS_ENTITIES = 2958; + static constexpr size_t NUM_PRECOMPUTED_ENTITIES = 119; + static constexpr size_t NUM_WITNESS_ENTITIES = 2955; static constexpr size_t NUM_SHIFTED_ENTITIES = 364; - static constexpr size_t NUM_WIRES = 2514; - static constexpr size_t NUM_ALL_ENTITIES = 3444; + static constexpr size_t NUM_WIRES = 2511; + static constexpr size_t NUM_ALL_ENTITIES = 3438; // Need to be templated for recursive verifier template diff --git a/barretenberg/cpp/src/barretenberg/vm2/generated/relations/lookups_tx.hpp b/barretenberg/cpp/src/barretenberg/vm2/generated/relations/lookups_tx.hpp index 816e1ed908f6..8e91d55c8b49 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/generated/relations/lookups_tx.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/generated/relations/lookups_tx.hpp @@ -16,27 +16,18 @@ namespace bb::avm2 { struct lookup_tx_read_phase_spec_settings_ { static constexpr std::string_view NAME = "LOOKUP_TX_READ_PHASE_SPEC"; static constexpr std::string_view RELATION_NAME = "tx"; - static constexpr size_t LOOKUP_TUPLE_SIZE = 16; + static constexpr size_t LOOKUP_TUPLE_SIZE = 13; static constexpr Column SRC_SELECTOR = Column::tx_sel; static constexpr Column DST_SELECTOR = Column::precomputed_sel_phase; static constexpr Column COUNTS = Column::lookup_tx_read_phase_spec_counts; static constexpr Column INVERSES = Column::lookup_tx_read_phase_spec_inv; static constexpr std::array SRC_COLUMNS = { - ColumnAndShifts::tx_phase_value, - ColumnAndShifts::tx_is_public_call_request, - ColumnAndShifts::tx_is_teardown, - ColumnAndShifts::tx_is_collect_fee, - ColumnAndShifts::tx_is_tree_padding, - ColumnAndShifts::tx_is_cleanup, - ColumnAndShifts::tx_is_revertible, - ColumnAndShifts::tx_read_pi_start_offset, - ColumnAndShifts::tx_read_pi_length_offset, - ColumnAndShifts::tx_sel_non_revertible_append_note_hash, - ColumnAndShifts::tx_sel_non_revertible_append_nullifier, - ColumnAndShifts::tx_sel_non_revertible_append_l2_l1_msg, - ColumnAndShifts::tx_sel_revertible_append_note_hash, - ColumnAndShifts::tx_sel_revertible_append_nullifier, - ColumnAndShifts::tx_sel_revertible_append_l2_l1_msg, + ColumnAndShifts::tx_phase_value, ColumnAndShifts::tx_is_public_call_request, + ColumnAndShifts::tx_is_teardown, ColumnAndShifts::tx_is_collect_fee, + ColumnAndShifts::tx_is_tree_padding, ColumnAndShifts::tx_is_cleanup, + ColumnAndShifts::tx_is_revertible, ColumnAndShifts::tx_read_pi_start_offset, + ColumnAndShifts::tx_read_pi_length_offset, ColumnAndShifts::tx_sel_append_note_hash, + ColumnAndShifts::tx_sel_append_nullifier, ColumnAndShifts::tx_sel_append_l2_l1_msg, ColumnAndShifts::tx_next_phase_on_revert }; static constexpr std::array DST_COLUMNS = { @@ -49,12 +40,9 @@ struct lookup_tx_read_phase_spec_settings_ { ColumnAndShifts::precomputed_is_revertible, ColumnAndShifts::precomputed_read_pi_start_offset, ColumnAndShifts::precomputed_read_pi_length_offset, - ColumnAndShifts::precomputed_sel_non_revertible_append_note_hash, - ColumnAndShifts::precomputed_sel_non_revertible_append_nullifier, - ColumnAndShifts::precomputed_sel_non_revertible_append_l2_l1_msg, - ColumnAndShifts::precomputed_sel_revertible_append_note_hash, - ColumnAndShifts::precomputed_sel_revertible_append_nullifier, - ColumnAndShifts::precomputed_sel_revertible_append_l2_l1_msg, + ColumnAndShifts::precomputed_sel_append_note_hash, + ColumnAndShifts::precomputed_sel_append_nullifier, + ColumnAndShifts::precomputed_sel_append_l2_l1_msg, ColumnAndShifts::precomputed_next_phase_on_revert }; }; @@ -151,7 +139,7 @@ struct lookup_tx_note_hash_append_settings_ { ColumnAndShifts::tx_prev_note_hash_tree_size, ColumnAndShifts::tx_prev_note_hash_tree_root, ColumnAndShifts::precomputed_zero, - ColumnAndShifts::tx_sel_revertible_append_note_hash, + ColumnAndShifts::tx_is_revertible, ColumnAndShifts::tx_prev_num_note_hashes_emitted, ColumnAndShifts::tx_discard, ColumnAndShifts::tx_next_note_hash_tree_root diff --git a/barretenberg/cpp/src/barretenberg/vm2/generated/relations/tx_context_impl.hpp b/barretenberg/cpp/src/barretenberg/vm2/generated/relations/tx_context_impl.hpp index e4163fab6215..528590e6c894 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/generated/relations/tx_context_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/generated/relations/tx_context_impl.hpp @@ -40,18 +40,12 @@ void tx_contextImpl::accumulate(ContainerOverSubrelations& evals, FF(uint256_t{ 1521641569468562450UL, 665739211013355724UL, 15332520522532078145UL, 1150206617693738821UL }); const auto constants_AVM_RETRIEVED_BYTECODES_TREE_INITIAL_SIZE = FF(1); const auto tx_NOT_LAST_ROW = in.get(C::tx_sel) * in.get(C::tx_sel_shift); - const auto tx_SEL_CAN_EMIT_NOTE_HASH = in.get(C::tx_is_public_call_request) + - in.get(C::tx_sel_non_revertible_append_note_hash) + - in.get(C::tx_sel_revertible_append_note_hash); - const auto tx_SEL_CAN_EMIT_NULLIFIER = in.get(C::tx_is_public_call_request) + - in.get(C::tx_sel_non_revertible_append_nullifier) + - in.get(C::tx_sel_revertible_append_nullifier); + const auto tx_SEL_CAN_EMIT_NOTE_HASH = in.get(C::tx_is_public_call_request) + in.get(C::tx_sel_append_note_hash); + const auto tx_SEL_CAN_EMIT_NULLIFIER = in.get(C::tx_is_public_call_request) + in.get(C::tx_sel_append_nullifier); const auto tx_SEL_CAN_WRITE_PUBLIC_DATA = in.get(C::tx_is_public_call_request) + in.get(C::tx_is_collect_fee); const auto tx_SEL_CAN_WRITE_WRITTEN_PUBLIC_DATA_SLOTS = in.get(C::tx_is_public_call_request); const auto tx_SEL_CAN_EMIT_PUBLIC_LOG = in.get(C::tx_is_public_call_request); - const auto tx_SEL_CAN_EMIT_L2_L1_MSG = in.get(C::tx_is_public_call_request) + - in.get(C::tx_sel_non_revertible_append_l2_l1_msg) + - in.get(C::tx_sel_revertible_append_l2_l1_msg); + const auto tx_SEL_CAN_EMIT_L2_L1_MSG = in.get(C::tx_is_public_call_request) + in.get(C::tx_sel_append_l2_l1_msg); { using View = typename std::tuple_element_t<0, ContainerOverSubrelations>::View; diff --git a/barretenberg/cpp/src/barretenberg/vm2/generated/relations/tx_impl.hpp b/barretenberg/cpp/src/barretenberg/vm2/generated/relations/tx_impl.hpp index f85ba389f749..5a6610f87be7 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/generated/relations/tx_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/generated/relations/tx_impl.hpp @@ -241,18 +241,15 @@ void txImpl::accumulate(ContainerOverSubrelations& evals, { using View = typename std::tuple_element_t<29, ContainerOverSubrelations>::View; auto tmp = (static_cast(in.get(C::tx_is_tree_insert_phase)) - - (static_cast(in.get(C::tx_sel_revertible_append_note_hash)) + - static_cast(in.get(C::tx_sel_non_revertible_append_note_hash)) + - static_cast(in.get(C::tx_sel_revertible_append_nullifier)) + - static_cast(in.get(C::tx_sel_non_revertible_append_nullifier)))); + (static_cast(in.get(C::tx_sel_append_note_hash)) + + static_cast(in.get(C::tx_sel_append_nullifier)))); std::get<29>(evals) += (tmp * scaling_factor); } { using View = typename std::tuple_element_t<30, ContainerOverSubrelations>::View; auto tmp = (static_cast(in.get(C::tx_sel_try_note_hash_append)) - (static_cast(in.get(C::tx_sel)) - static_cast(in.get(C::tx_is_padded))) * - (static_cast(in.get(C::tx_sel_revertible_append_note_hash)) + - static_cast(in.get(C::tx_sel_non_revertible_append_note_hash)))); + static_cast(in.get(C::tx_sel_append_note_hash))); std::get<30>(evals) += (tmp * scaling_factor); } { // MAX_NOTE_HASH_WRITES_REACHED @@ -291,8 +288,7 @@ void txImpl::accumulate(ContainerOverSubrelations& evals, using View = typename std::tuple_element_t<35, ContainerOverSubrelations>::View; auto tmp = (static_cast(in.get(C::tx_sel_try_nullifier_append)) - (static_cast(in.get(C::tx_sel)) - static_cast(in.get(C::tx_is_padded))) * - (static_cast(in.get(C::tx_sel_revertible_append_nullifier)) + - static_cast(in.get(C::tx_sel_non_revertible_append_nullifier)))); + static_cast(in.get(C::tx_sel_append_nullifier))); std::get<35>(evals) += (tmp * scaling_factor); } { @@ -365,8 +361,7 @@ void txImpl::accumulate(ContainerOverSubrelations& evals, using View = typename std::tuple_element_t<45, ContainerOverSubrelations>::View; auto tmp = (static_cast(in.get(C::tx_sel_try_l2_l1_msg_append)) - (static_cast(in.get(C::tx_sel)) - static_cast(in.get(C::tx_is_padded))) * - (static_cast(in.get(C::tx_sel_revertible_append_l2_l1_msg)) + - static_cast(in.get(C::tx_sel_non_revertible_append_l2_l1_msg)))); + static_cast(in.get(C::tx_sel_append_l2_l1_msg))); std::get<45>(evals) += (tmp * scaling_factor); } { // MAX_L2_L1_MSG_WRITES_REACHED diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/lib/phase_spec.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/lib/phase_spec.cpp index 637423b8db69..8a804ed18b89 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/lib/phase_spec.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/lib/phase_spec.cpp @@ -21,21 +21,21 @@ const std::unordered_map& get_tx_phase_spec_map() .read_pi_start_offset = AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_NULLIFIERS_ROW_IDX, .read_pi_length_offset = AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_ARRAY_LENGTHS_NULLIFIERS_ROW_IDX, - .non_revertible_append_nullifier = true, + .append_nullifier = true, } }, { TransactionPhase::NR_NOTE_INSERTION, { .read_pi_start_offset = AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_NOTE_HASHES_ROW_IDX, .read_pi_length_offset = AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_ARRAY_LENGTHS_NOTE_HASHES_ROW_IDX, - .non_revertible_append_note_hash = true, + .append_note_hash = true, } }, { TransactionPhase::NR_L2_TO_L1_MESSAGE, { .read_pi_start_offset = AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX, .read_pi_length_offset = AVM_PUBLIC_INPUTS_PREVIOUS_NON_REVERTIBLE_ACCUMULATED_DATA_ARRAY_LENGTHS_L2_TO_L1_MSGS_ROW_IDX, - .non_revertible_append_l2_l1_msg = true, + .append_l2_l1_msg = true, } }, { TransactionPhase::SETUP, { @@ -49,7 +49,7 @@ const std::unordered_map& get_tx_phase_spec_map() .read_pi_start_offset = AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_NULLIFIERS_ROW_IDX, .read_pi_length_offset = AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_ARRAY_LENGTHS_NULLIFIERS_ROW_IDX, - .revertible_append_nullifier = true, + .append_nullifier = true, .next_phase_on_revert = static_cast(TransactionPhase::TEARDOWN), } }, { TransactionPhase::R_NOTE_INSERTION, @@ -58,7 +58,7 @@ const std::unordered_map& get_tx_phase_spec_map() .read_pi_start_offset = AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_NOTE_HASHES_ROW_IDX, .read_pi_length_offset = AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_ARRAY_LENGTHS_NOTE_HASHES_ROW_IDX, - .revertible_append_note_hash = true, + .append_note_hash = true, .next_phase_on_revert = static_cast(TransactionPhase::TEARDOWN), } }, { TransactionPhase::R_L2_TO_L1_MESSAGE, @@ -67,7 +67,7 @@ const std::unordered_map& get_tx_phase_spec_map() .read_pi_start_offset = AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX, .read_pi_length_offset = AVM_PUBLIC_INPUTS_PREVIOUS_REVERTIBLE_ACCUMULATED_DATA_ARRAY_LENGTHS_L2_TO_L1_MSGS_ROW_IDX, - .revertible_append_l2_l1_msg = true, + .append_l2_l1_msg = true, .next_phase_on_revert = static_cast(TransactionPhase::TEARDOWN), } }, { TransactionPhase::APP_LOGIC, diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/lib/phase_spec.hpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/lib/phase_spec.hpp index bfb373e26f33..8f7649f5b270 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/lib/phase_spec.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/lib/phase_spec.hpp @@ -17,12 +17,9 @@ struct TxPhaseSpec { bool is_revertible = false; uint32_t read_pi_start_offset = 0; uint32_t read_pi_length_offset = 0; - bool non_revertible_append_note_hash = false; - bool non_revertible_append_nullifier = false; - bool non_revertible_append_l2_l1_msg = false; - bool revertible_append_note_hash = false; - bool revertible_append_nullifier = false; - bool revertible_append_l2_l1_msg = false; + bool append_note_hash = false; + bool append_nullifier = false; + bool append_l2_l1_msg = false; uint8_t next_phase_on_revert = 0; }; diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.cpp index 2c0ab208227a..f39415ef39df 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/precomputed_trace.cpp @@ -400,12 +400,9 @@ void PrecomputedTraceBuilder::process_phase_table(TraceContainer& trace) { C::precomputed_is_revertible, spec.is_revertible ? 1 : 0 }, { C::precomputed_read_pi_start_offset, spec.read_pi_start_offset }, { C::precomputed_read_pi_length_offset, spec.read_pi_length_offset }, - { C::precomputed_sel_non_revertible_append_note_hash, spec.non_revertible_append_note_hash ? 1 : 0 }, - { C::precomputed_sel_non_revertible_append_nullifier, spec.non_revertible_append_nullifier ? 1 : 0 }, - { C::precomputed_sel_non_revertible_append_l2_l1_msg, spec.non_revertible_append_l2_l1_msg ? 1 : 0 }, - { C::precomputed_sel_revertible_append_note_hash, spec.revertible_append_note_hash ? 1 : 0 }, - { C::precomputed_sel_revertible_append_nullifier, spec.revertible_append_nullifier ? 1 : 0 }, - { C::precomputed_sel_revertible_append_l2_l1_msg, spec.revertible_append_l2_l1_msg ? 1 : 0 }, + { C::precomputed_sel_append_note_hash, spec.append_note_hash ? 1 : 0 }, + { C::precomputed_sel_append_nullifier, spec.append_nullifier ? 1 : 0 }, + { C::precomputed_sel_append_l2_l1_msg, spec.append_l2_l1_msg ? 1 : 0 }, { C::precomputed_next_phase_on_revert, spec.next_phase_on_revert }, }; diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/tx_trace.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/tx_trace.cpp index c765ab5df280..c041cc37b6fd 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/tx_trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/tx_trace.cpp @@ -79,30 +79,6 @@ bool is_revertible(TransactionPhase phase) return get_tx_phase_spec_map().at(phase).is_revertible; } -/** - * @brief Returns true if the given phase is a note hash insertion phase. - * - * @param phase - * @return true if the given phase is a note hash insert phase, false otherwise. - */ -bool is_note_hash_insert_phase(TransactionPhase phase) -{ - return get_tx_phase_spec_map().at(phase).non_revertible_append_note_hash || - get_tx_phase_spec_map().at(phase).revertible_append_note_hash; -} - -/** - * @brief Returns true if the given phase is a nullifier insertion phase. - * - * @param phase - * @return true if the given phase is a nullifier insertion phase, false otherwise. - */ -bool is_nullifier_insert_phase(TransactionPhase phase) -{ - return get_tx_phase_spec_map().at(phase).non_revertible_append_nullifier || - get_tx_phase_spec_map().at(phase).revertible_append_nullifier; -} - /** * @brief Returns true if the given phase is a one-shot phase, i.e., a phase with exactly one phase event/row. * One-shot phases are COLLECT_GAS_FEES, TREE_PADDING and CLEANUP. @@ -249,16 +225,13 @@ std::vector> handle_phase_spec(TransactionPhase phase) { C::tx_is_revertible, phase_spec.is_revertible ? 1 : 0 }, { C::tx_read_pi_start_offset, phase_spec.read_pi_start_offset }, { C::tx_read_pi_length_offset, phase_spec.read_pi_length_offset }, - { C::tx_sel_non_revertible_append_note_hash, phase_spec.non_revertible_append_note_hash ? 1 : 0 }, - { C::tx_sel_non_revertible_append_nullifier, phase_spec.non_revertible_append_nullifier ? 1 : 0 }, - { C::tx_sel_non_revertible_append_l2_l1_msg, phase_spec.non_revertible_append_l2_l1_msg ? 1 : 0 }, - { C::tx_sel_revertible_append_note_hash, phase_spec.revertible_append_note_hash ? 1 : 0 }, - { C::tx_sel_revertible_append_nullifier, phase_spec.revertible_append_nullifier ? 1 : 0 }, - { C::tx_sel_revertible_append_l2_l1_msg, phase_spec.revertible_append_l2_l1_msg ? 1 : 0 }, + { C::tx_sel_append_note_hash, phase_spec.append_note_hash ? 1 : 0 }, + { C::tx_sel_append_nullifier, phase_spec.append_nullifier ? 1 : 0 }, + { C::tx_sel_append_l2_l1_msg, phase_spec.append_l2_l1_msg ? 1 : 0 }, { C::tx_next_phase_on_revert, phase_spec.next_phase_on_revert }, // Directly derived from the phase spec but not part of the phase spec struct. - { C::tx_is_tree_insert_phase, (is_note_hash_insert_phase(phase) || is_nullifier_insert_phase(phase)) ? 1 : 0 }, + { C::tx_is_tree_insert_phase, (phase_spec.append_note_hash || phase_spec.append_nullifier) ? 1 : 0 }, }; } @@ -395,10 +368,12 @@ std::vector> handle_append_tree_event(const PrivateAppendTreeEv TransactionPhase phase, const TxContextEvent& state_before) { - if (is_note_hash_insert_phase(phase)) { + const auto& phase_spec = get_tx_phase_spec_map().at(phase); + + if (phase_spec.append_note_hash) { return handle_note_hash_append(event, state_before); } - if (is_nullifier_insert_phase(phase)) { + if (phase_spec.append_nullifier) { return handle_nullifier_append(event, state_before); } diff --git a/barretenberg/cpp/src/barretenberg/vm2/tracegen/tx_trace.test.cpp b/barretenberg/cpp/src/barretenberg/vm2/tracegen/tx_trace.test.cpp index 84bbc00260ca..4218a14892f9 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/tracegen/tx_trace.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/tracegen/tx_trace.test.cpp @@ -169,7 +169,7 @@ TEST(TxTraceGenTest, BasicFirstPaddedRow) ROW_FIELD_EQ(tx_phase_value, static_cast(TransactionPhase::NR_NULLIFIER_INSERTION)), ROW_FIELD_EQ(tx_is_padded, 1), ROW_FIELD_EQ(tx_is_tree_insert_phase, 1), - ROW_FIELD_EQ(tx_sel_non_revertible_append_nullifier, 1), + ROW_FIELD_EQ(tx_sel_append_nullifier, 1), ROW_FIELD_EQ(tx_start_tx, 1), ROW_FIELD_EQ(tx_should_read_gas_limit, 1), ROW_FIELD_EQ(tx_gas_limit_pi_offset, AVM_PUBLIC_INPUTS_GAS_SETTINGS_GAS_LIMITS_ROW_IDX), From b8f76f3f3cd18e089447d23e51c910dd6541ccf7 Mon Sep 17 00:00:00 2001 From: Rumata888 Date: Wed, 6 May 2026 15:43:41 +0000 Subject: [PATCH 11/15] chore: Update Security.md with bug bounty --- SECURITY.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 5dc0da97c908..1a10d07e7ece 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,9 +6,11 @@ We welcome external submissions in the meantime. To submit a vulnerability, plea ## Reporting Security Vulnerabilities -- **Do not** open public GitHub issues or pull requests for suspected security vulnerabilities. +Please use [Aztec Network Bug Bounty](https://cantina.xyz/bounties/80e74370-10d8-4e52-8e4b-7294deb7c9ee) to submit vulnerabilities. If the vulnerability is not in scope of the bug bounty program, please use the following procedure. -Instead, please use the [Private Vulnerability Reporting](https://github.com/AztecProtocol/aztec-packages/security/advisories/new) process on GitHub. +**Do not** open public GitHub issues or pull requests for suspected security vulnerabilities. + +Instead, please use the [Private Vulnerability Reporting](https://github.com/AztecProtocol/aztec-packages/security/advisories/new) process on GitHub. - Navigate to the "Security" tab of this repository. - Click "Report a vulnerability" on the left sidebar. From 42e2e672d20f2c1f66d06ef13d88040ab7123037 Mon Sep 17 00:00:00 2001 From: federicobarbacovi <171914500+federicobarbacovi@users.noreply.github.com> Date: Wed, 6 May 2026 16:54:17 +0100 Subject: [PATCH 12/15] feat: Improve add_scaled_batch (#22981) Two improvements: 1. `add_scaled_batch` was iterating over all polys to be batched and processing indices based on the range of the destination poly (the biggest of the polys to be batched). This PR adds a skipping condition that speeds up the function: we only iterate over the poly to be batched 2. Write a bespoke `add_batch_scaled` for use in the AVM with dynamic allocation of threads: each thread picks up the new available poly and works with it. This makes optimal usage of the fact that many polys in the AVM are small. Link to AVM bulk test: http://ci.aztec-labs.com/1df80aa9b6ae0088. The PCS component is `446ms` down from ~`600ms` --- .../barretenberg/polynomials/polynomial.cpp | 18 +++-- .../barretenberg/vm2/constraining/prover.cpp | 81 +++++++++++-------- 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp index edb44a863612..a22ff4875daa 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp @@ -308,15 +308,23 @@ void add_scaled_batch(Polynomial& dst, parallel_for([&](const ThreadChunk& chunk) { BB_BENCH_TRACY_NAME("add_scaled_batch/chunk"); auto chunk_indices = chunk.range(union_size, min_start); + if (chunk_indices.empty()) { + return; + } + auto chunk_start = chunk_indices.front(); + auto chunk_end = chunk_indices.back(); + for (size_t k = 0; k < sources.size(); ++k) { const auto& src = sources[k]; - const Fr c = scalars[k]; + const Fr& c = scalars[k]; const size_t src_start = src.start_index; const size_t src_end = src.end_index(); - for (size_t i : chunk_indices) { - if (i >= src_start && i < src_end) { - dst.at(i) += c * src[i]; - } + + const size_t idx_start = std::max(chunk_start, src_start); + const size_t idx_end = std::min(chunk_end + 1, src_end); + + for (size_t i = idx_start; i < idx_end; ++i) { + dst.at(i) += c * src[i]; } } }); diff --git a/barretenberg/cpp/src/barretenberg/vm2/constraining/prover.cpp b/barretenberg/cpp/src/barretenberg/vm2/constraining/prover.cpp index e97708094a49..44eff905fc1a 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/constraining/prover.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/constraining/prover.cpp @@ -216,27 +216,49 @@ void AvmProver::execute_pcs_rounds() return static_cast(std::distance(polys.begin(), it)); }; + auto add_scaled_batched = + [](Polynomial& dst, const std::span& sources, const std::span& scalars, const size_t skip_idx) { + const size_t num_slots = bb::get_num_cpus(); + std::vector batched_polys(num_slots); + for (auto& poly : batched_polys) { + poly = Polynomial(dst.size(), dst.virtual_size(), dst.start_index()); + } + + // Chunks are consumed dynamically via an atomic counter: faster threads naturally pick up + // more chunks while the slot they write to stays fixed for the life of their outer task. + std::atomic next_poly(0); + + // Accumulate polynomials: each thread picks up the next available polynomial + parallel_for(num_slots, [&](size_t slot_id) { + while (true) { + const size_t poly_id = next_poly.fetch_add(1, std::memory_order_relaxed); + if (poly_id >= sources.size()) { + break; + } + if (poly_id == skip_idx) { + continue; + } + + const size_t start_idx = sources[poly_id].start_index(); + const size_t end_idx = sources[poly_id].end_index(); + for (size_t idx = start_idx; idx < end_idx; idx++) { + batched_polys[slot_id].at(idx) += scalars[poly_id] * sources[poly_id][idx]; + } + } + }); + + for (const auto& poly : batched_polys) { + dst += poly; + } + }; + // Batch to be shifted polys in their to_be_shifted form // Search for poly with largest end index to avoid allocating a zero polynomial of circuit size size_t max_idx = index_of_max_end_index(shifted_polys); Polynomial batched_shifted = std::move(shifted_polys[max_idx]); batched_shifted *= shifted_challenges[max_idx]; - { - // Fuse the remaining add_scaled dispatches into a single parallel_for to amortise startup cost. - std::vector> sources; - std::vector scalars; - sources.reserve(shifted_polys.size()); - scalars.reserve(shifted_polys.size()); - for (size_t idx = 0; idx < shifted_polys.size(); ++idx) { - if (idx != max_idx) { - sources.emplace_back(shifted_polys[idx]); - scalars.push_back(shifted_challenges[idx]); - } - } - add_scaled_batch( - batched_shifted, std::span>(sources), std::span(scalars)); - } + add_scaled_batched(batched_shifted, shifted_polys, shifted_challenges, max_idx); // Batch unshifted polys (to avoid allocating a zero polynomial of circuit size, we initialize the batched // polynomial with the polynomial of the largest size) @@ -245,25 +267,15 @@ void AvmProver::execute_pcs_rounds() Polynomial batched_unshifted = std::move(unshifted_polys[max_idx]); batched_unshifted *= unshifted_challenges[max_idx]; batched_unshifted += batched_shifted; - { - // Only operate in the range of not to be shifted polys, as the contribution for those has already been added. - std::vector> sources; - std::vector scalars; - sources.reserve(unshifted_polys.size()); - scalars.reserve(unshifted_polys.size()); - for (size_t idx = 0; idx < unshifted_polys.size(); ++idx) { - if (idx >= WIRES_TO_BE_SHIFTED_START_IDX && idx < WIRES_TO_BE_SHIFTED_END_IDX) { - continue; - } - if (idx == max_idx) { - continue; - } - sources.emplace_back(unshifted_polys[idx]); - scalars.push_back(unshifted_challenges[idx]); - } - add_scaled_batch( - batched_unshifted, std::span>(sources), std::span(scalars)); - } + add_scaled_batched(batched_unshifted, + unshifted_polys.subspan(0, WIRES_TO_BE_SHIFTED_START_IDX), + unshifted_challenges.subspan(0, WIRES_TO_BE_SHIFTED_START_IDX), + max_idx); + add_scaled_batched(batched_unshifted, + unshifted_polys.subspan(WIRES_TO_BE_SHIFTED_END_IDX), + unshifted_challenges.subspan(WIRES_TO_BE_SHIFTED_END_IDX), + max_idx > WIRES_TO_BE_SHIFTED_END_IDX ? max_idx - WIRES_TO_BE_SHIFTED_END_IDX + : unshifted_polys.size()); const size_t circuit_dyadic_size = numeric::round_up_power_2(batched_unshifted.end_index()); @@ -308,5 +320,4 @@ HonkProof AvmProver::construct_proof() return export_proof(); } - } // namespace bb::avm2 From d06c1415d29ea14df64b8f509cc350f8833faf79 Mon Sep 17 00:00:00 2001 From: AztecBot <49558828+AztecBot@users.noreply.github.com> Date: Wed, 6 May 2026 16:31:28 +0000 Subject: [PATCH 13/15] chore: Update Noir to nightly-2026-05-05 Automated update of Noir submodule to latest nightly. **Current**: unknown **New**: nightly-2026-05-05 [View changes in noir-lang/noir](https://github.com/noir-lang/noir/compare/20391fdcb15a8ec9086a4ad69c15b33d72ce5ea8...nightly-2026-05-05) --- noir/noir-repo | 2 +- yarn-project/end-to-end/src/e2e_avm_simulator.test.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/noir/noir-repo b/noir/noir-repo index 20391fdcb15a..d52888d57c7c 160000 --- a/noir/noir-repo +++ b/noir/noir-repo @@ -1 +1 @@ -Subproject commit 20391fdcb15a8ec9086a4ad69c15b33d72ce5ea8 +Subproject commit d52888d57c7ce960d81b4ba1f996234211f1ba24 diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index 58e7980edcea..05eef0fdf403 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -45,9 +45,9 @@ describe('e2e_avm_simulator', () => { describe('Assertions & error enriching', () => { /** * Expect an error like: - * Assertion failed: This assertion should fail! 'not_true == true' + * Assertion failed: This assertion should fail! 'assert(not_true == true, "This assertion should fail!")' * ... - * at not_true == true (../../../../../../../home/aztec-dev/aztec-packages/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr:223:16) + * at assert(not_true == true, "This assertion should fail!") (../../../../../../../home/aztec-dev/aztec-packages/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr:223:5) * at inner_helper_with_failed_assertion() (../../../../../../../home/aztec-dev/aztec-packages/noir-projects/noir-contracts/contracts/test/avm_test_contract/src/main.nr:228:9) * at quote { $self } (../std/meta/expr.nr:269:9) * at function.name(); @@ -60,7 +60,9 @@ describe('e2e_avm_simulator', () => { avmContract.methods.assertion_failure().simulate({ from: defaultAccountAddress }), ).rejects.toThrow( expect.objectContaining({ - message: expect.stringMatching(/Assertion failed: This assertion should fail! 'not_true == true'/), + message: expect.stringMatching( + /Assertion failed: This assertion should fail! 'assert\(not_true == true, "This assertion should fail!"\)'/, + ), stack: expect.stringMatching(/at inner_helper_with_failed_assertion[\s\S]*at AvmTest\..*/), }), ); From 1f9d87ba3ab320ce1910cac6006ab9749c20b613 Mon Sep 17 00:00:00 2001 From: Aztec Bot <49558828+AztecBot@users.noreply.github.com> Date: Wed, 6 May 2026 16:54:25 -0400 Subject: [PATCH 14/15] fix(bb): allocate gemini masking poly with virtual size == dyadic (#22937) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Nightly debug build was failing again because `OinkProver::commit_to_masking_poly` allocates `gemini_masking_poly` with size `max_end_index()`. When that value is odd, sumcheck's pairwise iteration over `(edge_idx, edge_idx + 1)` reads one element past the polynomial — `compute_effective_round_size` itself rounds the iteration bound up to even, but the masking polynomial wasn't padded to match. In release builds this is silent UB; in debug, the bounds-checked accessor inside `Polynomial::operator[]` trips and the build fails very early (~2 minutes wall-clock, consistent with the failing job duration). Fix: round the masking polynomial's allocation up to even at construction so the layout matches what sumcheck assumes. The change only ever enlarges the polynomial by at most one element and never affects non-ZK flavors (gated by `flavor_has_gemini_masking()`). ## Recurrence This is the **fifth** independent claudebox session converging on this exact patch over the past four days; the prior four (`claudebox/fix-bb-debug-nightly` 2026-05-01, `claudebox/fix-nightly-bb-debug-build` 2026-05-03, PR #22918 2026-05-04, plus an earlier branch) were not merged, so the bug recurred each night. The diff here is byte-for-byte identical to the closed PR #22918. Most recent visible failed run: https://github.com/AztecProtocol/aztec-packages/actions/runs/25303760883 ## Verification - `cmake --preset debug` configures cleanly. - `ninja ultra_honk_tests` builds cleanly in the `debug` preset. - `./bin/ultra_honk_tests --gtest_filter='*Gemini*:*ZK*:*Mask*'` — **28 passed, 6 skipped** (the 6 skips are flavor-gated, not regressions). Detailed analysis: https://gist.github.com/AztecBot/97de8e254a5df2dad90f895be7d28f08 ClaudeBox log: https://claudebox.work/s/bd19299558fa14bc?run=1 ClaudeBox log: https://claudebox.work/s/bd19299558fa14bc?run=1 --------- Co-authored-by: sergei iakovenko <105737703+iakovenkos@users.noreply.github.com> Co-authored-by: iakovenkos --- .../cpp/src/barretenberg/ultra_honk/oink_prover.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp index 32494b09caea..d2157c11fe73 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp @@ -186,9 +186,10 @@ template void OinkProver::commit_to_z_perm() template void OinkProver::commit_to_masking_poly() { if constexpr (flavor_has_gemini_masking()) { - // Gemini masking poly only needs to cover the actual polynomial extent, not full dyadic size - const size_t polynomial_size = prover_instance->polynomials.max_end_index(); - prover_instance->polynomials.gemini_masking_poly = Polynomial::random(polynomial_size); + // virtual_size = dyadic_size matches every other witness poly, so sumcheck's pairwise read + // past end_index lands in the virtual-zero region. + prover_instance->polynomials.gemini_masking_poly = Polynomial::random( + prover_instance->polynomials.max_end_index(), prover_instance->dyadic_size(), /*start_index=*/0); // Commit to the masking polynomial and send to transcript auto masking_commitment = commitment_key.commit(prover_instance->polynomials.gemini_masking_poly); From 65cc32634cc82af5943f4b0ddde0c76aff6d6c9b Mon Sep 17 00:00:00 2001 From: ledwards2225 <98505400+ledwards2225@users.noreply.github.com> Date: Wed, 6 May 2026 20:08:56 -0700 Subject: [PATCH 15/15] docs(barretenberg): document Prover.toml fixture regen for proof-length changes (#23014) Adds a "Prover.toml Fixtures" subsection to `barretenberg/cpp/CLAUDE.md` documenting the `AZTEC_GENERATE_TEST_DATA=1 FAKE_PROOFS=1 yarn workspace @aztec/end-to-end test full.test` regen step that proof-length changes (e.g. `CHONK_PROOF_LENGTH` bumps) require. Without it, `nargo execute` fails type-checking on every protocol circuit using `ChonkProofData`. --- barretenberg/cpp/CLAUDE.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/barretenberg/cpp/CLAUDE.md b/barretenberg/cpp/CLAUDE.md index b2b1dfeb8f8c..b2fb9b3aadac 100644 --- a/barretenberg/cpp/CLAUDE.md +++ b/barretenberg/cpp/CLAUDE.md @@ -108,6 +108,27 @@ Key constants to watch: If C++ static_asserts fail after your changes, update both the assert values AND the corresponding Noir constants, then run `yarn remake-constants`. +## Prover.toml Fixtures + +Proof-length-affecting changes (e.g. `CHONK_PROOF_LENGTH` bumps from MegaFlavor entity additions) make the committed `Prover.toml` fixtures stale. `nargo execute --program-dir ` then fails with `Type Array { length: N, typ: Field } is expected to have length N but value Vec(...)`. + +Regenerate via the e2e prover full test with fake proofs: + +```bash +cd yarn-project +AZTEC_GENERATE_TEST_DATA=1 FAKE_PROOFS=1 yarn workspace @aztec/end-to-end test full.test +``` + +`FAKE_PROOFS=1` skips real proving — runs in ~2 min (orchestrator + witness generation only). Writes 12 `Prover.toml` files under `noir-projects/noir-protocol-circuits/crates//Prover.toml`. + +For circuits not exercised by `full.test.ts` (`rollup-tx-merge`, `rollup-block-root`, `rollup-block-root-single-tx`, `rollup-block-merge`, `rollup-checkpoint-root`, `rollup-block-root-first-empty-tx`), additionally run: + +```bash +AZTEC_GENERATE_TEST_DATA=1 yarn workspace @aztec/prover-client test orchestrator_single_checkpoint +``` + +Verify with `nargo execute --program-dir noir-projects/noir-protocol-circuits/crates/` for any previously-failing crate; should print `Circuit witness successfully solved`. + ## Verification Keys **IMPORTANT**: When making barretenberg changes that could affect verification keys, you must verify that VKs haven't changed unexpectedly, or