import type { BlockNumber } from '@aztec/foundation/branded-types'; import { SerialQueue } from '@aztec/foundation/queue'; import type { IndexedTreeLeafPreimage, SiblingPath } from '@aztec/foundation/trees'; import type { BatchInsertionResult, IndexedTreeId, MerkleTreeId, MerkleTreeLeafType, MerkleTreeWriteOperations, SequentialInsertionResult, TreeHeights, TreeInfo, } from '@aztec/stdlib/trees'; import type { BlockHeader, StateReference } from '@aztec/stdlib/tx'; import type { WorldStateRevision, WorldStateRevisionWithHandle } from '@aztec/stdlib/world-state'; /** * Wraps an instance of `MerkleTreeWriteOperations` to allow the sequencer to gate access. * If transactions execution goes past the deadline, the simulator will continue to execute and update the world state * The public processor however requires that the world state remain constant after the deadline in order to finalize the block * The public processor provides this implementation of MerkleTreeWriteOperations to the simulator */ export class GuardedMerkleTreeOperations implements MerkleTreeWriteOperations { private isStopped = false; private serialQueue = new SerialQueue(); constructor(private target: MerkleTreeWriteOperations) { this.serialQueue.start(); } private guard() { if (this.isStopped) { throw new Error('Merkle tree access has been stopped'); } } // Executes the provided function only if the guard is not stopped. private guardAndPush(fn: () => Promise): Promise { this.guard(); return this.serialQueue.put(() => { this.guard(); return fn(); }); } public getUnderlyingFork(): MerkleTreeWriteOperations { return this.target; } // Stops all further access to the merkle trees via this object async stop(): Promise { await this.serialQueue.put(() => { this.isStopped = true; return Promise.resolve(); }); return this.serialQueue.end(); } // Proxy all methods to the target appendLeaves(treeId: ID, leaves: MerkleTreeLeafType[]): Promise { return this.guardAndPush(() => this.target.appendLeaves(treeId, leaves)); } updateArchive(header: BlockHeader): Promise { return this.guardAndPush(() => this.target.updateArchive(header)); } batchInsert( treeId: ID, leaves: Buffer[], subtreeHeight: number, ): Promise> { return this.guardAndPush(() => this.target.batchInsert(treeId, leaves, subtreeHeight)); } sequentialInsert( treeId: ID, leaves: Buffer[], ): Promise> { return this.guardAndPush(() => this.target.sequentialInsert(treeId, leaves)); } close(): Promise { return this.guardAndPush(() => this.target.close()); } async [Symbol.asyncDispose](): Promise { await this.close(); } getTreeInfo(treeId: MerkleTreeId): Promise { return this.guardAndPush(() => this.target.getTreeInfo(treeId)); } getStateReference(): Promise { return this.guardAndPush(() => this.target.getStateReference()); } getInitialHeader(): BlockHeader { return this.target.getInitialHeader(); } public getRevision(): WorldStateRevision | WorldStateRevisionWithHandle { return this.target.getRevision(); } getSiblingPath(treeId: ID, index: bigint): Promise> { return this.guardAndPush(() => this.target.getSiblingPath(treeId, index)); } getPreviousValueIndex( treeId: ID, value: bigint, ): Promise<{ index: bigint; alreadyPresent: boolean } | undefined> { return this.guardAndPush(() => this.target.getPreviousValueIndex(treeId, value)); } getLeafPreimage(treeId: ID, index: bigint): Promise { return this.guardAndPush(() => this.target.getLeafPreimage(treeId, index)); } findLeafIndices( treeId: ID, values: MerkleTreeLeafType[], ): Promise<(bigint | undefined)[]> { return this.guardAndPush(() => this.target.findLeafIndices(treeId, values)); } findLeafIndicesAfter( treeId: ID, values: MerkleTreeLeafType[], startIndex: bigint, ): Promise<(bigint | undefined)[]> { return this.guardAndPush(() => this.target.findLeafIndicesAfter(treeId, values, startIndex)); } getLeafValue( treeId: ID, index: bigint, ): Promise | undefined> { return this.guardAndPush(() => this.target.getLeafValue(treeId, index)); } getBlockNumbersForLeafIndices( treeId: ID, leafIndices: bigint[], ): Promise<(BlockNumber | undefined)[]> { return this.guardAndPush(() => this.target.getBlockNumbersForLeafIndices(treeId, leafIndices)); } createCheckpoint(): Promise { return this.guardAndPush(() => this.target.createCheckpoint()); } commitCheckpoint(): Promise { return this.guardAndPush(() => this.target.commitCheckpoint()); } revertCheckpoint(): Promise { return this.guardAndPush(() => this.target.revertCheckpoint()); } commitAllCheckpointsTo(depth: number): Promise { return this.guardAndPush(() => this.target.commitAllCheckpointsTo(depth)); } revertAllCheckpointsTo(depth: number): Promise { return this.guardAndPush(() => this.target.revertAllCheckpointsTo(depth)); } findSiblingPaths( treeId: ID, values: MerkleTreeLeafType[], ): Promise<({ path: SiblingPath; index: bigint } | undefined)[]> { return this.guardAndPush(() => this.target.findSiblingPaths(treeId, values)); } }