diff --git a/yarn-project/simulator/src/avm/avm_simulator.ts b/yarn-project/simulator/src/avm/avm_simulator.ts index f29e62e14f47..686de00cfcb6 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.ts @@ -205,7 +205,7 @@ export class AvmSimulator { const endTotalTime = performance.now(); const totalTime = endTotalTime - startTotalTime; - this.log.debug(`Total execution time: ${totalTime}ms`); + this.log.debug(`Core AVM simulation took ${totalTime}ms`); // Return results for processing by calling context return results; diff --git a/yarn-project/simulator/src/avm/avm_tree.ts b/yarn-project/simulator/src/avm/avm_tree.ts index b971da3ee4ef..d1744acd68ad 100644 --- a/yarn-project/simulator/src/avm/avm_tree.ts +++ b/yarn-project/simulator/src/avm/avm_tree.ts @@ -7,6 +7,21 @@ import { type IndexedTreeLeafPreimage, type TreeLeafPreimage } from '@aztec/foun import { strict as assert } from 'assert'; import cloneDeep from 'lodash.clonedeep'; +const MAX_TREE_DEPTH = 128; + +/** + * Helper function to precompute zero hashes + */ +async function preComputeZeroHashes(): Promise { + let currentHash = Fr.zero(); + const zeroHashes: Fr[] = []; + for (let i = 0; i < MAX_TREE_DEPTH; i++) { + zeroHashes.push(currentHash); + currentHash = await poseidon2Hash([currentHash, currentHash]); + } + return zeroHashes; +} + /****************************************************/ /****** Structs Used by the AvmEphemeralForest ******/ /****************************************************/ @@ -554,6 +569,8 @@ export class EphemeralAvmTree { private tree: Tree; public frontier: Fr[]; + private static precomputedZeroHashes: Fr[] | undefined; + private constructor(private root: Leaf, public leafCount: bigint, public depth: number, private zeroHashes: Fr[]) { this.tree = root; this.frontier = []; @@ -565,13 +582,12 @@ export class EphemeralAvmTree { treeDb: MerkleTreeReadOperations, merkleId: MerkleTreeId, ): Promise { - let zeroHash = Fr.zero(); - // Can probably cache this elsewhere - const zeroHashes = []; - for (let i = 0; i < depth; i++) { - zeroHashes.push(zeroHash); - zeroHash = await poseidon2Hash([zeroHash, zeroHash]); + let zeroHashes = EphemeralAvmTree.precomputedZeroHashes; + if (zeroHashes === undefined) { + zeroHashes = await preComputeZeroHashes(); + EphemeralAvmTree.precomputedZeroHashes = zeroHashes; } + const zeroHash = zeroHashes[depth - 1]; const tree = new EphemeralAvmTree(Leaf(zeroHash), forkedLeafCount, depth, zeroHashes); await tree.initializeFrontier(treeDb, merkleId); return tree; diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 3dd20c52eb4b..c7ed7e7b7586 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -144,16 +144,16 @@ export class AvmPersistableStateManager { * @param value - the value being written to the slot */ public async writeStorage(contractAddress: AztecAddress, slot: Fr, value: Fr, protocolWrite = false): Promise { - this.log.debug(`Storage write (address=${contractAddress}, slot=${slot}): value=${value}`); + this.log.trace(`Storage write (address=${contractAddress}, slot=${slot}): value=${value}`); const leafSlot = await computePublicDataTreeLeafSlot(contractAddress, slot); - this.log.debug(`leafSlot=${leafSlot}`); + this.log.trace(`leafSlot=${leafSlot}`); // Cache storage writes for later reference/reads this.publicStorage.write(contractAddress, slot, value); if (this.doMerkleOperations) { const result = await this.merkleTrees.writePublicStorage(leafSlot, value); assert(result !== undefined, 'Public data tree insertion error. You might want to disable doMerkleOperations.'); - this.log.debug(`Inserted public data tree leaf at leafSlot ${leafSlot}, value: ${value}`); + this.log.trace(`Inserted public data tree leaf at leafSlot ${leafSlot}, value: ${value}`); const lowLeafInfo = result.lowWitness; const lowLeafPreimage = result.lowWitness.preimage as PublicDataTreeLeafPreimage; @@ -195,9 +195,9 @@ export class AvmPersistableStateManager { */ public async readStorage(contractAddress: AztecAddress, slot: Fr): Promise { const { value, cached } = await this.publicStorage.read(contractAddress, slot); - this.log.debug(`Storage read (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`); + this.log.trace(`Storage read (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`); const leafSlot = await computePublicDataTreeLeafSlot(contractAddress, slot); - this.log.debug(`leafSlot=${leafSlot}`); + this.log.trace(`leafSlot=${leafSlot}`); if (this.doMerkleOperations) { // Get leaf if present, low leaf if absent @@ -212,8 +212,8 @@ export class AvmPersistableStateManager { const leafPath = await this.merkleTrees.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex); const leafPreimage = preimage as PublicDataTreeLeafPreimage; - this.log.debug(`leafPreimage.slot: ${leafPreimage.slot}, leafPreimage.value: ${leafPreimage.value}`); - this.log.debug( + this.log.trace(`leafPreimage.slot: ${leafPreimage.slot}, leafPreimage.value: ${leafPreimage.value}`); + this.log.trace( `leafPreimage.nextSlot: ${leafPreimage.nextSlot}, leafPreimage.nextIndex: ${Number(leafPreimage.nextIndex)}`, ); @@ -223,7 +223,7 @@ export class AvmPersistableStateManager { `Value mismatch when performing public data read (got value: ${value}, value in ephemeral tree: ${leafPreimage.value})`, ); } else { - this.log.debug(`Slot has never been written before!`); + this.log.trace(`Slot has never been written before!`); // Sanity check that the leaf slot is skipped by low leaf when it doesn't exist assert( leafPreimage.slot.toBigInt() < leafSlot.toBigInt() && @@ -250,7 +250,7 @@ export class AvmPersistableStateManager { */ public async peekStorage(contractAddress: AztecAddress, slot: Fr): Promise { const { value, cached } = await this.publicStorage.read(contractAddress, slot); - this.log.debug(`Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`); + this.log.trace(`Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`); return Promise.resolve(value); } @@ -266,7 +266,7 @@ export class AvmPersistableStateManager { public async checkNoteHashExists(contractAddress: AztecAddress, noteHash: Fr, leafIndex: Fr): Promise { const gotLeafValue = (await this.worldStateDB.getCommitmentValue(leafIndex.toBigInt())) ?? Fr.ZERO; const exists = gotLeafValue.equals(noteHash); - this.log.debug( + this.log.trace( `noteHashes(${contractAddress})@${noteHash} ?? leafIndex: ${leafIndex} | gotLeafValue: ${gotLeafValue}, exists: ${exists}.`, ); if (this.doMerkleOperations) { @@ -306,7 +306,7 @@ export class AvmPersistableStateManager { * @param noteHash - the siloed unique hash to write */ public async writeUniqueNoteHash(noteHash: Fr): Promise { - this.log.debug(`noteHashes += @${noteHash}.`); + this.log.trace(`noteHashes += @${noteHash}.`); if (this.doMerkleOperations) { // Should write a helper for this @@ -325,7 +325,7 @@ export class AvmPersistableStateManager { * @returns exists - whether the nullifier exists in the nullifier set */ public async checkNullifierExists(contractAddress: AztecAddress, nullifier: Fr): Promise { - this.log.debug(`Checking existence of nullifier (address=${contractAddress}, nullifier=${nullifier})`); + this.log.trace(`Checking existence of nullifier (address=${contractAddress}, nullifier=${nullifier})`); const siloedNullifier = await siloNullifier(contractAddress, nullifier); const [exists, leafOrLowLeafPreimage, leafOrLowLeafIndex, leafOrLowLeafPath] = await this.getNullifierMembership( siloedNullifier, @@ -367,7 +367,7 @@ export class AvmPersistableStateManager { ] > { const [exists, isPending, _] = await this.nullifiers.checkExists(siloedNullifier); - this.log.debug(`Checked siloed nullifier ${siloedNullifier} (exists=${exists}), pending=${isPending}`); + this.log.trace(`Checked siloed nullifier ${siloedNullifier} (exists=${exists}), pending=${isPending}`); if (this.doMerkleOperations) { // Get leaf if present, low leaf if absent @@ -386,7 +386,7 @@ export class AvmPersistableStateManager { ); if (exists) { - this.log.debug(`Siloed nullifier ${siloedNullifier} exists at leafIndex=${leafIndex}`); + this.log.trace(`Siloed nullifier ${siloedNullifier} exists at leafIndex=${leafIndex}`); } else { // Sanity check that the leaf value is skipped by low leaf when it doesn't exist assert( @@ -407,7 +407,7 @@ export class AvmPersistableStateManager { * @param nullifier - the unsiloed nullifier to write */ public async writeNullifier(contractAddress: AztecAddress, nullifier: Fr) { - this.log.debug(`Inserting new nullifier (address=${nullifier}, nullifier=${contractAddress})`); + this.log.trace(`Inserting new nullifier (address=${nullifier}, nullifier=${contractAddress})`); const siloedNullifier = await siloNullifier(contractAddress, nullifier); await this.writeSiloedNullifier(siloedNullifier); } @@ -417,7 +417,7 @@ export class AvmPersistableStateManager { * @param siloedNullifier - the siloed nullifier to write */ public async writeSiloedNullifier(siloedNullifier: Fr) { - this.log.debug(`Inserting siloed nullifier=${siloedNullifier}`); + this.log.trace(`Inserting siloed nullifier=${siloedNullifier}`); if (this.doMerkleOperations) { // Maybe overkill, but we should check if the nullifier is already present in the tree before attempting to insert @@ -447,13 +447,13 @@ export class AvmPersistableStateManager { // Cache pending nullifiers for later access await this.nullifiers.append(siloedNullifier); // We append the new nullifier - this.log.debug( + this.log.trace( `Nullifier tree root before insertion ${await this.merkleTrees.treeMap .get(MerkleTreeId.NULLIFIER_TREE)! .getRoot()}`, ); const appendResult = await this.merkleTrees.appendNullifier(siloedNullifier); - this.log.debug( + this.log.trace( `Nullifier tree root after insertion ${await this.merkleTrees.treeMap .get(MerkleTreeId.NULLIFIER_TREE)! .getRoot()}`, @@ -496,7 +496,7 @@ export class AvmPersistableStateManager { ): Promise { const valueAtIndex = (await this.worldStateDB.getL1ToL2LeafValue(msgLeafIndex.toBigInt())) ?? Fr.ZERO; const exists = valueAtIndex.equals(msgHash); - this.log.debug( + this.log.trace( `l1ToL2Messages(@${msgLeafIndex}) ?? exists: ${exists}, expected: ${msgHash}, found: ${valueAtIndex}.`, ); @@ -522,7 +522,7 @@ export class AvmPersistableStateManager { * @param content - Message content. */ public writeL2ToL1Message(contractAddress: AztecAddress, recipient: Fr, content: Fr) { - this.log.debug(`L2ToL1Messages(${contractAddress}) += (recipient: ${recipient}, content: ${content}).`); + this.log.trace(`L2ToL1Messages(${contractAddress}) += (recipient: ${recipient}, content: ${content}).`); this.trace.traceNewL2ToL1Message(contractAddress, recipient, content); } @@ -532,7 +532,7 @@ export class AvmPersistableStateManager { * @param log - log contents */ public writePublicLog(contractAddress: AztecAddress, log: Fr[]) { - this.log.debug(`PublicLog(${contractAddress}) += event with ${log.length} fields.`); + this.log.trace(`PublicLog(${contractAddress}) += event with ${log.length} fields.`); this.trace.tracePublicLog(contractAddress, log); } @@ -542,7 +542,7 @@ export class AvmPersistableStateManager { * @returns the contract instance or undefined if it does not exist. */ public async getContractInstance(contractAddress: AztecAddress): Promise { - this.log.debug(`Getting contract instance for address ${contractAddress}`); + this.log.trace(`Getting contract instance for address ${contractAddress}`); const instanceWithAddress = await this.worldStateDB.getContractInstance(contractAddress); const exists = instanceWithAddress !== undefined; @@ -568,7 +568,7 @@ export class AvmPersistableStateManager { if (exists) { const instance = new SerializableContractInstance(instanceWithAddress); - this.log.debug( + this.log.trace( `Got contract instance (address=${contractAddress}): exists=${exists}, instance=${jsonStringify(instance)}`, ); if (this.doMerkleOperations) { diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index af2b5c76abdd..a6e01d5115ff 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -162,7 +162,7 @@ export class PublicTxContext { * NOTE: this does not "halt" the entire transaction execution. */ revert(phase: TxExecutionPhase, revertReason: SimulationError | undefined = undefined, culprit = '') { - this.log.debug(`${TxExecutionPhase[phase]} phase reverted! ${culprit} failed with reason: ${revertReason}`); + this.log.warn(`${TxExecutionPhase[phase]} phase reverted! ${culprit} failed with reason: ${revertReason}`); if (revertReason && !this.revertReason) { // don't override revertReason @@ -170,7 +170,7 @@ export class PublicTxContext { this.revertReason = revertReason; } if (phase === TxExecutionPhase.SETUP) { - this.log.debug(`Setup phase reverted! The transaction will be thrown out.`); + this.log.warn(`Setup phase reverted! The transaction will be thrown out.`); if (revertReason) { throw revertReason; } else { diff --git a/yarn-project/simulator/src/public/side_effect_trace.ts b/yarn-project/simulator/src/public/side_effect_trace.ts index 20ef38ec2111..bd067e240b3b 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.ts @@ -127,7 +127,6 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { /** We need to track the set of class IDs used for bytecode retrieval to deduplicate and enforce limits. */ private gotBytecodeFromClassIds: UniqueClassIds = new UniqueClassIds(), ) { - this.log.debug(`Creating trace instance with startSideEffectCounter: ${startSideEffectCounter}`); this.sideEffectCounter = startSideEffectCounter; this.avmCircuitHints = AvmExecutionHints.empty(); } @@ -215,7 +214,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { path: Fr[] = emptyPublicDataPath(), ) { this.avmCircuitHints.publicDataReads.items.push(new AvmPublicDataReadTreeHint(leafPreimage, leafIndex, path)); - this.log.debug( + this.log.trace( `Tracing storage read (address=${contractAddress}, slot=${slot}): value=${value} (counter=${this.sideEffectCounter})`, ); this.incrementSideEffectCounter(); @@ -265,7 +264,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { new AvmPublicDataWriteTreeHint(readHint, newLeafPreimage, insertionPath), ); - this.log.debug( + this.log.trace( `Traced public data write (address=${contractAddress}, slot=${slot}): value=${value} (counter=${this.sideEffectCounter}, isProtocol:${protocolWrite})`, ); this.incrementSideEffectCounter(); @@ -282,6 +281,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { // New Hinting this.avmCircuitHints.noteHashReads.items.push(new AvmAppendTreeHint(leafIndex, noteHash, path)); // NOTE: counter does not increment for note hash checks (because it doesn't rely on pending note hashes) + this.log.trace(`Tracing note hash check (counter=${this.sideEffectCounter})`); } public traceNewNoteHash(noteHash: Fr, leafIndex: Fr = Fr.zero(), path: Fr[] = emptyNoteHashPath()) { @@ -290,8 +290,8 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { } this.noteHashes.push(new NoteHash(noteHash, this.sideEffectCounter)); - this.log.debug(`NEW_NOTE_HASH cnt: ${this.sideEffectCounter}`); this.avmCircuitHints.noteHashWrites.items.push(new AvmAppendTreeHint(leafIndex, noteHash, path)); + this.log.trace(`Tracing new note hash (counter=${this.sideEffectCounter})`); this.incrementSideEffectCounter(); } @@ -305,7 +305,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { this.avmCircuitHints.nullifierReads.items.push( new AvmNullifierReadTreeHint(lowLeafPreimage, lowLeafIndex, lowLeafPath), ); - this.log.debug(`NULLIFIER_EXISTS cnt: ${this.sideEffectCounter}`); + this.log.trace(`Tracing nullifier check (counter=${this.sideEffectCounter})`); this.incrementSideEffectCounter(); } @@ -324,7 +324,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { const lowLeafReadHint = new AvmNullifierReadTreeHint(lowLeafPreimage, lowLeafIndex, lowLeafPath); this.avmCircuitHints.nullifierWrites.items.push(new AvmNullifierWriteTreeHint(lowLeafReadHint, insertionPath)); - this.log.debug(`NEW_NULLIFIER cnt: ${this.sideEffectCounter}`); + this.log.trace(`Tracing new nullifier (counter=${this.sideEffectCounter})`); this.incrementSideEffectCounter(); } @@ -337,6 +337,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { path: Fr[] = emptyL1ToL2MessagePath(), ) { this.avmCircuitHints.l1ToL2MessageReads.items.push(new AvmAppendTreeHint(msgLeafIndex, msgHash, path)); + this.log.trace(`Tracing l1 to l2 message check (counter=${this.sideEffectCounter})`); } public traceNewL2ToL1Message(contractAddress: AztecAddress, recipient: Fr, content: Fr) { @@ -348,7 +349,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { this.l2ToL1Messages.push( new L2ToL1Message(recipientAddress, content, this.sideEffectCounter).scope(contractAddress), ); - this.log.debug(`NEW_L2_TO_L1_MSG cnt: ${this.sideEffectCounter}`); + this.log.trace(`Tracing new l2 to l1 message (counter=${this.sideEffectCounter})`); this.incrementSideEffectCounter(); } @@ -362,7 +363,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { } const publicLog = new PublicLog(contractAddress, padArrayEnd(log, Fr.ZERO, PUBLIC_LOG_DATA_SIZE_IN_FIELDS)); this.publicLogs.push(publicLog); - this.log.debug(`NEW_PUBLIC_LOG cnt: ${this.sideEffectCounter}`); + this.log.trace(`Tracing new public log (counter=${this.sideEffectCounter})`); this.incrementSideEffectCounter(); } @@ -387,7 +388,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { membershipHint, ), ); - this.log.debug(`CONTRACT_INSTANCE cnt: ${this.sideEffectCounter}`); + this.log.trace(`Tracing contract instance retrieval (counter=${this.sideEffectCounter})`); this.incrementSideEffectCounter(); } @@ -432,7 +433,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { // Since the bytecode hints are keyed by class ID, we need to hint the instance separately // since there might be multiple instances hinted for the same class ID. this.avmCircuitHints.contractInstances.items.push(instance); - this.log.debug( + this.log.trace( `Tracing contract instance for bytecode retrieval: exists=${exists}, instance=${jsonStringify(contractInstance)}`, ); @@ -447,7 +448,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { // To do so, the circuit needs to know the class ID in the if (this.gotBytecodeFromClassIds.has(contractInstance.contractClassId.toString())) { // this ensures there are no duplicates - this.log.debug( + this.log.trace( `Contract class id ${contractInstance.contractClassId.toString()} already exists in previous hints`, ); return; @@ -470,7 +471,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { ); } - this.log.debug(`Tracing bytecode & contract class for bytecode retrieval: class=${jsonStringify(contractClass)}`); + this.log.trace(`Tracing bytecode & contract class for bytecode retrieval: class=${jsonStringify(contractClass)}`); this.avmCircuitHints.contractBytecodeHints.set( contractInstance.contractClassId.toString(), new AvmContractBytecodeHints(bytecode, instance, contractClass), @@ -492,7 +493,6 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { /** Did the call revert? */ _reverted: boolean, ) { - this.log.debug(`Tracing enqueued call`); // TODO(4805): check if some threshold is reached for max enqueued or nested calls (to unique contracts?) this.enqueuedCalls.push(publicCallRequest); this.avmCircuitHints.enqueuedCalls.items.push(new AvmEnqueuedCallHint(publicCallRequest.contractAddress, calldata));