From 10eb0b5c5bae76ba3a581a4e7e72fadd5df883c5 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 28 Mar 2023 16:57:32 +0100 Subject: [PATCH 01/26] Update trees as we generate blocks --- .../circuits.js/src/structs/base_rollup.ts | 6 +- .../circuits.js/src/structs/shared.ts | 1 - yarn-project/sequencer-client/package.json | 2 + .../circuit_powered_block_builder.ts | 206 +++++++++++++++--- yarn-project/yarn.lock | 18 ++ 5 files changed, 194 insertions(+), 39 deletions(-) diff --git a/yarn-project/circuits.js/src/structs/base_rollup.ts b/yarn-project/circuits.js/src/structs/base_rollup.ts index 5642539fe2fe..ab7169502b6e 100644 --- a/yarn-project/circuits.js/src/structs/base_rollup.ts +++ b/yarn-project/circuits.js/src/structs/base_rollup.ts @@ -155,7 +155,7 @@ export class BaseRollupPublicInputs { public endPrivateDataTreeSnapshot: AppendOnlyTreeSnapshot, public startNullifierTreeSnapshot: AppendOnlyTreeSnapshot, - public endNullifierTreeSnapshots: AppendOnlyTreeSnapshot, + public endNullifierTreeSnapshot: AppendOnlyTreeSnapshot, public startContractTreeSnapshot: AppendOnlyTreeSnapshot, public endContractTreeSnapshot: AppendOnlyTreeSnapshot, @@ -196,7 +196,7 @@ export class BaseRollupPublicInputs { this.endPrivateDataTreeSnapshot, this.startNullifierTreeSnapshot, - this.endNullifierTreeSnapshots, + this.endNullifierTreeSnapshot, this.startContractTreeSnapshot, this.endContractTreeSnapshot, @@ -204,4 +204,4 @@ export class BaseRollupPublicInputs { this.calldataHash, ); } -} +} \ No newline at end of file diff --git a/yarn-project/circuits.js/src/structs/shared.ts b/yarn-project/circuits.js/src/structs/shared.ts index 93df6125969f..3fbe7ca2acb4 100644 --- a/yarn-project/circuits.js/src/structs/shared.ts +++ b/yarn-project/circuits.js/src/structs/shared.ts @@ -2,7 +2,6 @@ import { BufferReader } from '@aztec/foundation'; import { Fq, Fr } from '@aztec/foundation/fields'; import { assertLength, checkLength, range } from '../utils/jsUtils.js'; import { Bufferable, serializeToBuffer } from '../utils/serialize.js'; - export class MembershipWitness { constructor(pathSize: N, public leafIndex: UInt32, public siblingPath: Fr[]) { checkLength(this.siblingPath, pathSize, 'MembershipWitness.siblingPath'); diff --git a/yarn-project/sequencer-client/package.json b/yarn-project/sequencer-client/package.json index 9b6c3f0af8bd..dc708d767f8d 100644 --- a/yarn-project/sequencer-client/package.json +++ b/yarn-project/sequencer-client/package.json @@ -40,6 +40,7 @@ "@aztec/p2p": "workspace:^", "@aztec/tx": "workspace:^", "@aztec/world-state": "workspace:^", + "lodash.flatmap": "^4.5.0", "lodash.times": "^4.3.2", "tslib": "^2.4.0" }, @@ -47,6 +48,7 @@ "@jest/globals": "^29.4.3", "@rushstack/eslint-patch": "^1.1.4", "@types/jest": "^29.4.0", + "@types/lodash.flatmap": "^4.5.7", "@types/lodash.times": "^4.3.7", "@types/node": "^18.7.23", "concurrently": "^7.6.0", diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts index d9e93f297d4a..71bf3c72d9f9 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts @@ -6,6 +6,9 @@ import { ConstantBaseRollupData, CONTRACT_TREE_ROOTS_TREE_HEIGHT, Fr, + KERNEL_NEW_COMMITMENTS_LENGTH, + KERNEL_NEW_CONTRACTS_LENGTH, + KERNEL_NEW_NULLIFIERS_LENGTH, MembershipWitness, NullifierLeafPreimage, NULLIFIER_TREE_HEIGHT, @@ -14,10 +17,12 @@ import { PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT, RootRollupInputs, RootRollupPublicInputs, + UInt8Vector, VK_TREE_HEIGHT, } from '@aztec/circuits.js'; import { Tx } from '@aztec/tx'; -import { MerkleTreeId, MerkleTreeOperations } from '@aztec/world-state'; +import { MerkleTreeId, MerkleTreeDb } from '@aztec/world-state'; +import flatMap from 'lodash.flatmap'; import { makeEmptyTx } from '../deps/tx.js'; import { Proof, Prover } from '../prover/index.js'; import { Simulator } from '../simulator/index.js'; @@ -39,14 +44,14 @@ const TODO_ANY: any = {}; export class CircuitPoweredBlockBuilder { constructor( - private db: MerkleTreeOperations, - private nextRollupId: number, - private vks: VerificationKeys, - private simulator: Simulator, - private prover: Prover, + protected db: MerkleTreeDb, + protected nextRollupId: number, + protected vks: VerificationKeys, + protected simulator: Simulator, + protected prover: Prover, ) {} - public async buildL2Block(tx: Tx) { + public async buildL2Block(tx: Tx): Promise<[L2Block, UInt8Vector]> { const [ startPrivateDataTreeSnapshot, startNullifierTreeSnapshot, @@ -63,7 +68,9 @@ export class CircuitPoweredBlockBuilder { ].map(tree => this.getTreeSnapshot(tree)), ); - const [circuitsOutput] = await this.runCircuits(tx); + // We fill the tx batch with empty txs, we process only one tx at a time for now + const txs = [tx, makeEmptyTx(), makeEmptyTx(), makeEmptyTx()]; + const [circuitsOutput, proof] = await this.runCircuits(txs); const { endPrivateDataTreeSnapshot, @@ -73,6 +80,14 @@ export class CircuitPoweredBlockBuilder { endTreeOfHistoricContractTreeRootsSnapshot, } = circuitsOutput; + // Collect all new nullifiers, commitments, and contracts from all txs in this block + const newNullifiers = flatMap(txs, tx => tx.data.end.newNullifiers); + const newCommitments = flatMap(txs, tx => tx.data.end.newCommitments); + const newContracts = flatMap(txs, tx => tx.data.end.newContracts).map(c => c.functionTreeRoot); + const newContractData = flatMap(txs, tx => tx.data.end.newContracts).map( + n => new ContractData(n.contractAddress, n.portalContractAddress), + ); + const l2block = L2Block.fromFields({ number: this.nextRollupId, startPrivateDataTreeSnapshot, @@ -85,25 +100,30 @@ export class CircuitPoweredBlockBuilder { endTreeOfHistoricPrivateDataTreeRootsSnapshot, startTreeOfHistoricContractTreeRootsSnapshot, endTreeOfHistoricContractTreeRootsSnapshot, - newCommitments: tx.data.end.newCommitments, - newNullifiers: tx.data.end.newNullifiers, - newContracts: tx.data.end.newContracts.map(x => x.functionTreeRoot), - newContractData: tx.data.end.newContracts.map(n => new ContractData(n.contractAddress, n.portalContractAddress)), + newCommitments, + newNullifiers, + newContracts, + newContractData, }); - return l2block; + + return [l2block, proof]; } - private async getTreeSnapshot(id: MerkleTreeId): Promise { + protected async getTreeSnapshot(id: MerkleTreeId): Promise { const treeInfo = await this.db.getTreeInfo(id); return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size)); } - private async runCircuits(tx: Tx): Promise<[RootRollupPublicInputs, Proof]> { - const emptyTx = makeEmptyTx(); + protected async runCircuits(txs: Tx[]): Promise<[RootRollupPublicInputs, Proof]> { + const [tx1, tx2, tx3, tx4] = txs; - const [, baseRollupOutputLeft, baseRollupProofLeft] = await this.baseRollupCircuit(tx, emptyTx); + const [baseRollupInputLeft, baseRollupOutputLeft] = await this.baseRollupCircuit(tx1, tx2); + const [baseRollupInputRight, baseRollupOutputRight] = await this.baseRollupCircuit(tx3, tx4); - const [, baseRollupOutputRight, baseRollupProofRight] = await this.baseRollupCircuit(emptyTx, emptyTx); + const [baseRollupProofLeft, baseRollupProofRight] = await Promise.all([ + this.prover.getBaseRollupProof(baseRollupInputLeft, baseRollupOutputLeft), + this.prover.getBaseRollupProof(baseRollupInputRight, baseRollupOutputRight), + ]); const rootInput = await this.getRootRollupInput( baseRollupOutputLeft, @@ -111,20 +131,83 @@ export class CircuitPoweredBlockBuilder { baseRollupOutputRight, baseRollupProofRight, ); + const rootOutput = await this.simulator.rootRollupCircuit(rootInput); const rootProof = await this.prover.getRootRollupProof(rootInput, rootOutput); + await this.validateRootOutput(rootOutput); return [rootOutput, rootProof]; } - private async baseRollupCircuit(tx1: Tx, tx2: Tx) { - const rollupInput = await this.getBaseRollupInput(tx1, tx2); + protected async baseRollupCircuit(tx1: Tx, tx2: Tx) { + const rollupInput = await this.buildBaseRollupInput(tx1, tx2); const rollupOutput = await this.simulator.baseRollupCircuit(rollupInput); - const rollupProof = await this.prover.getBaseRollupProof(rollupInput, rollupOutput); - return [rollupInput, rollupOutput, rollupProof] as const; + await this.validateTrees(rollupOutput); + await this.updateRootTrees(); + return [rollupInput, rollupOutput] as const; } - private async getRootRollupInput( + // Updates our roots trees with the new generated trees after the rollup updates + protected async updateRootTrees() { + for (const [newTree, rootTree] of [ + [MerkleTreeId.DATA_TREE, MerkleTreeId.DATA_TREE_ROOTS_TREE], + [MerkleTreeId.CONTRACT_TREE, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE], + ] as const) { + const newTreeInfo = await this.db.getTreeInfo(newTree); + await this.db.appendLeaves(rootTree, [newTreeInfo.root]); + } + } + + // Validate that the new roots we calculated from manual insertions match the outputs of the simulation + protected async validateTrees(rollupOutput: BaseRollupPublicInputs | RootRollupPublicInputs) { + await Promise.all([ + this.validateTree(rollupOutput, MerkleTreeId.CONTRACT_TREE, 'Contract'), + this.validateTree(rollupOutput, MerkleTreeId.DATA_TREE, 'PrivateData'), + this.validateTree(rollupOutput, MerkleTreeId.NULLIFIER_TREE, 'Nullifier'), + ]); + } + + // Validate that the roots of all local trees match the output of the root circuit simulation + protected async validateRootOutput(rootOutput: RootRollupPublicInputs) { + await Promise.all([ + this.validateTrees(rootOutput), + this.validateRootTree(rootOutput, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE, 'Contract'), + this.validateRootTree(rootOutput, MerkleTreeId.DATA_TREE_ROOTS_TREE, 'PrivateData'), + ]); + } + + protected async validateRootTree( + rootOutput: RootRollupPublicInputs, + treeId: MerkleTreeId, + name: 'Contract' | 'PrivateData', + ) { + const localTree = await this.getTreeSnapshot(treeId); + const simulatedTree = rootOutput[`endTreeOfHistoric${name}TreeRootsSnapshot`]; + this.validateSimulatedTree(localTree, simulatedTree, name); + } + + protected async validateTree( + output: BaseRollupPublicInputs | RootRollupPublicInputs, + treeId: MerkleTreeId, + name: 'PrivateData' | 'Contract' | 'Nullifier', + ) { + const localTree = await this.getTreeSnapshot(treeId); + const simulatedTree = output[`end${name}TreeSnapshot`]; + this.validateSimulatedTree(localTree, simulatedTree, name); + } + + protected validateSimulatedTree(localTree: AppendOnlyTreeSnapshot, simulatedTree: AppendOnlyTreeSnapshot, name: string) { + if (!simulatedTree.root.toBuffer().equals(localTree.root.toBuffer())) { + throw new Error(`${name} tree root mismatch (local ${localTree.root}, simulated ${simulatedTree.root})`); + } + if (simulatedTree.nextAvailableLeafIndex !== localTree.nextAvailableLeafIndex) { + throw new Error( + `${name} tree next available leaf index mismatch (local ${localTree.nextAvailableLeafIndex}, simulated ${simulatedTree.nextAvailableLeafIndex})`, + ); + } + } + + protected async getRootRollupInput( rollupOutputLeft: BaseRollupPublicInputs, rollupProofLeft: Proof, rollupOutputRight: BaseRollupPublicInputs, @@ -145,7 +228,7 @@ export class CircuitPoweredBlockBuilder { ); } - private getPreviousRollupDataFromBaseRollup(rollupOutput: BaseRollupPublicInputs, rollupProof: Proof) { + protected getPreviousRollupDataFromBaseRollup(rollupOutput: BaseRollupPublicInputs, rollupProof: Proof) { return new PreviousRollupData( rollupOutput, rollupProof, @@ -157,7 +240,7 @@ export class CircuitPoweredBlockBuilder { ); } - private getKernelDataFor(tx: Tx) { + protected getKernelDataFor(tx: Tx) { return new PreviousKernelData( tx.data, tx.proof, @@ -172,7 +255,7 @@ export class CircuitPoweredBlockBuilder { } // Scan a tree searching for a specific value and return a membership witness proof for it - private async getMembershipWitnessFor( + protected async getMembershipWitnessFor( value: Fr, treeId: MerkleTreeId, height: N, @@ -188,7 +271,7 @@ export class CircuitPoweredBlockBuilder { ); } - private async getConstantBaseRollupData(): Promise { + protected async getConstantBaseRollupData(): Promise { return ConstantBaseRollupData.from({ baseRollupVkHash: DELETE_FR, mergeRollupVkHash: DELETE_FR, @@ -200,7 +283,7 @@ export class CircuitPoweredBlockBuilder { }); } - private async getLowNullifierInfo(nullifier: Fr) { + protected async getLowNullifierInfo(nullifier: Fr) { const tree = MerkleTreeId.NULLIFIER_TREE; const prevValueIndex = await this.db.getPreviousValueIndex(tree, frToBigInt(nullifier)); const prevValueInfo = this.db.getLeafData(tree, prevValueIndex.index); @@ -221,13 +304,44 @@ export class CircuitPoweredBlockBuilder { }; } - private async getBaseRollupInput(tx1: Tx, tx2: Tx) { - // Concatenate the new nullifiers of each tx being rolled up - // and get the previous node for each of them, along with the sibling path - const lowNullifierInfos = await Promise.all( - [...tx1.data.end.newNullifiers, ...tx2.data.end.newNullifiers].map(fr => this.getLowNullifierInfo(fr)), + protected async getSubtreeSiblingPath(treeId: MerkleTreeId, subtreeHeight: number): Promise { + // Get sibling path to the last leaf we inserted + const lastLeafIndex = (await this.db.getTreeInfo(treeId).then(t => t.size)) - 1n; + const fullSiblingPath = await this.db.getSiblingPath(treeId, lastLeafIndex); + + // Drop the first subtreeHeight items since we only care about the path to the subtree root + return fullSiblingPath.data.slice(subtreeHeight).map(b => Fr.fromBuffer(b)); + } + + protected async buildBaseRollupInput(tx1: Tx, tx2: Tx) { + // Get trees info before any changes hit + const constants = await this.getConstantBaseRollupData(); + const startNullifierTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); + const startContractTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); + const startPrivateDateTreeSnapshot = await this.getTreeSnapshot(MerkleTreeId.DATA_TREE); + + // Update the contract and data trees with the new items being inserted to get the new roots + // that will be used by the next iteration of the base rollup circuit + const newContracts = [...tx1.data.end.newContracts, ...tx2.data.end.newContracts]; + const newCommitments = [...tx1.data.end.newCommitments, ...tx2.data.end.newCommitments]; + await this.db.appendLeaves( + MerkleTreeId.CONTRACT_TREE, + newContracts.map(fr => fr.toBuffer()), ); + await this.db.appendLeaves( + MerkleTreeId.DATA_TREE, + newCommitments.map(fr => fr.toBuffer()), + ); + + // Update the nullifier tree, capturing the low nullifier info for each individual operation + const newNullifiers = [...tx1.data.end.newNullifiers, ...tx2.data.end.newNullifiers]; + const lowNullifierInfos = []; + for (const nullifier of newNullifiers) { + lowNullifierInfos.push(await this.getLowNullifierInfo(nullifier)); + await this.db.appendLeaves(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]); + } + // Calculates membership witness for a contract tree root in the contract tree roots tree const getContractMembershipWitnessFor = (tx: Tx) => this.getMembershipWitnessFor( tx.data.constants.oldTreeRoots.contractTreeRoot, @@ -235,6 +349,7 @@ export class CircuitPoweredBlockBuilder { CONTRACT_TREE_ROOTS_TREE_HEIGHT, ); + // Same but for data tree const getDataMembershipWitnessFor = (tx: Tx) => this.getMembershipWitnessFor( tx.data.constants.oldTreeRoots.privateDataTreeRoot, @@ -242,9 +357,30 @@ export class CircuitPoweredBlockBuilder { PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT, ); + // Calculate subtree heights as the log2 of the total number of new elements inserted by this circuit + const dataSubtreeSize = Math.log2(KERNEL_NEW_COMMITMENTS_LENGTH) + 1; + const contractSubtreeSize = Math.log2(KERNEL_NEW_CONTRACTS_LENGTH) + 1; + const nullifierSubtreeSize = Math.log2(KERNEL_NEW_NULLIFIERS_LENGTH) + 1; + + // Get the subtree sibling paths for the circuit + const newCommitmentsSubtreeSiblingPath = await this.getSubtreeSiblingPath(MerkleTreeId.DATA_TREE, dataSubtreeSize); + const newContractsSubtreeSiblingPath = await this.getSubtreeSiblingPath( + MerkleTreeId.CONTRACT_TREE, + contractSubtreeSize, + ); + const newNullifiersSubtreeSiblingPath = await this.getSubtreeSiblingPath( + MerkleTreeId.NULLIFIER_TREE, + nullifierSubtreeSize, + ); + return BaseRollupInputs.from({ - constants: await this.getConstantBaseRollupData(), - startNullifierTreeSnapshot: await this.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE), + constants, + startNullifierTreeSnapshot, + startContractTreeSnapshot, + startPrivateDateTreeSnapshot, + newCommitmentsSubtreeSiblingPath, + newContractsSubtreeSiblingPath, + newNullifiersSubtreeSiblingPath, lowNullifierLeafPreimages: lowNullifierInfos.map(i => i.leafPreimage), lowNullifierMembershipWitness: lowNullifierInfos.map(i => i.witness), kernelData: [this.getKernelDataFor(tx1), this.getKernelDataFor(tx2)], diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index dfc1d03123e0..912080c29b27 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -498,11 +498,13 @@ __metadata: "@jest/globals": ^29.4.3 "@rushstack/eslint-patch": ^1.1.4 "@types/jest": ^29.4.0 + "@types/lodash.flatmap": ^4.5.7 "@types/lodash.times": ^4.3.7 "@types/node": ^18.7.23 concurrently: ^7.6.0 jest: ^28.1.3 jest-mock-extended: ^3.0.3 + lodash.flatmap: ^4.5.0 lodash.times: ^4.3.2 ts-jest: ^28.0.7 ts-node: ^10.9.1 @@ -2277,6 +2279,15 @@ __metadata: languageName: node linkType: hard +"@types/lodash.flatmap@npm:^4.5.7": + version: 4.5.7 + resolution: "@types/lodash.flatmap@npm:4.5.7" + dependencies: + "@types/lodash": "*" + checksum: 2bcccdd30bcb06a400b37a2b731f194f6b11ae49467a98828d86e25dbd10055f8b033c7741755a41de7bc50f49e387ecec952f9871863f77c79582e891945486 + languageName: node + linkType: hard + "@types/lodash.times@npm:^4.3.7": version: 4.3.7 resolution: "@types/lodash.times@npm:4.3.7" @@ -6139,6 +6150,13 @@ __metadata: languageName: node linkType: hard +"lodash.flatmap@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.flatmap@npm:4.5.0" + checksum: c01a47d32e99f8fce75409f0a4a9bd12fbb2d3a46519a0dde14deedb1e527b5ddccc2bf997705c67bdecb915f47749e8a9ffefa7a91c41f0c448e06348ec81c7 + languageName: node + linkType: hard + "lodash.memoize@npm:4.x": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" From 27862b3408fac3a9b201b5363d7cf265b954fac2 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 28 Mar 2023 21:18:11 +0100 Subject: [PATCH 02/26] Block builder test in progress --- .../circuits.js/src/tests/factories.ts | 37 ++++++--- .../src/indexed_tree/indexed_tree.ts | 6 +- .../circuit_powered_block_builder.test.ts | 78 +++++++++++++++++++ .../circuit_powered_block_builder.ts | 6 +- 4 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index e31620a2a826..7394f999485c 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -1,5 +1,5 @@ -import { EthAddress, Fq, Fr, AztecAddress } from '@aztec/foundation'; -import { CallContext, PrivateCircuitPublicInputs } from '../index.js'; +import { AztecAddress, EthAddress, Fq, Fr } from '@aztec/foundation'; +import { CallContext, PrivateCircuitPublicInputs, RootRollupPublicInputs } from '../index.js'; import { AppendOnlyTreeSnapshot, BaseRollupPublicInputs, ConstantBaseRollupData } from '../structs/base_rollup.js'; import { ARGS_LENGTH, @@ -19,7 +19,7 @@ import { PRIVATE_CALL_STACK_LENGTH, PUBLIC_CALL_STACK_LENGTH, RETURN_VALUES_LENGTH, - VK_TREE_HEIGHT, + VK_TREE_HEIGHT } from '../structs/constants.js'; import { FunctionData } from '../structs/function_data.js'; import { @@ -31,16 +31,14 @@ import { PreviousKernelData, PrivateCallData, PrivateKernelInputs, - PrivateKernelPublicInputs, + PrivateKernelPublicInputs } from '../structs/kernel.js'; import { PrivateCallStackItem } from '../structs/private_call_stack_item.js'; import { AffineElement, AggregationObject, - ComposerType, - MembershipWitness, - UInt8Vector, - EcdsaSignature, + ComposerType, EcdsaSignature, MembershipWitness, + UInt8Vector } from '../structs/shared.js'; import { ContractDeploymentData, SignedTxRequest, TxContext, TxRequest } from '../structs/tx.js'; import { CommitmentMap, G1AffineElement, VerificationKey } from '../structs/verification_key.js'; @@ -246,10 +244,31 @@ export function makeBaseRollupPublicInputs(seed = 0) { makeAppendOnlyTreeSnapshot(seed + 0x600), makeAppendOnlyTreeSnapshot(seed + 0x700), makeAppendOnlyTreeSnapshot(seed + 0x800), - range(2, seed + 0x901).map(fr) as [Fr, Fr], + [fr(seed + 0x901), fr(seed + 0x902)], ); } +export function makeRootRollupPublicInputs(seed = 0) { + return RootRollupPublicInputs.from({ + startContractTreeSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x100), + startNullifierTreeSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x200), + startPrivateDataTreeSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x300), + startTreeOfHistoricContractTreeRootsSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x400), + startTreeOfHistoricPrivateDataTreeRootsSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x500), + endContractTreeSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x600), + endNullifierTreeSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x700), + endPrivateDataTreeSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x800), + endTreeOfHistoricContractTreeRootsSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x900), + endTreeOfHistoricPrivateDataTreeRootsSnapshot: makeAppendOnlyTreeSnapshot(seed + 0x1000), + endAggregationObject: makeAggregationObject(seed + 0x1100), + newCommitmentsHash: fr(seed + 0x1200), + newContractDataHash: fr(seed + 0x1200), + newL1MsgsHash: fr(seed + 0x1200), + newNullifiersHash: fr(seed + 0x1200), + proverContributionsHash: fr(seed + 0x1200), + }); +} + /** * Test only. Easy to identify big endian field serialize. * @param num - The number. diff --git a/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.ts b/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.ts index e1ea8b7a43e9..43c47381cc0c 100644 --- a/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.ts +++ b/yarn-project/merkle-tree/src/indexed_tree/indexed_tree.ts @@ -309,9 +309,9 @@ export class IndexedTree implements MerkleTree { : undefined; } - public async getLeafValue(index: bigint): Promise { + public getLeafValue(index: bigint): Promise { const leaf = this.getLatestLeafDataCopy(Number(index)); - if (!leaf) return undefined; - return toBufferBE(leaf.value, 32); + if (!leaf) return Promise.resolve(undefined); + return Promise.resolve(toBufferBE(leaf.value, 32)); } } diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts new file mode 100644 index 000000000000..57e29af420e9 --- /dev/null +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts @@ -0,0 +1,78 @@ +import { MerkleTreeDb, MerkleTreeId, MerkleTrees } from "@aztec/world-state"; +import { mock, MockProxy } from "jest-mock-extended"; +import { Prover } from "../prover/index.js"; +import { Simulator } from "../simulator/index.js"; +import { CircuitPoweredBlockBuilder } from "./circuit_powered_block_builder.js"; +import { VerificationKeys, getVerificationKeys } from "./vks.js"; +import { default as memdown } from 'memdown'; +import { default as levelup } from 'levelup'; +import { BaseRollupInputs, Fr, UInt8Vector } from "@aztec/circuits.js"; +import { Tx } from "@aztec/p2p"; +import { makeBaseRollupPublicInputs, makePrivateKernelPublicInputs, makeRootRollupPublicInputs } from "@aztec/circuits.js/factories"; + +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-ignore +export const createMemDown = () => memdown(); + +describe('sequencer/circuit_block_builder', () => { + let builder: TestSubject; + let db: MerkleTreeDb; + let blockNumber: number; + let vks: VerificationKeys; + let simulator: MockProxy; + let prover: MockProxy; + + const emptyProof = new UInt8Vector(Buffer.alloc(32, 0));; + + beforeEach(async () => { + blockNumber = 3; + db = await MerkleTrees.new(levelup(createMemDown())); + vks = getVerificationKeys(); + simulator = mock(); + prover = mock(); + builder = new TestSubject( + db, blockNumber, vks, simulator, prover + ); + + // Populate root trees with first roots + // TODO: Should MerkleTrees.init take care of this? + await builder.updateRootTrees(); + + prover.getBaseRollupProof.mockResolvedValue(emptyProof) + prover.getRootRollupProof.mockResolvedValue(emptyProof); + + simulator.baseRollupCircuit.mockResolvedValue(makeBaseRollupPublicInputs()); + simulator.rootRollupCircuit.mockResolvedValue(makeRootRollupPublicInputs()); + }); + + it('builds an L2 block', async () => { + // Assemble a fake transaction, we'll tweak some fields below + const tx = new Tx(makePrivateKernelPublicInputs(), emptyProof); + + // Set tree roots to proper values + for (const [name, id] of [ + ['privateDataTreeRoot', MerkleTreeId.DATA_TREE], + ['contractTreeRoot', MerkleTreeId.CONTRACT_TREE], + ['nullifierTreeRoot', MerkleTreeId.NULLIFIER_TREE], + ] as const) { + tx.data.constants.oldTreeRoots[name] = new Fr((await db.getTreeInfo(id)).root); + } + + // Actually build a block! + const [l2block, proof] = await builder.buildL2Block(tx); + + expect(l2block.number).toEqual(blockNumber); + expect(proof).toEqual(emptyProof); + }); +}); + +// Test subject class that exposes internal functions for testing +class TestSubject extends CircuitPoweredBlockBuilder { + public buildBaseRollupInput(tx1: Tx, tx2: Tx): Promise { + return super.buildBaseRollupInput(tx1, tx2); + } + + public updateRootTrees(): Promise { + return super.updateRootTrees(); + } +} \ No newline at end of file diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts index 71bf3c72d9f9..abdef591eabb 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts @@ -21,6 +21,7 @@ import { VK_TREE_HEIGHT, } from '@aztec/circuits.js'; import { Tx } from '@aztec/tx'; +import { toBigIntBE, toBufferBE } from '@aztec/foundation'; import { MerkleTreeId, MerkleTreeDb } from '@aztec/world-state'; import flatMap from 'lodash.flatmap'; import { makeEmptyTx } from '../deps/tx.js'; @@ -28,9 +29,8 @@ import { Proof, Prover } from '../prover/index.js'; import { Simulator } from '../simulator/index.js'; import { VerificationKeys } from './vks.js'; -// REFACTOR: Move this somewhere generic, and do something less horrible without going through hex strings. -const frToBigInt = (fr: Fr) => fr.value; -const bigintToFr = (num: bigint) => new Fr(num); +const frToBigInt = (fr: Fr) => toBigIntBE(fr.toBuffer()); +const bigintToFr = (num: bigint) => new Fr(toBufferBE(num, 32)); const bigintToNum = (num: bigint) => Number(num); // Denotes fields that are not used now, but will be in the future From 2b61f99c5d8b48825708e7781bf94bd97c9aa882 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 28 Mar 2023 21:35:06 +0100 Subject: [PATCH 03/26] Fix import and format --- .../circuit_powered_block_builder.test.ts | 40 ++++++++++--------- .../circuit_powered_block_builder.ts | 6 ++- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts index 57e29af420e9..136598832f4b 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts @@ -1,14 +1,18 @@ -import { MerkleTreeDb, MerkleTreeId, MerkleTrees } from "@aztec/world-state"; -import { mock, MockProxy } from "jest-mock-extended"; -import { Prover } from "../prover/index.js"; -import { Simulator } from "../simulator/index.js"; -import { CircuitPoweredBlockBuilder } from "./circuit_powered_block_builder.js"; -import { VerificationKeys, getVerificationKeys } from "./vks.js"; +import { MerkleTreeDb, MerkleTreeId, MerkleTrees } from '@aztec/world-state'; +import { mock, MockProxy } from 'jest-mock-extended'; +import { Prover } from '../prover/index.js'; +import { Simulator } from '../simulator/index.js'; +import { CircuitPoweredBlockBuilder } from './circuit_powered_block_builder.js'; +import { VerificationKeys, getVerificationKeys } from './vks.js'; import { default as memdown } from 'memdown'; import { default as levelup } from 'levelup'; -import { BaseRollupInputs, Fr, UInt8Vector } from "@aztec/circuits.js"; -import { Tx } from "@aztec/p2p"; -import { makeBaseRollupPublicInputs, makePrivateKernelPublicInputs, makeRootRollupPublicInputs } from "@aztec/circuits.js/factories"; +import { BaseRollupInputs, Fr, UInt8Vector } from '@aztec/circuits.js'; +import { Tx } from '@aztec/tx'; +import { + makeBaseRollupPublicInputs, + makePrivateKernelPublicInputs, + makeRootRollupPublicInputs, +} from '@aztec/circuits.js/factories'; /* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore @@ -21,8 +25,8 @@ describe('sequencer/circuit_block_builder', () => { let vks: VerificationKeys; let simulator: MockProxy; let prover: MockProxy; - - const emptyProof = new UInt8Vector(Buffer.alloc(32, 0));; + + const emptyProof = new UInt8Vector(Buffer.alloc(32, 0)); beforeEach(async () => { blockNumber = 3; @@ -30,15 +34,13 @@ describe('sequencer/circuit_block_builder', () => { vks = getVerificationKeys(); simulator = mock(); prover = mock(); - builder = new TestSubject( - db, blockNumber, vks, simulator, prover - ); + builder = new TestSubject(db, blockNumber, vks, simulator, prover); // Populate root trees with first roots // TODO: Should MerkleTrees.init take care of this? await builder.updateRootTrees(); - prover.getBaseRollupProof.mockResolvedValue(emptyProof) + prover.getBaseRollupProof.mockResolvedValue(emptyProof); prover.getRootRollupProof.mockResolvedValue(emptyProof); simulator.baseRollupCircuit.mockResolvedValue(makeBaseRollupPublicInputs()); @@ -48,7 +50,7 @@ describe('sequencer/circuit_block_builder', () => { it('builds an L2 block', async () => { // Assemble a fake transaction, we'll tweak some fields below const tx = new Tx(makePrivateKernelPublicInputs(), emptyProof); - + // Set tree roots to proper values for (const [name, id] of [ ['privateDataTreeRoot', MerkleTreeId.DATA_TREE], @@ -57,10 +59,10 @@ describe('sequencer/circuit_block_builder', () => { ] as const) { tx.data.constants.oldTreeRoots[name] = new Fr((await db.getTreeInfo(id)).root); } - + // Actually build a block! const [l2block, proof] = await builder.buildL2Block(tx); - + expect(l2block.number).toEqual(blockNumber); expect(proof).toEqual(emptyProof); }); @@ -75,4 +77,4 @@ class TestSubject extends CircuitPoweredBlockBuilder { public updateRootTrees(): Promise { return super.updateRootTrees(); } -} \ No newline at end of file +} diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts index abdef591eabb..50e9f17a1f8a 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts @@ -196,7 +196,11 @@ export class CircuitPoweredBlockBuilder { this.validateSimulatedTree(localTree, simulatedTree, name); } - protected validateSimulatedTree(localTree: AppendOnlyTreeSnapshot, simulatedTree: AppendOnlyTreeSnapshot, name: string) { + protected validateSimulatedTree( + localTree: AppendOnlyTreeSnapshot, + simulatedTree: AppendOnlyTreeSnapshot, + name: string, + ) { if (!simulatedTree.root.toBuffer().equals(localTree.root.toBuffer())) { throw new Error(`${name} tree root mismatch (local ${localTree.root}, simulated ${simulatedTree.root})`); } From 5525f98ff5df05fda3ef66c02ae57d9681f627f6 Mon Sep 17 00:00:00 2001 From: spalladino Date: Tue, 28 Mar 2023 22:17:23 +0000 Subject: [PATCH 04/26] Build fixes after rebase --- yarn-project/sequencer-client/package.json | 1 + .../src/sequencer/circuit_powered_block_builder.ts | 2 +- yarn-project/sequencer-client/tsconfig.dest.json | 1 + yarn-project/sequencer-client/tsconfig.json | 1 + yarn-project/world-state/package.json | 2 +- yarn-project/yarn.lock | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/yarn-project/sequencer-client/package.json b/yarn-project/sequencer-client/package.json index dc708d767f8d..bd9c2d173651 100644 --- a/yarn-project/sequencer-client/package.json +++ b/yarn-project/sequencer-client/package.json @@ -37,6 +37,7 @@ "@aztec/foundation": "workspace:^", "@aztec/l1-contracts": "workspace:^", "@aztec/l2-block": "workspace:^", + "@aztec/merkle-tree": "workspace:^", "@aztec/p2p": "workspace:^", "@aztec/tx": "workspace:^", "@aztec/world-state": "workspace:^", diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts index 50e9f17a1f8a..098069e9f5e9 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts @@ -30,7 +30,7 @@ import { Simulator } from '../simulator/index.js'; import { VerificationKeys } from './vks.js'; const frToBigInt = (fr: Fr) => toBigIntBE(fr.toBuffer()); -const bigintToFr = (num: bigint) => new Fr(toBufferBE(num, 32)); +const bigintToFr = (num: bigint) => new Fr(num); const bigintToNum = (num: bigint) => Number(num); // Denotes fields that are not used now, but will be in the future diff --git a/yarn-project/sequencer-client/tsconfig.dest.json b/yarn-project/sequencer-client/tsconfig.dest.json index 81e4f6044e2a..8e697e3d954d 100644 --- a/yarn-project/sequencer-client/tsconfig.dest.json +++ b/yarn-project/sequencer-client/tsconfig.dest.json @@ -2,6 +2,7 @@ "extends": ".", "exclude": ["**/*.test.*", "**/fixtures/*"], "references": [ + { "path": "../archiver/tsconfig.dest.json" }, { "path": "../circuits.js/tsconfig.dest.json" }, { "path": "../l1-contracts/tsconfig.dest.json" }, { "path": "../l2-block/tsconfig.dest.json" }, diff --git a/yarn-project/sequencer-client/tsconfig.json b/yarn-project/sequencer-client/tsconfig.json index 6e00fde0941c..0a94134774e3 100644 --- a/yarn-project/sequencer-client/tsconfig.json +++ b/yarn-project/sequencer-client/tsconfig.json @@ -7,6 +7,7 @@ }, "include": ["src", "test"], "references": [ + { "path": "../archiver/tsconfig.dest.json" }, { "path": "../circuits.js/tsconfig.dest.json" }, { "path": "../l1-contracts/tsconfig.dest.json" }, { "path": "../l2-block/tsconfig.dest.json" }, diff --git a/yarn-project/world-state/package.json b/yarn-project/world-state/package.json index 8ca298538d62..b7e7550a759e 100644 --- a/yarn-project/world-state/package.json +++ b/yarn-project/world-state/package.json @@ -46,4 +46,4 @@ "ts-node": "^10.9.1", "typescript": "^4.9.5" } -} \ No newline at end of file +} diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 912080c29b27..6d30c3a13c05 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -492,6 +492,7 @@ __metadata: "@aztec/foundation": "workspace:^" "@aztec/l1-contracts": "workspace:^" "@aztec/l2-block": "workspace:^" + "@aztec/merkle-tree": "workspace:^" "@aztec/p2p": "workspace:^" "@aztec/tx": "workspace:^" "@aztec/world-state": "workspace:^" From 2ff3d0fffc327bbd1770046f4e61dd465de9681e Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 09:24:38 +0000 Subject: [PATCH 05/26] Formatting --- .../circuits.js/src/structs/base_rollup.test.ts | 3 +-- yarn-project/circuits.js/src/structs/base_rollup.ts | 2 +- yarn-project/circuits.js/src/structs/root_rollup.ts | 1 - yarn-project/circuits.js/src/tests/factories.ts | 10 ++++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn-project/circuits.js/src/structs/base_rollup.test.ts b/yarn-project/circuits.js/src/structs/base_rollup.test.ts index 6cf4768a5b87..3366c6ab435c 100644 --- a/yarn-project/circuits.js/src/structs/base_rollup.test.ts +++ b/yarn-project/circuits.js/src/structs/base_rollup.test.ts @@ -26,12 +26,11 @@ describe('structs/base_rollup', () => { makePreviousKernelData(0x100), makePreviousKernelData(0x200), ]; - + const startPrivateDateTreeSnapshot = makeAppendOnlyTreeSnapshot(0x100); const startNullifierTreeSnapshot = makeAppendOnlyTreeSnapshot(0x200); const startContractTreeSnapshot = makeAppendOnlyTreeSnapshot(0x300); - const lowNullifierLeafPreimages = range(2 * KERNEL_NEW_NULLIFIERS_LENGTH, 0x1000).map( x => new NullifierLeafPreimage(fr(x), fr(x + 0x100), x + 0x200), ); diff --git a/yarn-project/circuits.js/src/structs/base_rollup.ts b/yarn-project/circuits.js/src/structs/base_rollup.ts index ab7169502b6e..c3b798cfe6d1 100644 --- a/yarn-project/circuits.js/src/structs/base_rollup.ts +++ b/yarn-project/circuits.js/src/structs/base_rollup.ts @@ -204,4 +204,4 @@ export class BaseRollupPublicInputs { this.calldataHash, ); } -} \ No newline at end of file +} diff --git a/yarn-project/circuits.js/src/structs/root_rollup.ts b/yarn-project/circuits.js/src/structs/root_rollup.ts index d0421bcf787d..2fb590fefbf0 100644 --- a/yarn-project/circuits.js/src/structs/root_rollup.ts +++ b/yarn-project/circuits.js/src/structs/root_rollup.ts @@ -19,7 +19,6 @@ export class RootRollupInputs { public newCommitmentsSubtreeSiblingPath: Fr[], public newNullifiersSubtreeSiblingPath: Fr[], public newContractsSubtreeSiblingPath: Fr[], - ) { assertLength(this, 'newCommitmentsSubtreeSiblingPath', PRIVATE_DATA_TREE_HEIGHT); assertLength(this, 'newNullifiersSubtreeSiblingPath', NULLIFIER_TREE_HEIGHT); diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 7394f999485c..c0b4d27b101c 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -19,7 +19,7 @@ import { PRIVATE_CALL_STACK_LENGTH, PUBLIC_CALL_STACK_LENGTH, RETURN_VALUES_LENGTH, - VK_TREE_HEIGHT + VK_TREE_HEIGHT, } from '../structs/constants.js'; import { FunctionData } from '../structs/function_data.js'; import { @@ -31,14 +31,16 @@ import { PreviousKernelData, PrivateCallData, PrivateKernelInputs, - PrivateKernelPublicInputs + PrivateKernelPublicInputs, } from '../structs/kernel.js'; import { PrivateCallStackItem } from '../structs/private_call_stack_item.js'; import { AffineElement, AggregationObject, - ComposerType, EcdsaSignature, MembershipWitness, - UInt8Vector + ComposerType, + EcdsaSignature, + MembershipWitness, + UInt8Vector, } from '../structs/shared.js'; import { ContractDeploymentData, SignedTxRequest, TxContext, TxRequest } from '../structs/tx.js'; import { CommitmentMap, G1AffineElement, VerificationKey } from '../structs/verification_key.js'; From a40e5265f1eaf5928a6a01a3b35ed4d6c8006a67 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 09:27:32 +0000 Subject: [PATCH 06/26] Use same empty proof size as cpp --- .../sequencer-client/src/prover/{mock.ts => empty.ts} | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) rename yarn-project/sequencer-client/src/prover/{mock.ts => empty.ts} (69%) diff --git a/yarn-project/sequencer-client/src/prover/mock.ts b/yarn-project/sequencer-client/src/prover/empty.ts similarity index 69% rename from yarn-project/sequencer-client/src/prover/mock.ts rename to yarn-project/sequencer-client/src/prover/empty.ts index 17c92a5dd6ea..9406035c3b74 100644 --- a/yarn-project/sequencer-client/src/prover/mock.ts +++ b/yarn-project/sequencer-client/src/prover/empty.ts @@ -11,14 +11,16 @@ import { Prover } from './index.js'; /* eslint-disable */ -export class MockProver implements Prover { +const EMPTY_PROOF_SIZE = 42; + +export class EmptyProver implements Prover { async getBaseRollupProof(input: BaseRollupInputs, publicInputs: BaseRollupPublicInputs): Promise { - return new UInt8Vector(Buffer.alloc(0)); + return new UInt8Vector(Buffer.alloc(EMPTY_PROOF_SIZE, 0)); } async getMergeRollupProof(input: MergeRollupInputs, publicInputs: MergeRollupPublicInputs): Promise { - return new UInt8Vector(Buffer.alloc(0)); + return new UInt8Vector(Buffer.alloc(EMPTY_PROOF_SIZE, 0)); } async getRootRollupProof(input: RootRollupInputs, publicInputs: RootRollupPublicInputs): Promise { - return new UInt8Vector(Buffer.alloc(0)); + return new UInt8Vector(Buffer.alloc(EMPTY_PROOF_SIZE, 0)); } } From 4ccd2cfefd7f30310f7d69ee0c5998317aee6e35 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 09:37:09 +0000 Subject: [PATCH 07/26] Fix fr initialization in test --- .../src/sequencer/circuit_powered_block_builder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts index 136598832f4b..de65b80978f3 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts @@ -57,7 +57,7 @@ describe('sequencer/circuit_block_builder', () => { ['contractTreeRoot', MerkleTreeId.CONTRACT_TREE], ['nullifierTreeRoot', MerkleTreeId.NULLIFIER_TREE], ] as const) { - tx.data.constants.oldTreeRoots[name] = new Fr((await db.getTreeInfo(id)).root); + tx.data.constants.oldTreeRoots[name] = Fr.fromBuffer((await db.getTreeInfo(id)).root); } // Actually build a block! From b24e5f655ed3111489d9c26e4d5d096501d28577 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 11:04:15 +0000 Subject: [PATCH 08/26] Aztec address back to 32 bytes --- yarn-project/foundation/src/aztec-address/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/foundation/src/aztec-address/index.ts b/yarn-project/foundation/src/aztec-address/index.ts index d00319affeb1..6b12d32fc7e1 100644 --- a/yarn-project/foundation/src/aztec-address/index.ts +++ b/yarn-project/foundation/src/aztec-address/index.ts @@ -2,7 +2,7 @@ import { randomBytes } from '../crypto/index.js'; import { BufferReader } from '../index.js'; export class AztecAddress { - public static SIZE_IN_BYTES = 64; + public static SIZE_IN_BYTES = 32; public static ZERO = new AztecAddress(Buffer.alloc(AztecAddress.SIZE_IN_BYTES)); constructor(private buffer: Buffer) { From f6407bb6616be87db21bec7b21996a428954ab31 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 11:05:22 +0000 Subject: [PATCH 09/26] Adjust rollup subtree sizes --- .../circuits.js/src/structs/base_rollup.ts | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/yarn-project/circuits.js/src/structs/base_rollup.ts b/yarn-project/circuits.js/src/structs/base_rollup.ts index c3b798cfe6d1..d707376e5f84 100644 --- a/yarn-project/circuits.js/src/structs/base_rollup.ts +++ b/yarn-project/circuits.js/src/structs/base_rollup.ts @@ -4,6 +4,8 @@ import { serializeToBuffer } from '../utils/serialize.js'; import { CONTRACT_TREE_HEIGHT, CONTRACT_TREE_ROOTS_TREE_HEIGHT, + KERNEL_NEW_COMMITMENTS_LENGTH, + KERNEL_NEW_CONTRACTS_LENGTH, KERNEL_NEW_NULLIFIERS_LENGTH, NULLIFIER_TREE_HEIGHT, PRIVATE_DATA_TREE_HEIGHT, @@ -85,6 +87,10 @@ export class ConstantBaseRollupData { * Inputs to the base rollup circuit */ export class BaseRollupInputs { + public static PRIVATE_DATA_SUBTREE_HEIGHT = Math.log2(KERNEL_NEW_COMMITMENTS_LENGTH * 2); + public static CONTRACT_SUBTREE_HEIGHT = Math.log2(KERNEL_NEW_CONTRACTS_LENGTH * 2); + public static NULLIFIER_SUBTREE_HEIGHT = Math.log2(KERNEL_NEW_NULLIFIERS_LENGTH * 2); + constructor( public kernelData: [PreviousKernelData, PreviousKernelData], @@ -112,9 +118,21 @@ export class BaseRollupInputs { ) { assertLength(this, 'lowNullifierLeafPreimages', 2 * KERNEL_NEW_NULLIFIERS_LENGTH); assertLength(this, 'lowNullifierMembershipWitness', 2 * KERNEL_NEW_NULLIFIERS_LENGTH); - assertLength(this, 'newCommitmentsSubtreeSiblingPath', PRIVATE_DATA_TREE_HEIGHT); - assertLength(this, 'newNullifiersSubtreeSiblingPath', NULLIFIER_TREE_HEIGHT); - assertLength(this, 'newContractsSubtreeSiblingPath', CONTRACT_TREE_HEIGHT); + assertLength( + this, + 'newCommitmentsSubtreeSiblingPath', + PRIVATE_DATA_TREE_HEIGHT - BaseRollupInputs.PRIVATE_DATA_SUBTREE_HEIGHT, + ); + assertLength( + this, + 'newNullifiersSubtreeSiblingPath', + NULLIFIER_TREE_HEIGHT - BaseRollupInputs.NULLIFIER_SUBTREE_HEIGHT, + ); + assertLength( + this, + 'newContractsSubtreeSiblingPath', + CONTRACT_TREE_HEIGHT - BaseRollupInputs.CONTRACT_SUBTREE_HEIGHT, + ); } static from(fields: FieldsOf): BaseRollupInputs { From 4e6c10fa13e7dc06a9a43bd36c7b55091acafb80 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 11:05:40 +0000 Subject: [PATCH 10/26] Add toString to fields --- yarn-project/foundation/src/fields/fields.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index cfd1182badc8..0a606c457db9 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -27,6 +27,10 @@ export class Fr { toBuffer() { return toBufferBE(this.value, Fr.SIZE_IN_BYTES); } + + toString() { + return '0x' + this.value.toString(16); + } } export class Fq { @@ -53,4 +57,8 @@ export class Fq { toBuffer() { return toBufferBE(this.value, Fq.SIZE_IN_BYTES); } + + toString() { + return '0x' + this.value.toString(16); + } } From 4f4b34a84fd463fc48654388dfb18cfe7e9ba054 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 11:05:51 +0000 Subject: [PATCH 11/26] Remove unused checkLength util --- yarn-project/circuits.js/src/structs/shared.ts | 4 ++-- yarn-project/circuits.js/src/utils/jsUtils.ts | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/yarn-project/circuits.js/src/structs/shared.ts b/yarn-project/circuits.js/src/structs/shared.ts index 3fbe7ca2acb4..986a56b87389 100644 --- a/yarn-project/circuits.js/src/structs/shared.ts +++ b/yarn-project/circuits.js/src/structs/shared.ts @@ -1,10 +1,10 @@ import { BufferReader } from '@aztec/foundation'; import { Fq, Fr } from '@aztec/foundation/fields'; -import { assertLength, checkLength, range } from '../utils/jsUtils.js'; +import { assertLength, range } from '../utils/jsUtils.js'; import { Bufferable, serializeToBuffer } from '../utils/serialize.js'; export class MembershipWitness { constructor(pathSize: N, public leafIndex: UInt32, public siblingPath: Fr[]) { - checkLength(this.siblingPath, pathSize, 'MembershipWitness.siblingPath'); + assertLength(this, 'siblingPath', pathSize); } toBuffer() { diff --git a/yarn-project/circuits.js/src/utils/jsUtils.ts b/yarn-project/circuits.js/src/utils/jsUtils.ts index 33c815e49410..10e55fd6071d 100644 --- a/yarn-project/circuits.js/src/utils/jsUtils.ts +++ b/yarn-project/circuits.js/src/utils/jsUtils.ts @@ -28,18 +28,6 @@ export function assertLength Date: Wed, 29 Mar 2023 11:06:03 +0000 Subject: [PATCH 12/26] Make empty txs explicit --- yarn-project/sequencer-client/src/deps/tx.ts | 3 ++- yarn-project/tx/src/tx.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/yarn-project/sequencer-client/src/deps/tx.ts b/yarn-project/sequencer-client/src/deps/tx.ts index 0bfa4847bfec..aab3c970c0a8 100644 --- a/yarn-project/sequencer-client/src/deps/tx.ts +++ b/yarn-project/sequencer-client/src/deps/tx.ts @@ -102,5 +102,6 @@ function makeEmptyPrivateKernelPublicInputs() { } export function makeEmptyTx(): Tx { - return new Tx(makeEmptyPrivateKernelPublicInputs(), makeEmptyProof()); + const isEmpty = true; + return new Tx(makeEmptyPrivateKernelPublicInputs(), makeEmptyProof(), isEmpty); } diff --git a/yarn-project/tx/src/tx.ts b/yarn-project/tx/src/tx.ts index 08963c84850e..d8a2a4dc5c4b 100644 --- a/yarn-project/tx/src/tx.ts +++ b/yarn-project/tx/src/tx.ts @@ -15,7 +15,7 @@ const hash = new Keccak(256); */ export class Tx { private _id?: Buffer; - constructor(public readonly data: PrivateKernelPublicInputs, public readonly proof: UInt8Vector) {} + constructor(public readonly data: PrivateKernelPublicInputs, public readonly proof: UInt8Vector, public readonly isEmpty = false) {} /** * Construct & return transaction ID. From 4000c23ffa19f51d56506032612761cdac1c1f57 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 11:06:52 +0000 Subject: [PATCH 13/26] Expose leaf values in merkle trees for debugging --- .../src/synchroniser/server_world_state_synchroniser.ts | 4 ++++ yarn-project/world-state/src/world-state-db/index.ts | 1 + .../world-state/src/world-state-db/memory_world_state_db.ts | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/yarn-project/world-state/src/synchroniser/server_world_state_synchroniser.ts b/yarn-project/world-state/src/synchroniser/server_world_state_synchroniser.ts index b9a1852e53ae..d87035f5134e 100644 --- a/yarn-project/world-state/src/synchroniser/server_world_state_synchroniser.ts +++ b/yarn-project/world-state/src/synchroniser/server_world_state_synchroniser.ts @@ -26,6 +26,10 @@ export class ServerWorldStateSynchroniser implements WorldStateSynchroniser { ) { this.l2BlockDownloader = new L2BlockDownloader(l2BlockSource, 1000, 100); } + + public getLeafValue(treeId: MerkleTreeId, index: bigint): Promise { + return this.merkleTreeDb.getLeafValue(treeId, index); + } public getPreviousValueIndex( treeId: MerkleTreeId.NULLIFIER_TREE, 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 0236445049e6..2c232f367eda 100644 --- a/yarn-project/world-state/src/world-state-db/index.ts +++ b/yarn-project/world-state/src/world-state-db/index.ts @@ -47,6 +47,7 @@ export interface MerkleTreeOperations { ): Promise<{ index: number; alreadyPresent: boolean }>; getLeafData(treeId: IndexedMerkleTreeId, index: number): LeafData | undefined; findLeafIndex(treeId: MerkleTreeId, value: Buffer): Promise; + getLeafValue(treeId: MerkleTreeId, index: bigint): Promise } /** diff --git a/yarn-project/world-state/src/world-state-db/memory_world_state_db.ts b/yarn-project/world-state/src/world-state-db/memory_world_state_db.ts index 048cfda5f33a..307a204038c9 100644 --- a/yarn-project/world-state/src/world-state-db/memory_world_state_db.ts +++ b/yarn-project/world-state/src/world-state-db/memory_world_state_db.ts @@ -19,7 +19,7 @@ export class MerkleTrees implements MerkleTreeDb { private jobQueue = new SerialQueue(); constructor(private db: levelup.LevelUp) {} - + /** * Initialises the collection of Merkle Trees. */ @@ -87,6 +87,10 @@ export class MerkleTrees implements MerkleTreeDb { return await this.synchronise(() => this._getTreeInfo(treeId)); } + public async getLeafValue(treeId: MerkleTreeId, index: bigint): Promise { + return await this.synchronise(() => this.trees[treeId].getLeafValue(index)); + } + /** * Gets the sibling path for a leaf in a tree. * @param treeId - The ID of the tree. From ca809738f90924936a7dc4fbd79c565c5d8d37b7 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 11:07:32 +0000 Subject: [PATCH 14/26] Handle empty txs in block builder --- .../circuit_powered_block_builder.ts | 59 ++++++++++++++----- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts index 098069e9f5e9..844a4e874278 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts @@ -6,9 +6,6 @@ import { ConstantBaseRollupData, CONTRACT_TREE_ROOTS_TREE_HEIGHT, Fr, - KERNEL_NEW_COMMITMENTS_LENGTH, - KERNEL_NEW_CONTRACTS_LENGTH, - KERNEL_NEW_NULLIFIERS_LENGTH, MembershipWitness, NullifierLeafPreimage, NULLIFIER_TREE_HEIGHT, @@ -20,10 +17,11 @@ import { UInt8Vector, VK_TREE_HEIGHT, } from '@aztec/circuits.js'; +import { toBigIntBE } from '@aztec/foundation'; import { Tx } from '@aztec/tx'; -import { toBigIntBE, toBufferBE } from '@aztec/foundation'; -import { MerkleTreeId, MerkleTreeDb } from '@aztec/world-state'; +import { MerkleTreeDb, MerkleTreeId } from '@aztec/world-state'; import flatMap from 'lodash.flatmap'; +import times from 'lodash.times'; import { makeEmptyTx } from '../deps/tx.js'; import { Proof, Prover } from '../prover/index.js'; import { Simulator } from '../simulator/index.js'; @@ -264,8 +262,14 @@ export class CircuitPoweredBlockBuilder { treeId: MerkleTreeId, height: N, ): Promise> { + // If this is an empty tx, then just return zeroes + if (value.value === 0n) return this.makeEmptyMembershipWitness(height); + const index = await this.db.findLeafIndex(treeId, value.toBuffer()); - if (!index) throw new Error(`Leaf with value ${value} not found in tree ${treeId}`); + if (index === undefined) { + await this.inspectTree(treeId); + throw new Error(`Leaf with value ${value} not found in tree ${treeId}`); + } const path = await this.db.getSiblingPath(treeId, index); // TODO: Check conversion from bigint to number return new MembershipWitness( @@ -288,11 +292,21 @@ export class CircuitPoweredBlockBuilder { } protected async getLowNullifierInfo(nullifier: Fr) { + // Return empty nullifier info for an empty tx + if (nullifier.value === 0n) { + return { + index: 0, + leafPreimage: new NullifierLeafPreimage(new Fr(0n), new Fr(0n), 0), + witness: this.makeEmptyMembershipWitness(NULLIFIER_TREE_HEIGHT), + }; + } + const tree = MerkleTreeId.NULLIFIER_TREE; const prevValueIndex = await this.db.getPreviousValueIndex(tree, frToBigInt(nullifier)); const prevValueInfo = this.db.getLeafData(tree, prevValueIndex.index); if (!prevValueInfo) throw new Error(`Nullifier tree should have one initial leaf`); const prevValueSiblingPath = await this.db.getSiblingPath(tree, BigInt(prevValueIndex.index)); + return { index: prevValueIndex, leafPreimage: new NullifierLeafPreimage( @@ -361,20 +375,18 @@ export class CircuitPoweredBlockBuilder { PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT, ); - // Calculate subtree heights as the log2 of the total number of new elements inserted by this circuit - const dataSubtreeSize = Math.log2(KERNEL_NEW_COMMITMENTS_LENGTH) + 1; - const contractSubtreeSize = Math.log2(KERNEL_NEW_CONTRACTS_LENGTH) + 1; - const nullifierSubtreeSize = Math.log2(KERNEL_NEW_NULLIFIERS_LENGTH) + 1; - // Get the subtree sibling paths for the circuit - const newCommitmentsSubtreeSiblingPath = await this.getSubtreeSiblingPath(MerkleTreeId.DATA_TREE, dataSubtreeSize); + const newCommitmentsSubtreeSiblingPath = await this.getSubtreeSiblingPath( + MerkleTreeId.DATA_TREE, + BaseRollupInputs.PRIVATE_DATA_SUBTREE_HEIGHT, + ); const newContractsSubtreeSiblingPath = await this.getSubtreeSiblingPath( MerkleTreeId.CONTRACT_TREE, - contractSubtreeSize, + BaseRollupInputs.CONTRACT_SUBTREE_HEIGHT, ); const newNullifiersSubtreeSiblingPath = await this.getSubtreeSiblingPath( MerkleTreeId.NULLIFIER_TREE, - nullifierSubtreeSize, + BaseRollupInputs.NULLIFIER_SUBTREE_HEIGHT, ); return BaseRollupInputs.from({ @@ -398,4 +410,23 @@ export class CircuitPoweredBlockBuilder { ], } as BaseRollupInputs); } + + // For debugging purposes (maybe move it to merkle tree db?) + protected async inspectTree(treeId: MerkleTreeId) { + for (let i = 0; i < (await this.db.getTreeInfo(treeId).then(t => t.size)); i++) { + console.log( + `Tree ${treeId} leaf ${i}: ${await this.db + .getLeafValue(treeId, BigInt(i)) + .then(x => x?.toString('hex') ?? '[undefined]')}`, + ); + } + } + + protected makeEmptyMembershipWitness(height: N) { + return new MembershipWitness( + height, + 0, + times(height, () => new Fr(0n)), + ); + } } From 3ef1e8d37226a643817a160b92423a69e4b6b711 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 14:19:23 +0000 Subject: [PATCH 15/26] Update root rollup inputs --- .../circuits.js/src/structs/root_rollup.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/yarn-project/circuits.js/src/structs/root_rollup.ts b/yarn-project/circuits.js/src/structs/root_rollup.ts index 2fb590fefbf0..7466dcd59742 100644 --- a/yarn-project/circuits.js/src/structs/root_rollup.ts +++ b/yarn-project/circuits.js/src/structs/root_rollup.ts @@ -2,7 +2,7 @@ import { Fr } from '@aztec/foundation'; import { assertLength, FieldsOf } from '../utils/jsUtils.js'; import { serializeToBuffer } from '../utils/serialize.js'; import { AppendOnlyTreeSnapshot } from './base_rollup.js'; -import { CONTRACT_TREE_HEIGHT, NULLIFIER_TREE_HEIGHT, PRIVATE_DATA_TREE_HEIGHT } from './constants.js'; +import { CONTRACT_TREE_ROOTS_TREE_HEIGHT, PRIVATE_DATA_TREE_HEIGHT } from './constants.js'; import { PreviousRollupData } from './merge_rollup.js'; import { AggregationObject } from './shared.js'; @@ -10,31 +10,32 @@ export class RootRollupInputs { constructor( public previousRollupData: [PreviousRollupData, PreviousRollupData], - public startPrivateDataTreeSnapshot: AppendOnlyTreeSnapshot, - // Note: the start_nullifier_tree_snapshot is contained within previous_rollup_data[0].public_inputs.start_nullifier_tree_snapshot. - public startContractTreeSnapshot: AppendOnlyTreeSnapshot, - - // For inserting the new subtrees into their respective trees: - // Note: the insertion leaf index can be derived from the above snapshots' `next_available_leaf_index` values. - public newCommitmentsSubtreeSiblingPath: Fr[], - public newNullifiersSubtreeSiblingPath: Fr[], - public newContractsSubtreeSiblingPath: Fr[], + public newHistoricPrivateDataTreeRootSiblingPath: Fr[], + public newHistoricContractDataTreeRootSiblingPath: Fr[], ) { - assertLength(this, 'newCommitmentsSubtreeSiblingPath', PRIVATE_DATA_TREE_HEIGHT); - assertLength(this, 'newNullifiersSubtreeSiblingPath', NULLIFIER_TREE_HEIGHT); - assertLength(this, 'newContractsSubtreeSiblingPath', CONTRACT_TREE_HEIGHT); + assertLength(this, 'newHistoricPrivateDataTreeRootSiblingPath', PRIVATE_DATA_TREE_HEIGHT); + assertLength(this, 'newHistoricContractDataTreeRootSiblingPath', CONTRACT_TREE_ROOTS_TREE_HEIGHT); } toBuffer() { return serializeToBuffer( this.previousRollupData, - this.startPrivateDataTreeSnapshot, - this.startContractTreeSnapshot, - this.newCommitmentsSubtreeSiblingPath, - this.newNullifiersSubtreeSiblingPath, - this.newContractsSubtreeSiblingPath, + this.newHistoricPrivateDataTreeRootSiblingPath, + this.newHistoricContractDataTreeRootSiblingPath, ); } + + static from(fields: FieldsOf): RootRollupInputs { + return new RootRollupInputs(...RootRollupInputs.getFields(fields)); + } + + static getFields(fields: FieldsOf) { + return [ + fields.previousRollupData, + fields.newHistoricPrivateDataTreeRootSiblingPath, + fields.newHistoricContractDataTreeRootSiblingPath, + ] as const; + } } export class RootRollupPublicInputs { From b7379b5ca7dd84d32ab2c1942e8ba74b5e28317d Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 14:19:43 +0000 Subject: [PATCH 16/26] Pedersen hash new contract data from a tx --- yarn-project/sequencer-client/src/deps/tx.ts | 27 +++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/yarn-project/sequencer-client/src/deps/tx.ts b/yarn-project/sequencer-client/src/deps/tx.ts index aab3c970c0a8..4e4e2ff50604 100644 --- a/yarn-project/sequencer-client/src/deps/tx.ts +++ b/yarn-project/sequencer-client/src/deps/tx.ts @@ -1,4 +1,7 @@ +import { BarretenbergWasm } from '@aztec/barretenberg.js/wasm'; +import { pedersenCompressInputs } from '@aztec/barretenberg.js/crypto'; import { + AccumulatedData, AffineElement, AggregationObject, AztecAddress, @@ -21,9 +24,8 @@ import { OptionallyRevealedData, PrivateKernelPublicInputs, TxContext, + UInt8Vector, } from '@aztec/circuits.js'; -import { AccumulatedData } from '@aztec/circuits.js'; -import { UInt8Vector } from '@aztec/circuits.js'; import { Tx } from '@aztec/tx'; import times from 'lodash.times'; @@ -39,11 +41,11 @@ function makeEmptyEthAddress() { return new EthAddress(Buffer.alloc(20, 0)); } -export function makeEmptyNewContractData(): NewContractData { +function makeEmptyNewContractData(): NewContractData { return new NewContractData(AztecAddress.ZERO, makeEmptyEthAddress(), frZero()); } -export function makeEmptyAggregationObject(): AggregationObject { +function makeEmptyAggregationObject(): AggregationObject { return new AggregationObject( new AffineElement(fqZero(), fqZero()), new AffineElement(fqZero(), fqZero()), @@ -52,20 +54,20 @@ export function makeEmptyAggregationObject(): AggregationObject { ); } -export function makeEmptyTxContext(): TxContext { +function makeEmptyTxContext(): TxContext { const deploymentData = new ContractDeploymentData(frZero(), frZero(), frZero(), makeEmptyEthAddress()); return new TxContext(false, false, true, deploymentData); } -export function makeEmptyOldTreeRoots(): OldTreeRoots { +function makeEmptyOldTreeRoots(): OldTreeRoots { return new OldTreeRoots(frZero(), frZero(), frZero(), frZero()); } -export function makeEmptyConstantData(): ConstantData { +function makeEmptyConstantData(): ConstantData { return new ConstantData(makeEmptyOldTreeRoots(), makeEmptyTxContext()); } -export function makeEmptyOptionallyRevealedData(): OptionallyRevealedData { +function makeEmptyOptionallyRevealedData(): OptionallyRevealedData { return new OptionallyRevealedData( frZero(), new FunctionData(0, true, true), @@ -79,7 +81,7 @@ export function makeEmptyOptionallyRevealedData(): OptionallyRevealedData { ); } -export function makeEmptyAccumulatedData(): AccumulatedData { +function makeEmptyAccumulatedData(): AccumulatedData { return new AccumulatedData( makeEmptyAggregationObject(), frZero(), @@ -105,3 +107,10 @@ export function makeEmptyTx(): Tx { const isEmpty = true; return new Tx(makeEmptyPrivateKernelPublicInputs(), makeEmptyProof(), isEmpty); } + +export function hashNewContractData(wasm: BarretenbergWasm, cd: NewContractData) { + return pedersenCompressInputs( + wasm, + [cd.contractAddress, cd.portalContractAddress, cd.functionTreeRoot].map(x => x.toBuffer()), + ); +} From 73a94fd881351093ac2171136a0914bd5ecd1b22 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 14:20:04 +0000 Subject: [PATCH 17/26] Add util for inspecting a tree --- .../server_world_state_synchroniser.ts | 2 +- .../world-state/src/world-state-db/index.ts | 16 +++++++++++++++- .../src/world-state-db/memory_world_state_db.ts | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/yarn-project/world-state/src/synchroniser/server_world_state_synchroniser.ts b/yarn-project/world-state/src/synchroniser/server_world_state_synchroniser.ts index d87035f5134e..1067f21c207b 100644 --- a/yarn-project/world-state/src/synchroniser/server_world_state_synchroniser.ts +++ b/yarn-project/world-state/src/synchroniser/server_world_state_synchroniser.ts @@ -26,7 +26,7 @@ export class ServerWorldStateSynchroniser implements WorldStateSynchroniser { ) { this.l2BlockDownloader = new L2BlockDownloader(l2BlockSource, 1000, 100); } - + public getLeafValue(treeId: MerkleTreeId, index: bigint): Promise { return this.merkleTreeDb.getLeafValue(treeId, index); } 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 2c232f367eda..481a48450133 100644 --- a/yarn-project/world-state/src/world-state-db/index.ts +++ b/yarn-project/world-state/src/world-state-db/index.ts @@ -47,7 +47,7 @@ export interface MerkleTreeOperations { ): Promise<{ index: number; alreadyPresent: boolean }>; getLeafData(treeId: IndexedMerkleTreeId, index: number): LeafData | undefined; findLeafIndex(treeId: MerkleTreeId, value: Buffer): Promise; - getLeafValue(treeId: MerkleTreeId, index: bigint): Promise + getLeafValue(treeId: MerkleTreeId, index: bigint): Promise; } /** @@ -57,3 +57,17 @@ export interface MerkleTreeDb extends MerkleTreeOperations { commit(): Promise; rollback(): Promise; } + +/** + * Outputs a tree leaves to console.log for debugging purposes. + */ +export async function inspectTree(db: MerkleTreeOperations, treeId: MerkleTreeId) { + const info = await db.getTreeInfo(treeId); + let output = [`Tree id=${treeId} size=${info.size} root=0x${info.root.toString('hex')}`]; + for (let i = 0; i < info.size; i++) { + output.push( + ` Leaf ${i}: ${await db.getLeafValue(treeId, BigInt(i)).then(x => x?.toString('hex') ?? '[undefined]')}`, + ); + } + console.log(output.join('\n')); +} diff --git a/yarn-project/world-state/src/world-state-db/memory_world_state_db.ts b/yarn-project/world-state/src/world-state-db/memory_world_state_db.ts index 307a204038c9..8d54290739cd 100644 --- a/yarn-project/world-state/src/world-state-db/memory_world_state_db.ts +++ b/yarn-project/world-state/src/world-state-db/memory_world_state_db.ts @@ -19,7 +19,7 @@ export class MerkleTrees implements MerkleTreeDb { private jobQueue = new SerialQueue(); constructor(private db: levelup.LevelUp) {} - + /** * Initialises the collection of Merkle Trees. */ From ddc150857d39d709db334ba51c54919af575f358 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 14:20:22 +0000 Subject: [PATCH 18/26] Updates to block builder (wip) --- .../circuit_powered_block_builder.test.ts | 126 +++++++++++++++-- .../circuit_powered_block_builder.ts | 127 ++++++++++-------- 2 files changed, 185 insertions(+), 68 deletions(-) diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts index de65b80978f3..55ebbaa38917 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts @@ -1,4 +1,4 @@ -import { MerkleTreeDb, MerkleTreeId, MerkleTrees } from '@aztec/world-state'; +import { inspectTree, MerkleTreeDb, MerkleTreeId, MerkleTrees } from '@aztec/world-state'; import { mock, MockProxy } from 'jest-mock-extended'; import { Prover } from '../prover/index.js'; import { Simulator } from '../simulator/index.js'; @@ -6,13 +6,23 @@ import { CircuitPoweredBlockBuilder } from './circuit_powered_block_builder.js'; import { VerificationKeys, getVerificationKeys } from './vks.js'; import { default as memdown } from 'memdown'; import { default as levelup } from 'levelup'; -import { BaseRollupInputs, Fr, UInt8Vector } from '@aztec/circuits.js'; +import { + AppendOnlyTreeSnapshot, + BaseRollupInputs, + BaseRollupPublicInputs, + Fr, + RootRollupPublicInputs, + UInt8Vector, +} from '@aztec/circuits.js'; import { Tx } from '@aztec/tx'; import { makeBaseRollupPublicInputs, makePrivateKernelPublicInputs, makeRootRollupPublicInputs, } from '@aztec/circuits.js/factories'; +import { hashNewContractData, makeEmptyTx } from '../deps/tx.js'; +import flatMap from 'lodash.flatmap'; +import { BarretenbergWasm } from '@aztec/barretenberg.js/wasm'; /* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore @@ -20,46 +30,136 @@ export const createMemDown = () => memdown(); describe('sequencer/circuit_block_builder', () => { let builder: TestSubject; - let db: MerkleTreeDb; - let blockNumber: number; + let builderDb: MerkleTreeDb; + let expectsDb: MerkleTreeDb; let vks: VerificationKeys; let simulator: MockProxy; let prover: MockProxy; + let blockNumber: number; + let baseRollupOutputLeft: BaseRollupPublicInputs; + let baseRollupOutputRight: BaseRollupPublicInputs; + let rootRollupOutput: RootRollupPublicInputs; + + let wasm: BarretenbergWasm; + const emptyProof = new UInt8Vector(Buffer.alloc(32, 0)); + beforeAll(async () => { + wasm = new BarretenbergWasm(); + await wasm.init(); + }); + beforeEach(async () => { blockNumber = 3; - db = await MerkleTrees.new(levelup(createMemDown())); + builderDb = await MerkleTrees.new(levelup(createMemDown())); + expectsDb = await MerkleTrees.new(levelup(createMemDown())); vks = getVerificationKeys(); simulator = mock(); prover = mock(); - builder = new TestSubject(db, blockNumber, vks, simulator, prover); + builder = new TestSubject(builderDb, blockNumber, vks, simulator, prover, wasm); - // Populate root trees with first roots - // TODO: Should MerkleTrees.init take care of this? + // Populate root trees with first roots from the empty trees + // TODO: Should this be responsibility of the MerkleTreeDb init? + await updateRootTrees(); await builder.updateRootTrees(); + // Create mock outputs for simualator + baseRollupOutputLeft = makeBaseRollupPublicInputs(); + baseRollupOutputRight = makeBaseRollupPublicInputs(); + rootRollupOutput = makeRootRollupPublicInputs(); + + // Set up mocks prover.getBaseRollupProof.mockResolvedValue(emptyProof); prover.getRootRollupProof.mockResolvedValue(emptyProof); - - simulator.baseRollupCircuit.mockResolvedValue(makeBaseRollupPublicInputs()); - simulator.rootRollupCircuit.mockResolvedValue(makeRootRollupPublicInputs()); + simulator.baseRollupCircuit + .mockResolvedValueOnce(baseRollupOutputLeft) + .mockResolvedValueOnce(baseRollupOutputRight); + simulator.rootRollupCircuit.mockResolvedValue(rootRollupOutput); }); + const updateRootTrees = async () => { + for (const [newTree, rootTree] of [ + [MerkleTreeId.DATA_TREE, MerkleTreeId.DATA_TREE_ROOTS_TREE], + [MerkleTreeId.CONTRACT_TREE, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE], + ] as const) { + if (rootTree === MerkleTreeId.CONTRACT_TREE_ROOTS_TREE) { + console.log(`About to update tree ${rootTree}`); + await inspectTree(expectsDb, rootTree); + } + const newTreeInfo = await expectsDb.getTreeInfo(newTree); + await expectsDb.appendLeaves(rootTree, [newTreeInfo.root]); + } + }; + + // Updates the expectedDb trees based on the new commitments, contracts, and nullifiers from these txs + const updateExpectedTreesFromTxs = async (txs: Tx[]) => { + for (const [tree, leaves] of [ + [MerkleTreeId.DATA_TREE, flatMap(txs, tx => tx.data.end.newCommitments.map(l => l.toBuffer()))], + [MerkleTreeId.CONTRACT_TREE, flatMap(txs, tx => tx.data.end.newContracts.map(n => hashNewContractData(wasm, n)))], + [MerkleTreeId.NULLIFIER_TREE, flatMap(txs, tx => tx.data.end.newNullifiers.map(l => l.toBuffer()))], + ] as const) { + await expectsDb.appendLeaves(tree, leaves); + } + + for (const [newTree, rootTree] of [ + [MerkleTreeId.DATA_TREE, MerkleTreeId.DATA_TREE_ROOTS_TREE], + [MerkleTreeId.CONTRACT_TREE, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE], + ] as const) { + const newTreeInfo = await expectsDb.getTreeInfo(newTree); + await expectsDb.appendLeaves(rootTree, [newTreeInfo.root]); + } + }; + + const getTreeSnapshot = async (tree: MerkleTreeId) => { + const treeInfo = await expectsDb.getTreeInfo(tree); + return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size)); + }; + it('builds an L2 block', async () => { // Assemble a fake transaction, we'll tweak some fields below const tx = new Tx(makePrivateKernelPublicInputs(), emptyProof); + const txsLeft = [tx, makeEmptyTx()]; + const txsRight = [makeEmptyTx(), makeEmptyTx()]; + const txs = [...txsLeft, ...txsRight]; - // Set tree roots to proper values + // Set tree roots to proper values in the tx for (const [name, id] of [ ['privateDataTreeRoot', MerkleTreeId.DATA_TREE], ['contractTreeRoot', MerkleTreeId.CONTRACT_TREE], ['nullifierTreeRoot', MerkleTreeId.NULLIFIER_TREE], ] as const) { - tx.data.constants.oldTreeRoots[name] = Fr.fromBuffer((await db.getTreeInfo(id)).root); + tx.data.constants.oldTreeRoots[name] = Fr.fromBuffer((await builderDb.getTreeInfo(id)).root); } + // Calculate what would be the tree roots after the txs from the first base rollup land and update mock circuit output + await updateExpectedTreesFromTxs(txsLeft); + baseRollupOutputLeft.endContractTreeSnapshot = await getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); + baseRollupOutputLeft.endNullifierTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); + baseRollupOutputLeft.endPrivateDataTreeSnapshot = await getTreeSnapshot(MerkleTreeId.DATA_TREE); + + await inspectTree(expectsDb, MerkleTreeId.CONTRACT_TREE); + + // Same for the two txs on the right + await updateExpectedTreesFromTxs(txsRight); + baseRollupOutputRight.endContractTreeSnapshot = await getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); + baseRollupOutputRight.endNullifierTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); + baseRollupOutputRight.endPrivateDataTreeSnapshot = await getTreeSnapshot(MerkleTreeId.DATA_TREE); + + // And update the root trees now to create proper output to the root rollup circuit + await updateRootTrees(); + console.log(`Expected tree`); + await inspectTree(expectsDb, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE); + rootRollupOutput.endContractTreeSnapshot = await getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); + rootRollupOutput.endNullifierTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); + rootRollupOutput.endPrivateDataTreeSnapshot = await getTreeSnapshot(MerkleTreeId.DATA_TREE); + rootRollupOutput.endTreeOfHistoricContractTreeRootsSnapshot = await getTreeSnapshot( + MerkleTreeId.CONTRACT_TREE_ROOTS_TREE, + ); + rootRollupOutput.endTreeOfHistoricPrivateDataTreeRootsSnapshot = await getTreeSnapshot( + MerkleTreeId.DATA_TREE_ROOTS_TREE, + ); + // Actually build a block! const [l2block, proof] = await builder.buildL2Block(tx); diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts index 844a4e874278..ed02aa4b59ff 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts @@ -1,4 +1,5 @@ import { ContractData, L2Block } from '@aztec/archiver'; +import { BarretenbergWasm } from '@aztec/barretenberg.js/wasm'; import { AppendOnlyTreeSnapshot, BaseRollupInputs, @@ -19,10 +20,10 @@ import { } from '@aztec/circuits.js'; import { toBigIntBE } from '@aztec/foundation'; import { Tx } from '@aztec/tx'; -import { MerkleTreeDb, MerkleTreeId } from '@aztec/world-state'; +import { inspectTree, MerkleTreeDb, MerkleTreeId } from '@aztec/world-state'; import flatMap from 'lodash.flatmap'; import times from 'lodash.times'; -import { makeEmptyTx } from '../deps/tx.js'; +import { hashNewContractData, makeEmptyTx } from '../deps/tx.js'; import { Proof, Prover } from '../prover/index.js'; import { Simulator } from '../simulator/index.js'; import { VerificationKeys } from './vks.js'; @@ -47,6 +48,7 @@ export class CircuitPoweredBlockBuilder { protected vks: VerificationKeys, protected simulator: Simulator, protected prover: Prover, + protected wasm: BarretenbergWasm, ) {} public async buildL2Block(tx: Tx): Promise<[L2Block, UInt8Vector]> { @@ -81,7 +83,9 @@ export class CircuitPoweredBlockBuilder { // Collect all new nullifiers, commitments, and contracts from all txs in this block const newNullifiers = flatMap(txs, tx => tx.data.end.newNullifiers); const newCommitments = flatMap(txs, tx => tx.data.end.newCommitments); - const newContracts = flatMap(txs, tx => tx.data.end.newContracts).map(c => c.functionTreeRoot); + const newContracts = flatMap(txs, tx => tx.data.end.newContracts).map(cd => + Fr.fromBuffer(hashNewContractData(this.wasm, cd)), + ); const newContractData = flatMap(txs, tx => tx.data.end.newContracts).map( n => new ContractData(n.contractAddress, n.portalContractAddress), ); @@ -115,14 +119,17 @@ export class CircuitPoweredBlockBuilder { protected async runCircuits(txs: Tx[]): Promise<[RootRollupPublicInputs, Proof]> { const [tx1, tx2, tx3, tx4] = txs; + // Simulate both base rollup circuits, updating the data, contract, and nullifier trees in the process const [baseRollupInputLeft, baseRollupOutputLeft] = await this.baseRollupCircuit(tx1, tx2); const [baseRollupInputRight, baseRollupOutputRight] = await this.baseRollupCircuit(tx3, tx4); + // Get the proofs for them in parallel (faked for now) const [baseRollupProofLeft, baseRollupProofRight] = await Promise.all([ this.prover.getBaseRollupProof(baseRollupInputLeft, baseRollupOutputLeft), this.prover.getBaseRollupProof(baseRollupInputRight, baseRollupOutputRight), ]); + // Get the input for the root rollup circuit based on the base rollup ones const rootInput = await this.getRootRollupInput( baseRollupOutputLeft, baseRollupProofLeft, @@ -130,8 +137,13 @@ export class CircuitPoweredBlockBuilder { baseRollupProofRight, ); + // Simulate and get proof for the root circuit const rootOutput = await this.simulator.rootRollupCircuit(rootInput); 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 + await this.updateRootTrees(); await this.validateRootOutput(rootOutput); return [rootOutput, rootProof]; @@ -140,8 +152,8 @@ export class CircuitPoweredBlockBuilder { protected async baseRollupCircuit(tx1: Tx, tx2: Tx) { const rollupInput = await this.buildBaseRollupInput(tx1, tx2); const rollupOutput = await this.simulator.baseRollupCircuit(rollupInput); + console.log(`Ran base rollup circuit`); await this.validateTrees(rollupOutput); - await this.updateRootTrees(); return [rollupInput, rollupOutput] as const; } @@ -174,6 +186,7 @@ export class CircuitPoweredBlockBuilder { ]); } + // Helper for validating a roots tree against a circuit simulation output protected async validateRootTree( rootOutput: RootRollupPublicInputs, treeId: MerkleTreeId, @@ -181,9 +194,12 @@ export class CircuitPoweredBlockBuilder { ) { const localTree = await this.getTreeSnapshot(treeId); const simulatedTree = rootOutput[`endTreeOfHistoric${name}TreeRootsSnapshot`]; - this.validateSimulatedTree(localTree, simulatedTree, name); + console.log(`Validating root tree ${name}`); + await inspectTree(this.db, treeId); + this.validateSimulatedTree(localTree, simulatedTree, name, `Roots ${name}`); } + // Helper for validating a non-roots tree against a circuit simulation output protected async validateTree( output: BaseRollupPublicInputs | RootRollupPublicInputs, treeId: MerkleTreeId, @@ -194,21 +210,26 @@ export class CircuitPoweredBlockBuilder { this.validateSimulatedTree(localTree, simulatedTree, name); } + // Helper for comparing two trees snapshots protected validateSimulatedTree( localTree: AppendOnlyTreeSnapshot, simulatedTree: AppendOnlyTreeSnapshot, name: string, + label?: string, ) { if (!simulatedTree.root.toBuffer().equals(localTree.root.toBuffer())) { - throw new Error(`${name} tree root mismatch (local ${localTree.root}, simulated ${simulatedTree.root})`); + throw new Error(`${label ?? name} tree root mismatch (local ${localTree.root}, simulated ${simulatedTree.root})`); } if (simulatedTree.nextAvailableLeafIndex !== localTree.nextAvailableLeafIndex) { throw new Error( - `${name} tree next available leaf index mismatch (local ${localTree.nextAvailableLeafIndex}, simulated ${simulatedTree.nextAvailableLeafIndex})`, + `${label ?? name} tree next available leaf index mismatch (local ${ + localTree.nextAvailableLeafIndex + }, simulated ${simulatedTree.nextAvailableLeafIndex})`, ); } } + // Builds the inputs for the root rollup circuit, without making any changes to trees protected async getRootRollupInput( rollupOutputLeft: BaseRollupPublicInputs, rollupProofLeft: Proof, @@ -220,14 +241,24 @@ export class CircuitPoweredBlockBuilder { this.getPreviousRollupDataFromBaseRollup(rollupOutputRight, rollupProofRight), ]; - return new RootRollupInputs( - previousRollupData, - await this.getTreeSnapshot(MerkleTreeId.DATA_TREE), - await this.getTreeSnapshot(MerkleTreeId.CONTRACT_TREE), - TODO_ANY, - TODO_ANY, - TODO_ANY, + const getRootTreeSiblingPath = async (treeId: MerkleTreeId) => { + // TODO: Synchronize these operations into the tree db to avoid race conditions + const { size } = await this.db.getTreeInfo(treeId); + // TODO: Check for off-by-one errors + const path = await this.db.getSiblingPath(treeId, size); + return path.data.map(b => Fr.fromBuffer(b)); + }; + + const newHistoricContractDataTreeRootSiblingPath = await getRootTreeSiblingPath( + MerkleTreeId.CONTRACT_TREE_ROOTS_TREE, ); + const newHistoricPrivateDataTreeRootSiblingPath = await getRootTreeSiblingPath(MerkleTreeId.DATA_TREE_ROOTS_TREE); + + return RootRollupInputs.from({ + previousRollupData, + newHistoricContractDataTreeRootSiblingPath, + newHistoricPrivateDataTreeRootSiblingPath, + }); } protected getPreviousRollupDataFromBaseRollup(rollupOutput: BaseRollupPublicInputs, rollupProof: Proof) { @@ -267,7 +298,7 @@ export class CircuitPoweredBlockBuilder { const index = await this.db.findLeafIndex(treeId, value.toBuffer()); if (index === undefined) { - await this.inspectTree(treeId); + await inspectTree(this.db, treeId); throw new Error(`Leaf with value ${value} not found in tree ${treeId}`); } const path = await this.db.getSiblingPath(treeId, index); @@ -279,6 +310,22 @@ export class CircuitPoweredBlockBuilder { ); } + protected getContractMembershipWitnessFor(tx: Tx) { + return this.getMembershipWitnessFor( + tx.data.constants.oldTreeRoots.contractTreeRoot, + MerkleTreeId.CONTRACT_TREE_ROOTS_TREE, + CONTRACT_TREE_ROOTS_TREE_HEIGHT, + ); + } + + protected getDataMembershipWitnessFor(tx: Tx) { + return this.getMembershipWitnessFor( + tx.data.constants.oldTreeRoots.privateDataTreeRoot, + MerkleTreeId.DATA_TREE_ROOTS_TREE, + PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT, + ); + } + protected async getConstantBaseRollupData(): Promise { return ConstantBaseRollupData.from({ baseRollupVkHash: DELETE_FR, @@ -331,6 +378,7 @@ export class CircuitPoweredBlockBuilder { return fullSiblingPath.data.slice(subtreeHeight).map(b => Fr.fromBuffer(b)); } + // Builds the base rollup inputs, updating the contract, nullifier, and data trees in the process protected async buildBaseRollupInput(tx1: Tx, tx2: Tx) { // Get trees info before any changes hit const constants = await this.getConstantBaseRollupData(); @@ -340,16 +388,12 @@ export class CircuitPoweredBlockBuilder { // Update the contract and data trees with the new items being inserted to get the new roots // that will be used by the next iteration of the base rollup circuit - const newContracts = [...tx1.data.end.newContracts, ...tx2.data.end.newContracts]; - const newCommitments = [...tx1.data.end.newCommitments, ...tx2.data.end.newCommitments]; - await this.db.appendLeaves( - MerkleTreeId.CONTRACT_TREE, - newContracts.map(fr => fr.toBuffer()), - ); - await this.db.appendLeaves( - MerkleTreeId.DATA_TREE, - newCommitments.map(fr => fr.toBuffer()), + const newContracts = flatMap([tx1, tx2], tx => + tx.data.end.newContracts.map(cd => hashNewContractData(this.wasm, cd)), ); + const newCommitments = flatMap([tx1, tx2], tx => tx.data.end.newCommitments.map(x => x.toBuffer())); + await this.db.appendLeaves(MerkleTreeId.CONTRACT_TREE, newContracts); + await this.db.appendLeaves(MerkleTreeId.DATA_TREE, newCommitments); // Update the nullifier tree, capturing the low nullifier info for each individual operation const newNullifiers = [...tx1.data.end.newNullifiers, ...tx2.data.end.newNullifiers]; @@ -359,22 +403,6 @@ export class CircuitPoweredBlockBuilder { await this.db.appendLeaves(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]); } - // Calculates membership witness for a contract tree root in the contract tree roots tree - const getContractMembershipWitnessFor = (tx: Tx) => - this.getMembershipWitnessFor( - tx.data.constants.oldTreeRoots.contractTreeRoot, - MerkleTreeId.CONTRACT_TREE_ROOTS_TREE, - CONTRACT_TREE_ROOTS_TREE_HEIGHT, - ); - - // Same but for data tree - const getDataMembershipWitnessFor = (tx: Tx) => - this.getMembershipWitnessFor( - tx.data.constants.oldTreeRoots.privateDataTreeRoot, - MerkleTreeId.DATA_TREE_ROOTS_TREE, - PRIVATE_DATA_TREE_ROOTS_TREE_HEIGHT, - ); - // Get the subtree sibling paths for the circuit const newCommitmentsSubtreeSiblingPath = await this.getSubtreeSiblingPath( MerkleTreeId.DATA_TREE, @@ -401,27 +429,16 @@ export class CircuitPoweredBlockBuilder { lowNullifierMembershipWitness: lowNullifierInfos.map(i => i.witness), kernelData: [this.getKernelDataFor(tx1), this.getKernelDataFor(tx2)], historicContractsTreeRootMembershipWitnesses: [ - await getContractMembershipWitnessFor(tx1), - await getContractMembershipWitnessFor(tx2), + await this.getContractMembershipWitnessFor(tx1), + await this.getContractMembershipWitnessFor(tx2), ], historicPrivateDataTreeRootMembershipWitnesses: [ - await getDataMembershipWitnessFor(tx1), - await getDataMembershipWitnessFor(tx2), + await this.getDataMembershipWitnessFor(tx1), + await this.getDataMembershipWitnessFor(tx2), ], } as BaseRollupInputs); } - // For debugging purposes (maybe move it to merkle tree db?) - protected async inspectTree(treeId: MerkleTreeId) { - for (let i = 0; i < (await this.db.getTreeInfo(treeId).then(t => t.size)); i++) { - console.log( - `Tree ${treeId} leaf ${i}: ${await this.db - .getLeafValue(treeId, BigInt(i)) - .then(x => x?.toString('hex') ?? '[undefined]')}`, - ); - } - } - protected makeEmptyMembershipWitness(height: N) { return new MembershipWitness( height, From 871d09e3e2e00578c63b6eca4168558277c8e8c9 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 14:32:51 +0000 Subject: [PATCH 19/26] Block builder test passing --- .../circuit_powered_block_builder.test.ts | 11 ----------- .../sequencer/circuit_powered_block_builder.ts | 16 ++++++++++------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts index 55ebbaa38917..ac7333197dbe 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.test.ts @@ -84,7 +84,6 @@ describe('sequencer/circuit_block_builder', () => { [MerkleTreeId.CONTRACT_TREE, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE], ] as const) { if (rootTree === MerkleTreeId.CONTRACT_TREE_ROOTS_TREE) { - console.log(`About to update tree ${rootTree}`); await inspectTree(expectsDb, rootTree); } const newTreeInfo = await expectsDb.getTreeInfo(newTree); @@ -101,14 +100,6 @@ describe('sequencer/circuit_block_builder', () => { ] as const) { await expectsDb.appendLeaves(tree, leaves); } - - for (const [newTree, rootTree] of [ - [MerkleTreeId.DATA_TREE, MerkleTreeId.DATA_TREE_ROOTS_TREE], - [MerkleTreeId.CONTRACT_TREE, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE], - ] as const) { - const newTreeInfo = await expectsDb.getTreeInfo(newTree); - await expectsDb.appendLeaves(rootTree, [newTreeInfo.root]); - } }; const getTreeSnapshot = async (tree: MerkleTreeId) => { @@ -148,8 +139,6 @@ describe('sequencer/circuit_block_builder', () => { // And update the root trees now to create proper output to the root rollup circuit await updateRootTrees(); - console.log(`Expected tree`); - await inspectTree(expectsDb, MerkleTreeId.CONTRACT_TREE_ROOTS_TREE); rootRollupOutput.endContractTreeSnapshot = await getTreeSnapshot(MerkleTreeId.CONTRACT_TREE); rootRollupOutput.endNullifierTreeSnapshot = await getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE); rootRollupOutput.endPrivateDataTreeSnapshot = await getTreeSnapshot(MerkleTreeId.DATA_TREE); diff --git a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts index ed02aa4b59ff..170a5851991c 100644 --- a/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts +++ b/yarn-project/sequencer-client/src/sequencer/circuit_powered_block_builder.ts @@ -1,3 +1,4 @@ +import { createDebugLogger } from '@aztec/foundation'; import { ContractData, L2Block } from '@aztec/archiver'; import { BarretenbergWasm } from '@aztec/barretenberg.js/wasm'; import { @@ -20,7 +21,7 @@ import { } from '@aztec/circuits.js'; import { toBigIntBE } from '@aztec/foundation'; import { Tx } from '@aztec/tx'; -import { inspectTree, MerkleTreeDb, MerkleTreeId } from '@aztec/world-state'; +import { MerkleTreeDb, MerkleTreeId } from '@aztec/world-state'; import flatMap from 'lodash.flatmap'; import times from 'lodash.times'; import { hashNewContractData, makeEmptyTx } from '../deps/tx.js'; @@ -39,7 +40,6 @@ const FUTURE_NUM = 0; // Denotes fields that should be deleted const DELETE_FR = new Fr(0n); const DELETE_ANY: any = {}; -const TODO_ANY: any = {}; export class CircuitPoweredBlockBuilder { constructor( @@ -49,6 +49,7 @@ export class CircuitPoweredBlockBuilder { protected simulator: Simulator, protected prover: Prover, protected wasm: BarretenbergWasm, + protected debug = createDebugLogger('aztec:sequencer'), ) {} public async buildL2Block(tx: Tx): Promise<[L2Block, UInt8Vector]> { @@ -120,16 +121,20 @@ export class CircuitPoweredBlockBuilder { const [tx1, tx2, tx3, tx4] = txs; // Simulate both base rollup circuits, updating the data, contract, and nullifier trees in the process + this.debug(`Running left base rollup simulator`); const [baseRollupInputLeft, baseRollupOutputLeft] = await this.baseRollupCircuit(tx1, tx2); + this.debug(`Running right base rollup simulator`); const [baseRollupInputRight, baseRollupOutputRight] = await this.baseRollupCircuit(tx3, tx4); // Get the proofs for them in parallel (faked for now) + this.debug(`Running base rollup circuit provers`); const [baseRollupProofLeft, baseRollupProofRight] = await Promise.all([ this.prover.getBaseRollupProof(baseRollupInputLeft, baseRollupOutputLeft), this.prover.getBaseRollupProof(baseRollupInputRight, baseRollupOutputRight), ]); // Get the input for the root rollup circuit based on the base rollup ones + this.debug(`Producing root rollup inputs`); const rootInput = await this.getRootRollupInput( baseRollupOutputLeft, baseRollupProofLeft, @@ -138,11 +143,14 @@ export class CircuitPoweredBlockBuilder { ); // Simulate and get proof for the root circuit + this.debug(`Running root rollup simulator`); const rootOutput = await this.simulator.rootRollupCircuit(rootInput); + this.debug(`Running root rollup circuit prover`); 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(); await this.validateRootOutput(rootOutput); @@ -152,7 +160,6 @@ export class CircuitPoweredBlockBuilder { protected async baseRollupCircuit(tx1: Tx, tx2: Tx) { const rollupInput = await this.buildBaseRollupInput(tx1, tx2); const rollupOutput = await this.simulator.baseRollupCircuit(rollupInput); - console.log(`Ran base rollup circuit`); await this.validateTrees(rollupOutput); return [rollupInput, rollupOutput] as const; } @@ -194,8 +201,6 @@ export class CircuitPoweredBlockBuilder { ) { const localTree = await this.getTreeSnapshot(treeId); const simulatedTree = rootOutput[`endTreeOfHistoric${name}TreeRootsSnapshot`]; - console.log(`Validating root tree ${name}`); - await inspectTree(this.db, treeId); this.validateSimulatedTree(localTree, simulatedTree, name, `Roots ${name}`); } @@ -298,7 +303,6 @@ export class CircuitPoweredBlockBuilder { const index = await this.db.findLeafIndex(treeId, value.toBuffer()); if (index === undefined) { - await inspectTree(this.db, treeId); throw new Error(`Leaf with value ${value} not found in tree ${treeId}`); } const path = await this.db.getSiblingPath(treeId, index); From 3db619e78bd7b5761eacaaa5241da4c1980a4308 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 15:34:40 +0000 Subject: [PATCH 20/26] Pick toBuffer32 is available in serialization (fixes ethAddress) --- yarn-project/circuits.js/src/structs/call_context.ts | 2 +- yarn-project/circuits.js/src/utils/serialize.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/yarn-project/circuits.js/src/structs/call_context.ts b/yarn-project/circuits.js/src/structs/call_context.ts index 16676997fd42..d4fd5045c6cf 100644 --- a/yarn-project/circuits.js/src/structs/call_context.ts +++ b/yarn-project/circuits.js/src/structs/call_context.ts @@ -23,7 +23,7 @@ export class CallContext { return serializeToBuffer( this.msgSender, this.storageContractAddress, - this.portalContractAddress.toBuffer(), + this.portalContractAddress, this.isDelegateCall, this.isStaticCall, this.isContractDeployment, diff --git a/yarn-project/circuits.js/src/utils/serialize.ts b/yarn-project/circuits.js/src/utils/serialize.ts index 0b7a5e960787..b9fe432952be 100644 --- a/yarn-project/circuits.js/src/utils/serialize.ts +++ b/yarn-project/circuits.js/src/utils/serialize.ts @@ -105,6 +105,12 @@ export type Bufferable = | Buffer | number | string + | { + /** + * Serialize to a buffer of 32 bytes. + */ + toBuffer32: () => Buffer; + } | { /** * Serialize to a buffer. @@ -113,6 +119,10 @@ export type Bufferable = } | Bufferable[]; +function isSerializableToBuffer32(obj: Object): obj is { toBuffer32: () => Buffer } { + return !!(obj as { toBuffer32: () => Buffer }).toBuffer32; +} + /** * Serializes a list of objects contiguously for calling into wasm. * @param objs - Objects to serialize. @@ -134,6 +144,8 @@ export function serializeToBufferArray(...objs: Bufferable[]): Buffer[] { } else if (typeof obj === 'string') { ret.push(numToUInt32BE(obj.length)); ret.push(Buffer.from(obj)); + } else if (isSerializableToBuffer32(obj)) { + ret.push(obj.toBuffer32()) } else { ret.push(obj.toBuffer()); } From 4b7b8af02c8e3e9c2b4a95a5e1a4231b114e7c96 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Wed, 29 Mar 2023 15:37:14 +0000 Subject: [PATCH 21/26] Updated test --- .../circuits.js/src/structs/base_rollup.test.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/yarn-project/circuits.js/src/structs/base_rollup.test.ts b/yarn-project/circuits.js/src/structs/base_rollup.test.ts index 3366c6ab435c..e58c805f87f0 100644 --- a/yarn-project/circuits.js/src/structs/base_rollup.test.ts +++ b/yarn-project/circuits.js/src/structs/base_rollup.test.ts @@ -39,9 +39,18 @@ describe('structs/base_rollup', () => { MembershipWitness.mock(NULLIFIER_TREE_HEIGHT, x), ); - const newCommitmentsSubtreeSiblingPath = range(PRIVATE_DATA_TREE_HEIGHT, 0x3000).map(x => fr(x)); - const newNullifiersSubtreeSiblingPath = range(NULLIFIER_TREE_HEIGHT, 0x4000).map(x => fr(x)); - const newContractsSubtreeSiblingPath = range(CONTRACT_TREE_HEIGHT, 0x5000).map(x => fr(x)); + const newCommitmentsSubtreeSiblingPath = range( + PRIVATE_DATA_TREE_HEIGHT - BaseRollupInputs.PRIVATE_DATA_SUBTREE_HEIGHT, + 0x3000, + ).map(x => fr(x)); + const newNullifiersSubtreeSiblingPath = range( + NULLIFIER_TREE_HEIGHT - BaseRollupInputs.NULLIFIER_SUBTREE_HEIGHT, + 0x4000, + ).map(x => fr(x)); + const newContractsSubtreeSiblingPath = range( + CONTRACT_TREE_HEIGHT - BaseRollupInputs.CONTRACT_SUBTREE_HEIGHT, + 0x5000, + ).map(x => fr(x)); const historicPrivateDataTreeRootMembershipWitnesses: BaseRollupInputs['historicPrivateDataTreeRootMembershipWitnesses'] = [ From 3787e129f19a9c56315369a34e6645a853aa4238 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 15:48:30 +0000 Subject: [PATCH 22/26] Format --- yarn-project/circuits.js/src/utils/serialize.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn-project/circuits.js/src/utils/serialize.ts b/yarn-project/circuits.js/src/utils/serialize.ts index b9fe432952be..834f8c643955 100644 --- a/yarn-project/circuits.js/src/utils/serialize.ts +++ b/yarn-project/circuits.js/src/utils/serialize.ts @@ -106,11 +106,11 @@ export type Bufferable = | number | string | { - /** - * Serialize to a buffer of 32 bytes. - */ - toBuffer32: () => Buffer; - } + /** + * Serialize to a buffer of 32 bytes. + */ + toBuffer32: () => Buffer; + } | { /** * Serialize to a buffer. @@ -145,7 +145,7 @@ export function serializeToBufferArray(...objs: Bufferable[]): Buffer[] { ret.push(numToUInt32BE(obj.length)); ret.push(Buffer.from(obj)); } else if (isSerializableToBuffer32(obj)) { - ret.push(obj.toBuffer32()) + ret.push(obj.toBuffer32()); } else { ret.push(obj.toBuffer()); } From 51ab6559b59d69703ea89916d1fbfee519ebbb8f Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 15:48:38 +0000 Subject: [PATCH 23/26] Update base rollup inputs snapshot --- .../src/structs/__snapshots__/base_rollup.test.ts.snap | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/base_rollup.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/base_rollup.test.ts.snap index c6ea9571b44d..7f25306ddff8 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/base_rollup.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/base_rollup.test.ts.snap @@ -115,7 +115,7 @@ portal_contract_address: 0x707070707070707070707070707070707070707 is_private: 1 proof: [ 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ] -vk: 0x12ca00 +vk: 0x12c940 vk_index: 42 vk_path: [ 0x1000 0x1001 0x1002 ] public_inputs: end: @@ -231,7 +231,7 @@ portal_contract_address: 0x707070707070707070707070707070707070707 is_private: 1 proof: [ 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ] -vk: 0x135540 +vk: 0x135480 vk_index: 42 vk_path: [ 0x1000 0x1001 0x1002 ] ] @@ -284,11 +284,11 @@ sibling_path: [ 0x2006 0x2007 0x2008 0x2009 0x200a 0x200b 0x200c 0x200d ] sibling_path: [ 0x2007 0x2008 0x2009 0x200a 0x200b 0x200c 0x200d 0x200e ] ] new_commitments_subtree_sibling_path: -[ 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 0x3006 0x3007 ] +[ 0x3000 0x3001 0x3002 0x3003 0x3004 ] new_nullifiers_subtree_sibling_path: -[ 0x4000 0x4001 0x4002 0x4003 0x4004 0x4005 0x4006 0x4007 ] +[ 0x4000 0x4001 0x4002 0x4003 0x4004 ] new_contracts_subtree_sibling_path: -[ 0x5000 0x5001 0x5002 0x5003 ] +[ 0x5000 0x5001 0x5002 ] historic_private_data_tree_root_membership_witnesses: [ leaf_index: 6000 sibling_path: [ 0x6000 0x6001 0x6002 0x6003 0x6004 0x6005 0x6006 0x6007 ] From fea2fc6cf59d3b0575567b38a8c4290fb15bd00a Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 15:58:10 +0000 Subject: [PATCH 24/26] Update circuits to latest (includes fix for base rollup inputs) --- circuits | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuits b/circuits index 7679e98cb4e5..eea39b37d08b 160000 --- a/circuits +++ b/circuits @@ -1 +1 @@ -Subproject commit 7679e98cb4e5af7be6855fb39eb7f37e26ebe717 +Subproject commit eea39b37d08b21337fa9d1f67d9ae7db403049e4 From 7f92e440376f04bcf7d32c6fd77d00c40d506410 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 16:27:26 +0000 Subject: [PATCH 25/26] Update circuits to latest --- circuits | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuits b/circuits index eea39b37d08b..eeb138136492 160000 --- a/circuits +++ b/circuits @@ -1 +1 @@ -Subproject commit eea39b37d08b21337fa9d1f67d9ae7db403049e4 +Subproject commit eeb13813649213a10514fc0045c9309bee7c90eb From 0d86b5914c93415c00cb7eb21105a25733ed8af7 Mon Sep 17 00:00:00 2001 From: spalladino Date: Wed, 29 Mar 2023 16:27:43 +0000 Subject: [PATCH 26/26] Fix aztec.js dockerfile build --- yarn-project/aztec.js/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/aztec.js/Dockerfile b/yarn-project/aztec.js/Dockerfile index 614f06b17991..2e4ade811397 100644 --- a/yarn-project/aztec.js/Dockerfile +++ b/yarn-project/aztec.js/Dockerfile @@ -1,6 +1,6 @@ FROM 278380418400.dkr.ecr.eu-west-2.amazonaws.com/yarn-project-base AS builder COPY . . -RUN cd aztec-rpc && yarn build +RUN cd aztec-js && yarn build WORKDIR /usr/src/yarn-project/aztec.js RUN yarn build && yarn formatting && yarn test