diff --git a/yarn-project/circuits.js/src/structs/constants.ts b/yarn-project/circuits.js/src/structs/constants.ts index d2a7673f15d..77f96f06313 100644 --- a/yarn-project/circuits.js/src/structs/constants.ts +++ b/yarn-project/circuits.js/src/structs/constants.ts @@ -31,6 +31,7 @@ export const PUBLIC_DATA_TREE_HEIGHT = 254; export const NULLIFIER_TREE_HEIGHT = 8; export const L1_TO_L2_MESSAGES_TREE_HEIGHT = 8; export const L1_TO_L2_MESSAGES_ROOTS_TREE_HEIGHT = 8; +export const L1_TO_L2_MESSAGES_SUBTREE_INSERTION_HEIGHT = 4; export const PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT = 8; export const CONTRACT_TREE_ROOTS_TREE_HEIGHT = 8; diff --git a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts index a04555dfddc..06cdcbe4c71 100644 --- a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts +++ b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts @@ -5,6 +5,7 @@ import { AppendOnlyTreeSnapshot } from './append_only_tree_snapshot.js'; import { CONTRACT_TREE_ROOTS_TREE_HEIGHT, L1_TO_L2_MESSAGES_ROOTS_TREE_HEIGHT, + L1_TO_L2_MESSAGES_SUBTREE_INSERTION_HEIGHT, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT, } from '../constants.js'; @@ -17,27 +18,22 @@ export class RootRollupInputs { public newHistoricPrivateDataTreeRootSiblingPath: Fr[], public newHistoricContractDataTreeRootSiblingPath: Fr[], + public newL1ToL2Messages: Fr[], public newL1ToL2MessageTreeRootSiblingPath: Fr[], public newHistoricL1ToL2MessageTreeRootSiblingPath: Fr[], - public newL1ToL2Messages: Fr[], + public startL1ToL2MessageTreeSnapshot: AppendOnlyTreeSnapshot, + public startHistoricTreeL1ToL2MessageTreeRootsSnapshot: AppendOnlyTreeSnapshot, ) { assertLength(this, 'newHistoricPrivateDataTreeRootSiblingPath', PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT); assertLength(this, 'newHistoricContractDataTreeRootSiblingPath', CONTRACT_TREE_ROOTS_TREE_HEIGHT); // TODO: the height of this could be wrong - assertLength(this, 'newL1ToL2MessageTreeRootSiblingPath', L1_TO_L2_MESSAGES_ROOTS_TREE_HEIGHT); + assertLength(this, 'newL1ToL2MessageTreeRootSiblingPath', L1_TO_L2_MESSAGES_SUBTREE_INSERTION_HEIGHT); assertLength(this, 'newHistoricL1ToL2MessageTreeRootSiblingPath', L1_TO_L2_MESSAGES_ROOTS_TREE_HEIGHT); assertLength(this, 'newL1ToL2Messages', NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); } toBuffer() { - return serializeToBuffer( - this.previousRollupData, - this.newHistoricPrivateDataTreeRootSiblingPath, - this.newHistoricContractDataTreeRootSiblingPath, - this.newL1ToL2MessageTreeRootSiblingPath, - this.newHistoricL1ToL2MessageTreeRootSiblingPath, - this.newL1ToL2Messages, - ); + return serializeToBuffer(...RootRollupInputs.getFields(this)); } static from(fields: FieldsOf): RootRollupInputs { @@ -49,9 +45,11 @@ export class RootRollupInputs { fields.previousRollupData, fields.newHistoricPrivateDataTreeRootSiblingPath, fields.newHistoricContractDataTreeRootSiblingPath, + fields.newL1ToL2Messages, fields.newL1ToL2MessageTreeRootSiblingPath, fields.newHistoricL1ToL2MessageTreeRootSiblingPath, - fields.newL1ToL2Messages, + fields.startL1ToL2MessageTreeSnapshot, + fields.startHistoricTreeL1ToL2MessageTreeRootsSnapshot, ] as const; } } diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 70c75648c9c..f0dd11ebe60 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -47,6 +47,7 @@ import { KERNEL_PUBLIC_CALL_STACK_LENGTH, L1_MSG_STACK_LENGTH, L1_TO_L2_MESSAGES_ROOTS_TREE_HEIGHT, + L1_TO_L2_MESSAGES_SUBTREE_INSERTION_HEIGHT, L1_TO_L2_MESSAGES_TREE_HEIGHT, NEW_COMMITMENTS_LENGTH, NEW_NULLIFIERS_LENGTH, @@ -396,9 +397,11 @@ export function makeRootRollupInputs(seed = 0) { [makePreviousBaseRollupData(seed), makePreviousBaseRollupData(seed + 0x1000)], range(PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT, 0x2000).map(fr), range(CONTRACT_TREE_ROOTS_TREE_HEIGHT, 0x2100).map(fr), - range(L1_TO_L2_MESSAGES_TREE_HEIGHT, 0x2100).map(fr), - range(L1_TO_L2_MESSAGES_ROOTS_TREE_HEIGHT, 0x2100).map(fr), range(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, 0x2100).map(fr), + range(L1_TO_L2_MESSAGES_SUBTREE_INSERTION_HEIGHT, 0x2100).map(fr), + range(L1_TO_L2_MESSAGES_ROOTS_TREE_HEIGHT, 0x2100).map(fr), + makeAppendOnlyTreeSnapshot(seed + 0x2200), + makeAppendOnlyTreeSnapshot(seed + 0x2300), ); } diff --git a/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.test.ts b/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.test.ts index 079ca3f875b..c5eaacd1fd4 100644 --- a/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.test.ts @@ -66,7 +66,7 @@ describe('sequencer/circuit_block_builder', () => { prover = mock(); builder = new TestSubject(builderDb, vks, simulator, prover); - mockL1ToL2Messages = Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(0); + mockL1ToL2Messages = Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)); // Populate root trees with first roots from the empty trees // TODO: Should this be responsibility of the MerkleTreeDb init? @@ -90,6 +90,7 @@ describe('sequencer/circuit_block_builder', () => { for (const [newTree, rootTree] of [ [MerkleTreeId.PRIVATE_DATA_TREE, MerkleTreeId.PRIVATE_DATA_TREE_ROOTS_TREE], [MerkleTreeId.CONTRACT_TREE, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE], + [MerkleTreeId.L1_TO_L2_MESSAGES_TREE, MerkleTreeId.L1_TO_L2_MESSAGES_ROOTS_TREE], ] as const) { const newTreeInfo = await expectsDb.getTreeInfo(newTree); await expectsDb.appendLeaves(rootTree, [newTreeInfo.root]); @@ -111,6 +112,12 @@ describe('sequencer/circuit_block_builder', () => { } }; + // TODO: could just inline this? + const updateL1ToL2MessagesTree = async (l1ToL2Messages: Fr[]) => { + const asBuffer = l1ToL2Messages.map(m => m.toBuffer()); + await expectsDb.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGES_TREE, asBuffer); + }; + const getTreeSnapshot = async (tree: MerkleTreeId) => { const treeInfo = await expectsDb.getTreeInfo(tree); return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size)); @@ -158,7 +165,9 @@ describe('sequencer/circuit_block_builder', () => { baseRollupOutputRight.endPrivateDataTreeSnapshot = await getTreeSnapshot(MerkleTreeId.PRIVATE_DATA_TREE); baseRollupOutputRight.endPublicDataTreeSnapshot = await getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE); + // Update l1 to l2 data tree // And update the root trees now to create proper output to the root rollup circuit + await updateL1ToL2MessagesTree(mockL1ToL2Messages); await updateRootTrees(); rootRollupOutput.endContractTreeSnapshot = await getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); rootRollupOutput.endNullifierTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); @@ -175,6 +184,10 @@ describe('sequencer/circuit_block_builder', () => { MerkleTreeId.L1_TO_L2_MESSAGES_ROOTS_TREE, ); + console.log('rro inside'); + console.log(rootRollupOutput); + console.log(rootRollupOutput.endTreeOfHistoricL1ToL2MessageTreeRootsSnapshot.root.toBuffer().toString('hex')); + // Actually build a block! const txs = [tx, await makeEmptyProcessedTx(), await makeEmptyProcessedTx(), await makeEmptyProcessedTx()]; const [l2Block, proof] = await builder.buildL2Block(blockNumber, txs, mockL1ToL2Messages); @@ -279,7 +292,7 @@ describe('sequencer/circuit_block_builder', () => { const [l2Block] = await builder.buildL2Block(blockNumber, txs, mockL1ToL2Messages); expect(l2Block.number).toEqual(blockNumber); - }); + }, 20000); }); }); diff --git a/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.ts b/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.ts index 403db0c0f89..8740b7bc17e 100644 --- a/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.ts +++ b/yarn-project/sequencer-client/src/block_builder/circuit_block_builder.ts @@ -38,6 +38,17 @@ import { RollupSimulator } from '../simulator/index.js'; import { ProcessedTx } from '../sequencer/processed_tx.js'; import { BlockBuilder } from './index.js'; +// TODO: where to put this type +type AllowedTreeNames = + T extends RootRollupPublicInputs + ? 'PrivateData' | 'Contract' | 'Nullifier' | 'L1ToL2Message' + : 'PrivateData' | 'Contract' | 'Nullifier'; + +// TODO: triple check and clean this type casting +type OutputWithTreeSnapshot = { + [K in `end${AllowedTreeNames}TreeSnapshot`]: AppendOnlyTreeSnapshot; +}; + const frToBigInt = (fr: Fr) => toBigIntBE(fr.toBuffer()); const bigintToFr = (num: bigint) => new Fr(num); const bigintToNum = (num: bigint) => Number(num); @@ -249,14 +260,30 @@ export class CircuitBlockBuilder implements BlockBuilder { this.debug(`Running root rollup circuit`); const rootInput = await this.getRootRollupInput(...left, ...right, newL1ToL2Messages); + // Update the local trees to include the new l1 to l2 messages + // TODO: + await this.db.appendLeaves( + MerkleTreeId.L1_TO_L2_MESSAGES_TREE, + newL1ToL2Messages.map(m => m.toBuffer()), + ); + + console.log('root rollup inputs'); + console.log(rootInput); + // Simulate and get proof for the root circuit const rootOutput = await this.simulator.rootRollupCircuit(rootInput); + + console.log('root output'); + console.log(rootOutput); + const rootProof = await this.prover.getRootRollupProof(rootInput, rootOutput); // Update the root trees with the latest data and contract tree roots, // and validate them against the output of the root circuit simulation this.debug(`Updating and validating root trees`); await this.updateRootTrees(); + console.log('root output in the sim'); + console.log(rootOutput); await this.validateRootOutput(rootOutput); return [rootOutput, rootProof]; @@ -267,6 +294,7 @@ export class CircuitBlockBuilder implements BlockBuilder { for (const [newTree, rootTree] of [ [MerkleTreeId.PRIVATE_DATA_TREE, MerkleTreeId.PRIVATE_DATA_TREE_ROOTS_TREE], [MerkleTreeId.CONTRACT_TREE, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE], + [MerkleTreeId.L1_TO_L2_MESSAGES_TREE, MerkleTreeId.L1_TO_L2_MESSAGES_ROOTS_TREE], ] as const) { const newTreeInfo = await this.db.getTreeInfo(newTree); await this.db.appendLeaves(rootTree, [newTreeInfo.root]); @@ -288,6 +316,8 @@ export class CircuitBlockBuilder implements BlockBuilder { this.validateTrees(rootOutput), this.validateRootTree(rootOutput, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE, 'Contract'), this.validateRootTree(rootOutput, MerkleTreeId.PRIVATE_DATA_TREE_ROOTS_TREE, 'PrivateData'), + this.validateRootTree(rootOutput, MerkleTreeId.L1_TO_L2_MESSAGES_ROOTS_TREE, 'L1ToL2Message'), + this.validateTree(rootOutput, MerkleTreeId.L1_TO_L2_MESSAGES_TREE, 'L1ToL2Message'), ]); } @@ -295,7 +325,7 @@ export class CircuitBlockBuilder implements BlockBuilder { protected async validateRootTree( rootOutput: RootRollupPublicInputs, treeId: MerkleTreeId, - name: 'Contract' | 'PrivateData', + name: 'Contract' | 'PrivateData' | 'L1ToL2Message', ) { const localTree = await this.getTreeSnapshot(treeId); const simulatedTree = rootOutput[`endTreeOfHistoric${name}TreeRootsSnapshot`]; @@ -303,13 +333,17 @@ export class CircuitBlockBuilder implements BlockBuilder { } // Helper for validating a non-roots tree against a circuit simulation output - protected async validateTree( - output: BaseOrMergeRollupPublicInputs | RootRollupPublicInputs, + protected async validateTree( + output: T, treeId: MerkleTreeId, - name: 'PrivateData' | 'Contract' | 'Nullifier', + name: AllowedTreeNames, ) { + if ('endL1ToL2MessageTreeSnapshot' in output && !(output instanceof RootRollupPublicInputs)) { + throw new Error(`The name 'L1ToL2Message' can only be used when output is of type RootRollupPublicInputs`); + } + const localTree = await this.getTreeSnapshot(treeId); - const simulatedTree = output[`end${name}TreeSnapshot`]; + const simulatedTree = (output as OutputWithTreeSnapshot)[`end${name}TreeSnapshot`]; this.validateSimulatedTree(localTree, simulatedTree, name); } @@ -321,6 +355,8 @@ export class CircuitBlockBuilder implements BlockBuilder { label?: string, ) { if (!simulatedTree.root.toBuffer().equals(localTree.root.toBuffer())) { + console.log('local: ', localTree); + console.log('sim: ', simulatedTree); throw new Error(`${label ?? name} tree root mismatch (local ${localTree.root}, simulated ${simulatedTree.root})`); } if (simulatedTree.nextAvailableLeafIndex !== localTree.nextAvailableLeafIndex) { @@ -364,8 +400,20 @@ export class CircuitBlockBuilder implements BlockBuilder { MerkleTreeId.L1_TO_L2_MESSAGES_ROOTS_TREE, ); - // TODO: not sure if this is the correct way to fetch this - const newL1ToL2MessageTreeRootSiblingPath = await getRootTreeSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGES_TREE); + // TODO: UPDATE 4 to be a constant like in base rollups! + const newL1ToL2MessageTreeRootSiblingPath = await this.getSubtreeSiblingPath( + MerkleTreeId.L1_TO_L2_MESSAGES_TREE, + 4, + ); + + // Get tree snapshots + const startL1ToL2MessageTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGES_TREE); + + console.log('startL1ToL2MessageTreeSnapshot'); + console.log(startL1ToL2MessageTreeSnapshot); + const startHistoricTreeL1ToL2MessageTreeRootsSnapshot = await this.getTreeSnapshot( + MerkleTreeId.L1_TO_L2_MESSAGES_ROOTS_TREE, + ); return RootRollupInputs.from({ previousRollupData, @@ -374,6 +422,8 @@ export class CircuitBlockBuilder implements BlockBuilder { newL1ToL2Messages, newHistoricL1ToL2MessageTreeRootSiblingPath, newL1ToL2MessageTreeRootSiblingPath, + startL1ToL2MessageTreeSnapshot, + startHistoricTreeL1ToL2MessageTreeRootsSnapshot, }); } diff --git a/yarn-project/sequencer-client/src/block_builder/standalone_block_builder.ts b/yarn-project/sequencer-client/src/block_builder/standalone_block_builder.ts index d97a38e9368..dfbe7220c61 100644 --- a/yarn-project/sequencer-client/src/block_builder/standalone_block_builder.ts +++ b/yarn-project/sequencer-client/src/block_builder/standalone_block_builder.ts @@ -49,6 +49,7 @@ export class StandaloneBlockBuilder implements BlockBuilder { for (const tx of txs) { await this.updateTrees(tx); } + await this.updateL1ToL2MessagesTree(newL1ToL2Messages); await this.updateRootTrees(); @@ -104,6 +105,7 @@ export class StandaloneBlockBuilder implements BlockBuilder { computeContractLeaf(wasm, x).toBuffer(), ); + // TODO: why do these need a loop? come back to for (let i = 0; i < KERNEL_NEW_COMMITMENTS_LENGTH; i++) { await this.db.appendLeaves(MerkleTreeId.PRIVATE_DATA_TREE, [dataTreeLeaves[i]]); } @@ -115,10 +117,17 @@ export class StandaloneBlockBuilder implements BlockBuilder { } } + private async updateL1ToL2MessagesTree(l1ToL2Messages: Fr[]) { + const leaves = l1ToL2Messages.map((x: Fr) => x.toBuffer()); + await this.db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGES_TREE, leaves); + } + private async updateRootTrees() { const newDataTreeInfo = await this.getTreeSnapshot(MerkleTreeId.PRIVATE_DATA_TREE); const newContractsTreeInfo = await this.getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); + const newL1ToL2MessagesTreeInfo = await this.getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGES_TREE); await this.db.appendLeaves(MerkleTreeId.CONTRACT_TREE_ROOTS_TREE, [newContractsTreeInfo.root.toBuffer()]); await this.db.appendLeaves(MerkleTreeId.PRIVATE_DATA_TREE_ROOTS_TREE, [newDataTreeInfo.root.toBuffer()]); + await this.db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGES_ROOTS_TREE, [newL1ToL2MessagesTreeInfo.root.toBuffer()]); } } diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index f0e3c8cd53e..5010e32f162 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -1,4 +1,4 @@ -import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, makeEmptyProof } from '@aztec/circuits.js'; +import { Fr, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, makeEmptyProof } from '@aztec/circuits.js'; import { P2P, P2PClientState } from '@aztec/p2p'; import { L2Block, PrivateTx, Tx, UnverifiedData } from '@aztec/types'; import { MerkleTreeId, MerkleTreeOperations, WorldStateRunningState, WorldStateSynchroniser } from '@aztec/world-state'; @@ -65,7 +65,7 @@ describe('sequencer', () => { lastBlockNumber + 1, expectedTxHashes.map(hash => expect.objectContaining({ hash })), // TODO: longer term solution to this - Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(0), + Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); expect(publisher.processL2Block).toHaveBeenCalledWith(block); expect(publisher.processUnverifiedData).toHaveBeenCalledWith(lastBlockNumber + 1, expectedUnverifiedData); @@ -103,7 +103,7 @@ describe('sequencer', () => { lastBlockNumber + 1, expectedTxHashes.map(hash => expect.objectContaining({ hash })), // TODO: longer term solution to this - Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(0), + Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); expect(publisher.processL2Block).toHaveBeenCalledWith(block); expect(publisher.processUnverifiedData).toHaveBeenCalledWith(lastBlockNumber + 1, expectedUnverifiedData); diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index cafd8d03fd2..4a8b8105ad2 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -210,7 +210,7 @@ export class Sequencer { protected takeL1ToL2MessagesFromContract(): Fr[] { // TODO: use protective typing for the l1 to l2 messages array. - remove Fr import // TODO: dont make all zeros - return new Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(0); + return new Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)); } /** diff --git a/yarn-project/world-state/src/world-state-db/index.ts b/yarn-project/world-state/src/world-state-db/index.ts index a4a34034ff8..18851f59314 100644 --- a/yarn-project/world-state/src/world-state-db/index.ts +++ b/yarn-project/world-state/src/world-state-db/index.ts @@ -13,7 +13,7 @@ export enum MerkleTreeId { PRIVATE_DATA_TREE = 3, PRIVATE_DATA_TREE_ROOTS_TREE = 4, PUBLIC_DATA_TREE = 5, - L1_TO_L2_MESSAGES_TREE, + L1_TO_L2_MESSAGES_TREE = 6, L1_TO_L2_MESSAGES_ROOTS_TREE = 7, }