diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index cf2d21ee20d0..004a41332463 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -26,14 +26,9 @@ import { TestContract } from '@aztec/noir-contracts.js/Test'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import type { SequencerClient } from '@aztec/sequencer-client'; import type { TestSequencerClient } from '@aztec/sequencer-client/test'; -import { - PublicContractsDB, - PublicProcessorFactory, - type PublicTreesDB, - type PublicTxResult, - TelemetryPublicTxSimulator, -} from '@aztec/simulator/server'; +import { type PublicContractsDB, PublicProcessorFactory, TelemetryPublicTxSimulator } from '@aztec/simulator/server'; import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client'; +import type { MerkleTreeWriteOperations } from '@aztec/stdlib/trees'; import { TX_ERROR_EXISTING_NULLIFIER, type Tx } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; @@ -631,19 +626,19 @@ async function sendAndWait(calls: ContractFunctionInteraction[]) { const TEST_PUBLIC_TX_SIMULATION_DELAY_MS = 300; class TestPublicTxSimulator extends TelemetryPublicTxSimulator { - public override async simulate(tx: Tx): Promise { + public override async simulate(tx: Tx) { await sleep(TEST_PUBLIC_TX_SIMULATION_DELAY_MS); return super.simulate(tx); } } class TestPublicProcessorFactory extends PublicProcessorFactory { protected override createPublicTxSimulator( - treesDB: PublicTreesDB, + merkleTree: MerkleTreeWriteOperations, contractsDB: PublicContractsDB, globalVariables: GlobalVariables, doMerkleOperations: boolean, skipFeeEnforcement: boolean, ) { - return new TestPublicTxSimulator(treesDB, contractsDB, globalVariables, doMerkleOperations, skipFeeEnforcement); + return new TestPublicTxSimulator(merkleTree, contractsDB, globalVariables, doMerkleOperations, skipFeeEnforcement); } } diff --git a/yarn-project/prover-client/src/mocks/test_context.ts b/yarn-project/prover-client/src/mocks/test_context.ts index 4b940f41ae89..e06337630cb7 100644 --- a/yarn-project/prover-client/src/mocks/test_context.ts +++ b/yarn-project/prover-client/src/mocks/test_context.ts @@ -7,11 +7,9 @@ import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; import { protocolContractTreeRoot } from '@aztec/protocol-contracts'; import { computeFeePayerBalanceLeafSlot } from '@aztec/protocol-contracts/fee-juice'; import { - PublicContractsDB, PublicProcessor, - PublicTreesDB, + PublicProcessorFactory, PublicTxSimulationTester, - PublicTxSimulator, SimpleContractDataSource, } from '@aztec/simulator/server'; import { PublicDataWrite } from '@aztec/stdlib/avm'; @@ -40,7 +38,6 @@ export class TestContext { private feePayerBalance: Fr; constructor( - public publicTxSimulator: PublicTxSimulator, public worldState: MerkleTreeAdminDatabase, public publicProcessor: PublicProcessor, public globalVariables: GlobalVariables, @@ -79,26 +76,17 @@ export class TestContext { // Separated dbs for public processor and prover - see public_processor for context const ws = await NativeWorldStateService.tmp( - undefined /* rollupAddress */, - true /* cleanupTmpDir */, + /*rollupAddress=*/ undefined, + /*cleanupTmpDir=*/ true, prefilledPublicData, ); const merkleTrees = await ws.fork(); const contractDataSource = new SimpleContractDataSource(); - const treesDB = new PublicTreesDB(merkleTrees); - const contractsDB = new PublicContractsDB(contractDataSource); - const tester = new PublicTxSimulationTester(merkleTrees, contractDataSource); - const publicTxSimulator = new PublicTxSimulator(treesDB, contractsDB, globalVariables, true); - const processor = new PublicProcessor( - globalVariables, - treesDB, - contractsDB, - publicTxSimulator, - new TestDateProvider(), - ); + const processorFactory = new PublicProcessorFactory(contractDataSource, new TestDateProvider()); + const processor = processorFactory.create(merkleTrees, globalVariables, /*skipFeeEnforcement=*/ false); let localProver: ServerCircuitProver; const config = await getEnvironmentConfig(logger); @@ -127,7 +115,6 @@ export class TestContext { facade.start(); return new this( - publicTxSimulator, ws, processor, globalVariables, diff --git a/yarn-project/simulator/src/public/avm/avm_simulator.test.ts b/yarn-project/simulator/src/public/avm/avm_simulator.test.ts index b5044f151dfe..a72038aefe27 100644 --- a/yarn-project/simulator/src/public/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/public/avm/avm_simulator.test.ts @@ -12,13 +12,11 @@ import { Fq, Fr, Point } from '@aztec/foundation/fields'; import type { Fieldable } from '@aztec/foundation/serialize'; import { CounterContract } from '@aztec/noir-contracts.js/Counter'; import { type FunctionArtifact, FunctionSelector } from '@aztec/stdlib/abi'; -import { PublicDataWrite } from '@aztec/stdlib/avm'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { SerializableContractInstance, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; import { GasFees } from '@aztec/stdlib/gas'; import { computeNoteHashNonce, - computePublicDataTreeLeafSlot, computeUniqueNoteHash, computeVarArgsHash, siloNoteHash, @@ -26,7 +24,6 @@ import { } from '@aztec/stdlib/hash'; import { PublicKeys } from '@aztec/stdlib/keys'; import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/stdlib/testing'; -import { MerkleTreeId } from '@aztec/stdlib/trees'; import { NativeWorldStateService } from '@aztec/world-state'; import { strict as assert } from 'assert'; @@ -35,6 +32,7 @@ import { mock } from 'jest-mock-extended'; import { SideEffectTrace } from '../../public/side_effect_trace.js'; import type { PublicSideEffectTraceInterface } from '../../public/side_effect_trace_interface.js'; +import { SimpleContractDataSource } from '../fixtures/simple_contract_data_source.js'; import { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js'; import type { PublicPersistableStateManager } from '../state_manager/state_manager.js'; import type { AvmContext } from './avm_context.js'; @@ -57,7 +55,6 @@ import { resolveAvmTestContractAssertionMessage, resolveContractAssertionMessage, } from './fixtures/index.js'; -import { SimpleContractDataSource } from './fixtures/simple_contract_data_source.js'; import { Add, CalldataCopy, @@ -1112,10 +1109,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { let trace: PublicSideEffectTraceInterface; let persistableState: PublicPersistableStateManager; - let leafSlot0: Fr; - beforeAll(async () => { - leafSlot0 = await computePublicDataTreeLeafSlot(address, slot0); siloedNullifier0 = await siloNullifier(address, value0); const siloedNoteHash0 = await siloNoteHash(address, value0); const nonce = await computeNoteHashNonce(firstNullifier, noteHashIndexInTx); @@ -1161,8 +1155,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(trace.traceNewNoteHash).toHaveBeenCalledWith(uniqueNoteHash0); }); it('Note hash check properly returns exists=false', async () => { - const treeInfo = await treesDB.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE); - const leafIndex = treeInfo.size; + const leafIndex = (await treesDB.getTreeSnapshots()).noteHashTree.nextAvailableLeafIndex; const calldata = [uniqueNoteHash0, new Fr(leafIndex)]; const context = createContext(calldata); @@ -1173,9 +1166,8 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.output).toEqual([/*exists=*/ Fr.ZERO]); }); it('Note hash check properly returns exists=true', async () => { - const treeInfo = await treesDB.getTreeInfo(MerkleTreeId.NOTE_HASH_TREE); - const leafIndex = treeInfo.size; - await treesDB.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash0]); + const leafIndex = (await treesDB.getTreeSnapshots()).noteHashTree.nextAvailableLeafIndex; + await treesDB.writeNoteHash(uniqueNoteHash0); const calldata = [uniqueNoteHash0, new Fr(leafIndex)]; const context = createContext(calldata); @@ -1210,7 +1202,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { }); it('Nullifier check properly returns exists=true', async () => { const calldata = [value0]; - await treesDB.sequentialInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier0.toBuffer()]); + await treesDB.writeNullifier(siloedNullifier0); const context = createContext(calldata); const bytecode = getAvmTestContractBytecode('nullifier_exists'); @@ -1245,8 +1237,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { it('Should read value in storage (single) - written before, leaf exists', async () => { const context = createContext(); - const publicDataWrite = new PublicDataWrite(leafSlot0, value0); - await treesDB.sequentialInsert(MerkleTreeId.PUBLIC_DATA_TREE, [publicDataWrite.toBuffer()]); + await treesDB.storageWrite(context.environment.address, slot0, value0); const bytecode = getAvmTestContractBytecode('read_storage_single'); diff --git a/yarn-project/simulator/src/public/avm/fixtures/avm_simulation_tester.ts b/yarn-project/simulator/src/public/avm/fixtures/avm_simulation_tester.ts index ea52a7da7e84..ba48342d990d 100644 --- a/yarn-project/simulator/src/public/avm/fixtures/avm_simulation_tester.ts +++ b/yarn-project/simulator/src/public/avm/fixtures/avm_simulation_tester.ts @@ -16,11 +16,11 @@ import { initExecutionEnvironment, resolveContractAssertionMessage, } from '../../avm/fixtures/index.js'; +import { SimpleContractDataSource } from '../../fixtures/simple_contract_data_source.js'; import { PublicContractsDB, PublicTreesDB } from '../../public_db_sources.js'; import { PublicPersistableStateManager } from '../../state_manager/state_manager.js'; import { AvmSimulator } from '../avm_simulator.js'; import { BaseAvmSimulationTester } from './base_avm_simulation_tester.js'; -import { SimpleContractDataSource } from './simple_contract_data_source.js'; const TIMESTAMP = new Fr(99833); const DEFAULT_GAS_FEES = new GasFees(2, 3); diff --git a/yarn-project/simulator/src/public/avm/fixtures/base_avm_simulation_tester.ts b/yarn-project/simulator/src/public/avm/fixtures/base_avm_simulation_tester.ts index 4f9b5046e4e1..5433e1166d1a 100644 --- a/yarn-project/simulator/src/public/avm/fixtures/base_avm_simulation_tester.ts +++ b/yarn-project/simulator/src/public/avm/fixtures/base_avm_simulation_tester.ts @@ -11,8 +11,8 @@ import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/stdlib/hash import type { MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server'; import { MerkleTreeId } from '@aztec/stdlib/trees'; +import type { SimpleContractDataSource } from '../../fixtures/simple_contract_data_source.js'; import { createContractClassAndInstance } from './index.js'; -import type { SimpleContractDataSource } from './simple_contract_data_source.js'; /** * An abstract test class that enables tests of real apps in the AVM without requiring e2e tests. diff --git a/yarn-project/simulator/src/public/avm/index.ts b/yarn-project/simulator/src/public/avm/index.ts index 62c6c0b6105c..ac112e51021b 100644 --- a/yarn-project/simulator/src/public/avm/index.ts +++ b/yarn-project/simulator/src/public/avm/index.ts @@ -1,2 +1,2 @@ export * from './avm_simulator.js'; -export * from './fixtures/simple_contract_data_source.js'; +export * from '../fixtures/simple_contract_data_source.js'; diff --git a/yarn-project/simulator/src/public/avm/opcodes/accrued_substate.ts b/yarn-project/simulator/src/public/avm/opcodes/accrued_substate.ts index 9502fcf830ad..14a26d758fea 100644 --- a/yarn-project/simulator/src/public/avm/opcodes/accrued_substate.ts +++ b/yarn-project/simulator/src/public/avm/opcodes/accrued_substate.ts @@ -181,11 +181,7 @@ export class L1ToL2MessageExists extends Instruction { const msgHash = memory.get(msgHashOffset).toFr(); const msgLeafIndex = memory.get(msgLeafIndexOffset).toFr(); - const exists = await context.persistableState.checkL1ToL2MessageExists( - context.environment.address, - msgHash, - msgLeafIndex, - ); + const exists = await context.persistableState.checkL1ToL2MessageExists(msgHash, msgLeafIndex); memory.set(existsOffset, exists ? new Uint1(1) : new Uint1(0)); } } diff --git a/yarn-project/simulator/src/common/debug_fn_name.ts b/yarn-project/simulator/src/public/debug_fn_name.ts similarity index 89% rename from yarn-project/simulator/src/common/debug_fn_name.ts rename to yarn-project/simulator/src/public/debug_fn_name.ts index 901440b3e2e0..8bc24788f97d 100644 --- a/yarn-project/simulator/src/common/debug_fn_name.ts +++ b/yarn-project/simulator/src/public/debug_fn_name.ts @@ -2,7 +2,7 @@ import type { Fr } from '@aztec/foundation/fields'; import { FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { PublicContractsDBInterface } from '../public/db_interfaces.js'; +import type { PublicContractsDBInterface } from './db_interfaces.js'; export async function getPublicFunctionDebugName( db: PublicContractsDBInterface, diff --git a/yarn-project/simulator/src/public/fixtures/index.ts b/yarn-project/simulator/src/public/fixtures/index.ts index 98fc8928e0c6..027e8673a154 100644 --- a/yarn-project/simulator/src/public/fixtures/index.ts +++ b/yarn-project/simulator/src/public/fixtures/index.ts @@ -1,2 +1,3 @@ export * from './public_tx_simulation_tester.js'; export * from './utils.js'; +export * from './simple_contract_data_source.js'; diff --git a/yarn-project/simulator/src/public/fixtures/public_tx_simulation_tester.ts b/yarn-project/simulator/src/public/fixtures/public_tx_simulation_tester.ts index ad6c8fea2ed5..2a0577b3a25f 100644 --- a/yarn-project/simulator/src/public/fixtures/public_tx_simulation_tester.ts +++ b/yarn-project/simulator/src/public/fixtures/public_tx_simulation_tester.ts @@ -10,11 +10,11 @@ import { NativeWorldStateService } from '@aztec/world-state'; import { BaseAvmSimulationTester } from '../avm/fixtures/base_avm_simulation_tester.js'; import { DEFAULT_BLOCK_NUMBER, getContractFunctionAbi, getFunctionSelector } from '../avm/fixtures/index.js'; -import { SimpleContractDataSource } from '../avm/fixtures/simple_contract_data_source.js'; -import { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js'; +import { PublicContractsDB } from '../public_db_sources.js'; import { MeasuredPublicTxSimulator } from '../public_tx_simulator/measured_public_tx_simulator.js'; import type { PublicTxResult } from '../public_tx_simulator/public_tx_simulator.js'; import { TestExecutorMetrics } from '../test_executor_metrics.js'; +import { SimpleContractDataSource } from './simple_contract_data_source.js'; import { createTxForPublicCalls } from './utils.js'; const TIMESTAMP = new Fr(99833); @@ -47,11 +47,9 @@ export class PublicTxSimulationTester extends BaseAvmSimulationTester { ) { super(contractDataSource, merkleTree); - const treesDB = new PublicTreesDB(merkleTree); const contractsDB = new PublicContractsDB(contractDataSource); - this.simulator = new MeasuredPublicTxSimulator( - treesDB, + merkleTree, contractsDB, globals, /*doMerkleOperations=*/ true, diff --git a/yarn-project/simulator/src/public/avm/fixtures/simple_contract_data_source.ts b/yarn-project/simulator/src/public/fixtures/simple_contract_data_source.ts similarity index 98% rename from yarn-project/simulator/src/public/avm/fixtures/simple_contract_data_source.ts rename to yarn-project/simulator/src/public/fixtures/simple_contract_data_source.ts index 7b371ef9ead8..032cc21126ba 100644 --- a/yarn-project/simulator/src/public/avm/fixtures/simple_contract_data_source.ts +++ b/yarn-project/simulator/src/public/fixtures/simple_contract_data_source.ts @@ -4,7 +4,7 @@ import type { ContractArtifact, FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract'; -import { getFunctionSelector } from './index.js'; +import { getFunctionSelector } from '../avm/fixtures/index.js'; /** * This class is used during public/avm testing to function as a database of diff --git a/yarn-project/simulator/src/public/hinting_db_sources.ts b/yarn-project/simulator/src/public/hinting_db_sources.ts index bab2827a9003..a2bf26568492 100644 --- a/yarn-project/simulator/src/public/hinting_db_sources.ts +++ b/yarn-project/simulator/src/public/hinting_db_sources.ts @@ -24,9 +24,11 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/stdlib/contract'; import { AppendOnlyTreeSnapshot, + type BatchInsertionResult, type IndexedTreeId, MerkleTreeId, type MerkleTreeLeafType, + type MerkleTreeWriteOperations, NullifierLeaf, NullifierLeafPreimage, PublicDataTreeLeaf, @@ -35,11 +37,11 @@ import { getTreeName, merkleTreeIds, } from '@aztec/stdlib/trees'; +import { TreeSnapshots } from '@aztec/stdlib/tx'; import { strict as assert } from 'assert'; import type { PublicContractsDBInterface } from './db_interfaces.js'; -import { PublicTreesDB } from './public_db_sources.js'; /** * A public contracts database that forwards requests and collects AVM hints. @@ -101,10 +103,10 @@ export class HintingPublicContractsDB implements PublicContractsDBInterface { } /** - * A public trees database that forwards requests and collects AVM hints. + * A low-level merkle DB that collects hints. */ -export class HintingPublicTreesDB extends PublicTreesDB { - private static readonly log: Logger = createLogger('HintingPublicTreesDB'); +export class HintingMerkleWriteOperations implements MerkleTreeWriteOperations { + private static readonly log: Logger = createLogger('simulator:hinting-merkle-db'); // This stack is only for debugging purposes. // The top of the stack is the current checkpoint id. // We need the stack to be non-empty and use 0 as an arbitrary initial checkpoint id. @@ -113,19 +115,31 @@ export class HintingPublicTreesDB extends PublicTreesDB { private nextCheckpointId: number = 1; private checkpointActionCounter: number = 0; // yes, a side-effect counter. - constructor(db: PublicTreesDB, private hints: AvmExecutionHints) { - super(db); + public static async create(db: MerkleTreeWriteOperations, hints: AvmExecutionHints) { + const hintingTreesDB = new HintingMerkleWriteOperations(db, hints); + const startStateReference = await db.getStateReference(); + hints.startingTreeRoots = new TreeSnapshots( + startStateReference.l1ToL2MessageTree, + startStateReference.partial.noteHashTree, + startStateReference.partial.nullifierTree, + startStateReference.partial.publicDataTree, + ); + + return hintingTreesDB; } + // Use create() to instantiate. + private constructor(private db: MerkleTreeWriteOperations, private hints: AvmExecutionHints) {} + // Getters. - public override async getSiblingPath(treeId: MerkleTreeId, index: bigint): Promise> { - const path = await super.getSiblingPath(treeId, index); + public async getSiblingPath(treeId: MerkleTreeId, index: bigint): Promise> { + const path = await this.db.getSiblingPath(treeId, index); const key = await this.getHintKey(treeId); this.hints.getSiblingPathHints.push(new AvmGetSiblingPathHint(key, treeId, index, path.toFields())); return Promise.resolve(path); } - public override async getPreviousValueIndex( + public async getPreviousValueIndex( treeId: ID, value: bigint, ): Promise< @@ -135,7 +149,7 @@ export class HintingPublicTreesDB extends PublicTreesDB { } | undefined > { - const result = await super.getPreviousValueIndex(treeId, value); + const result = await this.db.getPreviousValueIndex(treeId, value); if (result === undefined) { throw new Error( `getPreviousValueIndex(${getTreeName( @@ -150,11 +164,11 @@ export class HintingPublicTreesDB extends PublicTreesDB { return result; } - public override async getLeafPreimage( + public async getLeafPreimage( treeId: ID, index: bigint, ): Promise { - const preimage = await super.getLeafPreimage(treeId, index); + const preimage = await this.db.getLeafPreimage(treeId, index); if (preimage) { const key = await this.getHintKey(treeId); @@ -179,14 +193,14 @@ export class HintingPublicTreesDB extends PublicTreesDB { return preimage; } - public override async getLeafValue( + public async getLeafValue( treeId: ID, index: bigint, ): Promise | undefined> { // Use getLeafPreimage for PublicDataTree and NullifierTree. assert(treeId == MerkleTreeId.NOTE_HASH_TREE || treeId == MerkleTreeId.L1_TO_L2_MESSAGE_TREE); - const value = await super.getLeafValue(treeId, index); + const value = await this.db.getLeafValue(treeId, index); if (value) { const key = await this.getHintKey(treeId); // We can cast to Fr because we know the type of the tree. @@ -199,7 +213,7 @@ export class HintingPublicTreesDB extends PublicTreesDB { // State modification. // FIXME(fcarreiro): This is a horrible interface (in the merkle ops). It's receiving the leaves as buffers, // from a leaf class that is NOT the one that will be used to write. Make this type safe. - public override async sequentialInsert( + public async sequentialInsert( treeId: ID, leaves: Buffer[], ): Promise> { @@ -210,11 +224,10 @@ export class HintingPublicTreesDB extends PublicTreesDB { const beforeState = await this.getHintKey(treeId); - const result = await super.sequentialInsert(treeId, leaves); + const result = await this.db.sequentialInsert(treeId, leaves); const afterState = await this.getHintKey(treeId); - HintingPublicTreesDB.log.debug('[sequentialInsert] Evolved tree state.'); - HintingPublicTreesDB.logTreeChange(beforeState, afterState, treeId); + HintingMerkleWriteOperations.logTreeChange('sequentialInsert', beforeState, afterState, treeId); switch (treeId) { case MerkleTreeId.PUBLIC_DATA_TREE: @@ -265,10 +278,7 @@ export class HintingPublicTreesDB extends PublicTreesDB { return result; } - public override async appendLeaves( - treeId: ID, - leaves: MerkleTreeLeafType[], - ): Promise { + public async appendLeaves(treeId: ID, leaves: MerkleTreeLeafType[]): Promise { // Use sequentialInsert for PublicDataTree and NullifierTree. assert(treeId == MerkleTreeId.NOTE_HASH_TREE || treeId == MerkleTreeId.L1_TO_L2_MESSAGE_TREE); @@ -279,39 +289,39 @@ export class HintingPublicTreesDB extends PublicTreesDB { } } - public override async createCheckpoint(): Promise { + public async createCheckpoint(): Promise { const actionCounter = this.checkpointActionCounter++; const oldCheckpointId = this.getCurrentCheckpointId(); const treesStateHash = await this.getTreesStateHash(); - await super.createCheckpoint(); + await this.db.createCheckpoint(); this.checkpointStack.push(this.nextCheckpointId++); const newCheckpointId = this.getCurrentCheckpointId(); this.hints.createCheckpointHints.push(new AvmCreateCheckpointHint(actionCounter, oldCheckpointId, newCheckpointId)); - HintingPublicTreesDB.log.debug( + HintingMerkleWriteOperations.log.trace( `[createCheckpoint:${actionCounter}] Checkpoint evolved ${oldCheckpointId} -> ${newCheckpointId} at trees state ${treesStateHash}.`, ); } - public override async commitCheckpoint(): Promise { + public async commitCheckpoint(): Promise { const actionCounter = this.checkpointActionCounter++; const oldCheckpointId = this.getCurrentCheckpointId(); const treesStateHash = await this.getTreesStateHash(); - await super.commitCheckpoint(); + await this.db.commitCheckpoint(); this.checkpointStack.pop(); const newCheckpointId = this.getCurrentCheckpointId(); this.hints.commitCheckpointHints.push(new AvmCommitCheckpointHint(actionCounter, oldCheckpointId, newCheckpointId)); - HintingPublicTreesDB.log.debug( + HintingMerkleWriteOperations.log.trace( `[commitCheckpoint:${actionCounter}] Checkpoint evolved ${oldCheckpointId} -> ${newCheckpointId} at trees state ${treesStateHash}.`, ); } - public override async revertCheckpoint(): Promise { + public async revertCheckpoint(): Promise { const actionCounter = this.checkpointActionCounter++; const oldCheckpointId = this.getCurrentCheckpointId(); const treesStateHash = await this.getTreesStateHash(); @@ -324,7 +334,7 @@ export class HintingPublicTreesDB extends PublicTreesDB { [MerkleTreeId.ARCHIVE]: await this.getHintKey(MerkleTreeId.ARCHIVE), }; - await super.revertCheckpoint(); + await this.db.revertCheckpoint(); this.checkpointStack.pop(); const newCheckpointId = this.getCurrentCheckpointId(); @@ -340,17 +350,17 @@ export class HintingPublicTreesDB extends PublicTreesDB { AvmRevertCheckpointHint.create(actionCounter, oldCheckpointId, newCheckpointId, beforeState, afterState), ); - HintingPublicTreesDB.log.debug( + HintingMerkleWriteOperations.log.trace( `[revertCheckpoint:${actionCounter}] Checkpoint evolved ${oldCheckpointId} -> ${newCheckpointId} at trees state ${treesStateHash}.`, ); for (const treeId of merkleTreeIds()) { - HintingPublicTreesDB.logTreeChange(beforeState[treeId], afterState[treeId], treeId); + HintingMerkleWriteOperations.logTreeChange('revertCheckpoint', beforeState[treeId], afterState[treeId], treeId); } } // Private methods. private async getHintKey(treeId: MerkleTreeId): Promise { - const treeInfo = await super.getTreeInfo(treeId); + const treeInfo = await this.db.getTreeInfo(treeId); return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size)); } @@ -360,18 +370,19 @@ export class HintingPublicTreesDB extends PublicTreesDB { // For logging/debugging purposes. private async getTreesStateHash(): Promise { - const stateReferenceFields = (await super.getStateReference()).toFields(); + const stateReferenceFields = (await this.db.getStateReference()).toFields(); return Fr.fromBuffer(sha256Trunc(Buffer.concat(stateReferenceFields.map(field => field.toBuffer())))); } private static logTreeChange( + action: string, beforeState: AppendOnlyTreeSnapshot, afterState: AppendOnlyTreeSnapshot, treeId: MerkleTreeId, ) { const treeName = getTreeName(treeId); - HintingPublicTreesDB.log.debug( - `[${treeName}] Evolved tree state: ${beforeState.root}, ${beforeState.nextAvailableLeafIndex} -> ${afterState.root}, ${afterState.nextAvailableLeafIndex}.`, + HintingMerkleWriteOperations.log.trace( + `[${action}] ${treeName} tree state: ${beforeState.root}, ${beforeState.nextAvailableLeafIndex} -> ${afterState.root}, ${afterState.nextAvailableLeafIndex}.`, ); } @@ -384,15 +395,69 @@ export class HintingPublicTreesDB extends PublicTreesDB { const beforeState = await this.getHintKey(treeId); - await super.appendLeaves(treeId, [leaf]); + await this.db.appendLeaves(treeId, [leaf]); const afterState = await this.getHintKey(treeId); - HintingPublicTreesDB.log.debug('[appendLeaves] Evolved tree state.'); - HintingPublicTreesDB.logTreeChange(beforeState, afterState, treeId); + HintingMerkleWriteOperations.logTreeChange('appendLeaves', beforeState, afterState, treeId); this.hints.appendLeavesHints.push(new AvmAppendLeavesHint(beforeState, afterState, treeId, [leaf as Fr])); return await this.getSiblingPath(treeId, BigInt(beforeState.nextAvailableLeafIndex)); } + + // Non-hinted required methods from MerkleTreeWriteOperations interface + public async getTreeInfo(treeId: MerkleTreeId) { + return await this.db.getTreeInfo(treeId); + } + + public async getStateReference() { + return await this.db.getStateReference(); + } + + public getInitialHeader() { + return this.db.getInitialHeader(); + } + + public async updateArchive(header: any): Promise { + return await this.db.updateArchive(header); + } + + public async batchInsert< + TreeHeight extends number, + SubtreeSiblingPathHeight extends number, + ID extends IndexedTreeId, + >( + treeId: ID, + leaves: Buffer[], + subtreeHeight: number, + ): Promise> { + return await this.db.batchInsert(treeId, leaves, subtreeHeight); + } + + public async close(): Promise { + return await this.db.close(); + } + + public async findLeafIndices( + treeId: ID, + values: MerkleTreeLeafType[], + ): Promise<(bigint | undefined)[]> { + return await this.db.findLeafIndices(treeId, values); + } + + public async findLeafIndicesAfter( + treeId: ID, + values: MerkleTreeLeafType[], + startIndex: bigint, + ): Promise<(bigint | undefined)[]> { + return await this.db.findLeafIndicesAfter(treeId, values, startIndex); + } + + public async getBlockNumbersForLeafIndices( + treeId: ID, + leafIndices: bigint[], + ): Promise<(bigint | undefined)[]> { + return await this.db.getBlockNumbersForLeafIndices(treeId, leafIndices); + } } diff --git a/yarn-project/simulator/src/public/index.ts b/yarn-project/simulator/src/public/index.ts index 802b80f80720..6fb7f1151d9d 100644 --- a/yarn-project/simulator/src/public/index.ts +++ b/yarn-project/simulator/src/public/index.ts @@ -1,8 +1,6 @@ -export * from './db_interfaces.js'; -export * from './public_tx_simulator/index.js'; -export * from './public_db_sources.js'; +export { PublicContractsDB } from './public_db_sources.js'; +export { type PublicTxResult, PublicTxSimulator, TelemetryPublicTxSimulator } from './public_tx_simulator/index.js'; export { PublicProcessor, PublicProcessorFactory } from './public_processor/public_processor.js'; -export { SideEffectTrace } from './side_effect_trace.js'; export { PublicTxSimulationTester } from './fixtures/index.js'; -export * from './avm/index.js'; export { getCallRequestsWithCalldataByPhase } from './utils.js'; +export { SimpleContractDataSource } from './fixtures/simple_contract_data_source.js'; diff --git a/yarn-project/simulator/src/public/public_db_sources.ts b/yarn-project/simulator/src/public/public_db_sources.ts index 5b51e0ff97a7..9f54398529d1 100644 --- a/yarn-project/simulator/src/public/public_db_sources.ts +++ b/yarn-project/simulator/src/public/public_db_sources.ts @@ -2,7 +2,6 @@ import { NULLIFIER_SUBTREE_HEIGHT, PUBLIC_DATA_SUBTREE_HEIGHT } from '@aztec/con import { Fr } from '@aztec/foundation/fields'; import { createLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; -import type { IndexedTreeLeafPreimage, SiblingPath } from '@aztec/foundation/trees'; import { ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer'; import { ContractInstanceDeployedEvent } from '@aztec/protocol-contracts/instance-deployer'; import type { FunctionSelector } from '@aztec/stdlib/abi'; @@ -15,18 +14,17 @@ import { computePublicBytecodeCommitment, } from '@aztec/stdlib/contract'; import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash'; -import type { - BatchInsertionResult, - IndexedTreeId, - MerkleTreeLeafType, - MerkleTreeWriteOperations, - SequentialInsertionResult, - TreeInfo, -} from '@aztec/stdlib/interfaces/server'; +import type { MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server'; import { ContractClassLog, PrivateLog } from '@aztec/stdlib/logs'; import type { PublicDBAccessStats } from '@aztec/stdlib/stats'; -import { MerkleTreeId, NullifierLeaf, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees'; -import type { BlockHeader, StateReference, Tx } from '@aztec/stdlib/tx'; +import { + MerkleTreeId, + NullifierLeaf, + PublicDataTreeLeaf, + type PublicDataTreeLeafPreimage, + getTreeName, +} from '@aztec/stdlib/trees'; +import { TreeSnapshots, type Tx } from '@aztec/stdlib/tx'; import type { PublicContractsDBInterface, PublicStateDBInterface } from './db_interfaces.js'; import { TxContractCache } from './tx_contract_cache.js'; @@ -276,172 +274,69 @@ export class PublicContractsDB implements PublicContractsDBInterface { } /** - * Proxy class that forwards all merkle tree operations to the underlying object. + * A high-level class that provides access to the merkle trees. * - * NOTE: It might be possible to prune this to just the methods used in public. - * Then we'd need to define a new interface, instead of MerkleTreeWriteOperations, - * to be used by all our classes (that could be PublicStateDBInterface). - */ -class ForwardMerkleTree implements MerkleTreeWriteOperations { - constructor(private readonly operations: MerkleTreeWriteOperations) {} - - getTreeInfo(treeId: MerkleTreeId): Promise { - return this.operations.getTreeInfo(treeId); - } - - getStateReference(): Promise { - return this.operations.getStateReference(); - } - - getInitialHeader(): BlockHeader { - return this.operations.getInitialHeader(); - } - - getSiblingPath(treeId: MerkleTreeId, index: bigint): Promise> { - return this.operations.getSiblingPath(treeId, index); - } - - getPreviousValueIndex( - treeId: ID, - value: bigint, - ): Promise< - | { - index: bigint; - alreadyPresent: boolean; - } - | undefined - > { - return this.operations.getPreviousValueIndex(treeId, value); - } - - getLeafPreimage(treeId: ID, index: bigint): Promise { - return this.operations.getLeafPreimage(treeId, index); - } - - findLeafIndices( - treeId: ID, - values: MerkleTreeLeafType[], - ): Promise<(bigint | undefined)[]> { - return this.operations.findLeafIndices(treeId, values); - } - - findLeafIndicesAfter( - treeId: ID, - values: MerkleTreeLeafType[], - startIndex: bigint, - ): Promise<(bigint | undefined)[]> { - return this.operations.findLeafIndicesAfter(treeId, values, startIndex); - } - - getLeafValue( - treeId: ID, - index: bigint, - ): Promise | undefined> { - return this.operations.getLeafValue(treeId, index); - } - - getBlockNumbersForLeafIndices( - treeId: ID, - leafIndices: bigint[], - ): Promise<(bigint | undefined)[]> { - return this.operations.getBlockNumbersForLeafIndices(treeId, leafIndices); - } - - createCheckpoint(): Promise { - return this.operations.createCheckpoint(); - } - - commitCheckpoint(): Promise { - return this.operations.commitCheckpoint(); - } - - revertCheckpoint(): Promise { - return this.operations.revertCheckpoint(); - } - - appendLeaves(treeId: ID, leaves: MerkleTreeLeafType[]): Promise { - return this.operations.appendLeaves(treeId, leaves); - } - - updateArchive(header: BlockHeader): Promise { - return this.operations.updateArchive(header); - } - - batchInsert( - treeId: ID, - leaves: Buffer[], - subtreeHeight: number, - ): Promise> { - return this.operations.batchInsert(treeId, leaves, subtreeHeight); - } - - sequentialInsert( - treeId: ID, - leaves: Buffer[], - ): Promise> { - return this.operations.sequentialInsert(treeId, leaves); - } - - close(): Promise { - return this.operations.close(); - } -} - -/** - * A class that provides access to the merkle trees, and other helper methods. + * This class is just a helper wrapper around a merkle db. Anything that you can do with it + * can also be done directly with the merkle db. This class should NOT be exposed or used + * outside of `simulator/src/public`. + * + * NOTE: This class is currently written in such a way that it would generate the + * necessary hints if used with a hinting merkle db. This is a bit of a leak of concepts. + * Eventually we can have everything depend on a config/factory at the TxSimulator level + * to decide whether to use hints or not (same with tracing, etc). */ -export class PublicTreesDB extends ForwardMerkleTree implements PublicStateDBInterface { +export class PublicTreesDB implements PublicStateDBInterface { private logger = createLogger('simulator:public-trees-db'); - constructor(db: MerkleTreeWriteOperations) { - super(db); - } + constructor(private readonly db: MerkleTreeWriteOperations) {} - /** - * Reads a value from public storage, returning zero if none. - * @param contract - Owner of the storage. - * @param slot - Slot to read in the contract storage. - * @returns The current value in the storage slot. - */ public async storageRead(contract: AztecAddress, slot: Fr): Promise { + const timer = new Timer(); const leafSlot = (await computePublicDataTreeLeafSlot(contract, slot)).toBigInt(); - const lowLeafResult = await this.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot); + const lowLeafResult = await this.db.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot); if (!lowLeafResult) { throw new Error('Low leaf not found'); } - // TODO(fcarreiro): We need this for the hints. Might move it to the hinting layer. - await this.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index); + // TODO: We need this for the hints. See class comment for more details. + await this.db.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index); // Unconditionally fetching the preimage for the hints. Move it to the hinting layer? - const preimage = (await this.getLeafPreimage( + const preimage = (await this.db.getLeafPreimage( MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index, )) as PublicDataTreeLeafPreimage; - return lowLeafResult.alreadyPresent ? preimage.leaf.value : Fr.ZERO; + const result = lowLeafResult.alreadyPresent ? preimage.leaf.value : Fr.ZERO; + this.logger.debug(`Storage read (contract=${contract}, slot=${slot}, value=${result})`, { + eventName: 'public-db-access', + duration: timer.ms(), + operation: 'storage-read', + } satisfies PublicDBAccessStats); + + return result; } - /** - * Records a write to public storage. - * @param contract - Owner of the storage. - * @param slot - Slot to read in the contract storage. - * @param newValue - The new value to store. - * @returns The slot of the written leaf in the public data tree. - */ public async storageWrite(contract: AztecAddress, slot: Fr, newValue: Fr): Promise { + const timer = new Timer(); const leafSlot = await computePublicDataTreeLeafSlot(contract, slot); const publicDataWrite = new PublicDataWrite(leafSlot, newValue); - await this.sequentialInsert(MerkleTreeId.PUBLIC_DATA_TREE, [publicDataWrite.toBuffer()]); + await this.db.sequentialInsert(MerkleTreeId.PUBLIC_DATA_TREE, [publicDataWrite.toBuffer()]); + + this.logger.debug(`Storage write (contract=${contract}, slot=${slot}, value=${newValue})`, { + eventName: 'public-db-access', + duration: timer.ms(), + operation: 'storage-write', + } satisfies PublicDBAccessStats); } public async getL1ToL2LeafValue(leafIndex: bigint): Promise { const timer = new Timer(); - const leafValue = await this.getLeafValue(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, leafIndex); - // TODO(fcarreiro): We need this for the hints. Might move it to the hinting layer. - await this.getSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, leafIndex); + const leafValue = await this.db.getLeafValue(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, leafIndex); + // TODO: We need this for the hints. See class comment for more details. + await this.db.getSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, leafIndex); - this.logger.debug(`[DB] Fetched L1 to L2 message leaf value`, { + this.logger.debug(`Fetched L1 to L2 message leaf value (leafIndex=${leafIndex}, value=${leafValue})`, { eventName: 'public-db-access', duration: timer.ms(), operation: 'get-l1-to-l2-message-leaf-value', @@ -451,11 +346,11 @@ export class PublicTreesDB extends ForwardMerkleTree implements PublicStateDBInt public async getNoteHash(leafIndex: bigint): Promise { const timer = new Timer(); - const leafValue = await this.getLeafValue(MerkleTreeId.NOTE_HASH_TREE, leafIndex); - // TODO(fcarreiro): We need this for the hints. Might move it to the hinting layer. - await this.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex); + const leafValue = await this.db.getLeafValue(MerkleTreeId.NOTE_HASH_TREE, leafIndex); + // TODO: We need this for the hints. See class comment for more details. + await this.db.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex); - this.logger.debug(`[DB] Fetched note hash leaf value`, { + this.logger.debug(`Fetched note hash leaf value (leafIndex=${leafIndex}, value=${leafValue})`, { eventName: 'public-db-access', duration: timer.ms(), operation: 'get-note-hash', @@ -463,19 +358,30 @@ export class PublicTreesDB extends ForwardMerkleTree implements PublicStateDBInt return leafValue; } + public async writeNoteHash(noteHash: Fr): Promise { + const timer = new Timer(); + await this.db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [noteHash]); + + this.logger.debug(`Wrote note hash (noteHash=${noteHash})`, { + eventName: 'public-db-access', + duration: timer.ms(), + operation: 'write-note-hash', + } satisfies PublicDBAccessStats); + } + public async checkNullifierExists(nullifier: Fr): Promise { const timer = new Timer(); - const lowLeafResult = await this.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt()); + const lowLeafResult = await this.db.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt()); if (!lowLeafResult) { throw new Error('Low leaf not found'); } - // TODO(fcarreiro): We need this for the hints. Might move it to the hinting layer. - await this.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, lowLeafResult.index); - // TODO(fcarreiro): We need this for the hints. Might move it to the hinting layer. - await this.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, lowLeafResult.index); + // TODO: We need this for the hints. See class comment for more details. + await this.db.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, lowLeafResult.index); + // TODO: We need this for the hints. See class comment for more details. + await this.db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, lowLeafResult.index); const exists = lowLeafResult.alreadyPresent; - this.logger.debug(`[DB] Checked nullifier exists`, { + this.logger.debug(`Checked nullifier exists (nullifier=${nullifier}, exists=${exists})`, { eventName: 'public-db-access', duration: timer.ms(), operation: 'check-nullifier-exists', @@ -483,30 +389,71 @@ export class PublicTreesDB extends ForwardMerkleTree implements PublicStateDBInt return exists; } + public async writeNullifier(siloedNullifier: Fr): Promise { + const timer = new Timer(); + await this.db.sequentialInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()]); + + this.logger.debug(`Wrote nullifier (nullifier=${siloedNullifier})`, { + eventName: 'public-db-access', + duration: timer.ms(), + operation: 'write-nullifier', + } satisfies PublicDBAccessStats); + } + public async padTree(treeId: MerkleTreeId, leavesToInsert: number): Promise { + const timer = new Timer(); + switch (treeId) { // Indexed trees. case MerkleTreeId.NULLIFIER_TREE: - await this.batchInsert( + await this.db.batchInsert( treeId, Array(leavesToInsert).fill(NullifierLeaf.empty().toBuffer()), NULLIFIER_SUBTREE_HEIGHT, ); break; case MerkleTreeId.PUBLIC_DATA_TREE: - await this.batchInsert( + await this.db.batchInsert( treeId, Array(leavesToInsert).fill(PublicDataTreeLeaf.empty().toBuffer()), PUBLIC_DATA_SUBTREE_HEIGHT, ); break; - // Non-indexed trees. + // Append-only trees. case MerkleTreeId.L1_TO_L2_MESSAGE_TREE: case MerkleTreeId.NOTE_HASH_TREE: - await this.appendLeaves(treeId, Array(leavesToInsert).fill(Fr.ZERO)); + await this.db.appendLeaves(treeId, Array(leavesToInsert).fill(Fr.ZERO)); break; default: throw new Error(`Padding not supported for tree ${treeId}`); } + + this.logger.debug(`Padded tree (tree=${getTreeName(treeId)}, leavesToInsert=${leavesToInsert})`, { + eventName: 'public-db-access', + duration: timer.ms(), + operation: 'pad-tree', + } satisfies PublicDBAccessStats); + } + + public async createCheckpoint(): Promise { + await this.db.createCheckpoint(); + } + + public async commitCheckpoint(): Promise { + await this.db.commitCheckpoint(); + } + + public async revertCheckpoint(): Promise { + await this.db.revertCheckpoint(); + } + + public async getTreeSnapshots(): Promise { + const stateReference = await this.db.getStateReference(); + return new TreeSnapshots( + stateReference.l1ToL2MessageTree, + stateReference.partial.noteHashTree, + stateReference.partial.nullifierTree, + stateReference.partial.publicDataTree, + ); } } diff --git a/yarn-project/simulator/src/public/public_processor/apps_tests/deployments.test.ts b/yarn-project/simulator/src/public/public_processor/apps_tests/deployments.test.ts index c3e815d38b7e..aece9618ca23 100644 --- a/yarn-project/simulator/src/public/public_processor/apps_tests/deployments.test.ts +++ b/yarn-project/simulator/src/public/public_processor/apps_tests/deployments.test.ts @@ -9,13 +9,9 @@ import { GlobalVariables } from '@aztec/stdlib/tx'; import { getTelemetryClient } from '@aztec/telemetry-client'; import { NativeWorldStateService } from '@aztec/world-state'; -import { - PublicContractsDB, - PublicTreesDB, - PublicTxSimulationTester, - SimpleContractDataSource, -} from '../../../server.js'; +import { PublicContractsDB, PublicTxSimulationTester } from '../../../server.js'; import { createContractClassAndInstance } from '../../avm/fixtures/index.js'; +import { SimpleContractDataSource } from '../../fixtures/simple_contract_data_source.js'; import { addNewContractClassToTx, addNewContractInstanceToTx, createTxForPrivateOnly } from '../../fixtures/utils.js'; import { PublicTxSimulator } from '../../public_tx_simulator/public_tx_simulator.js'; import { PublicProcessor } from '../public_processor.js'; @@ -24,7 +20,6 @@ describe('Public processor contract registration/deployment tests', () => { const admin = AztecAddress.fromNumber(42); const sender = AztecAddress.fromNumber(111); - let treesDB: PublicTreesDB; let contractsDB: PublicContractsDB; let tester: PublicTxSimulationTester; let processor: PublicProcessor; @@ -36,13 +31,12 @@ describe('Public processor contract registration/deployment tests', () => { const contractDataSource = new SimpleContractDataSource(); const merkleTrees = await (await NativeWorldStateService.tmp()).fork(); - treesDB = new PublicTreesDB(merkleTrees); contractsDB = new PublicContractsDB(contractDataSource); - const simulator = new PublicTxSimulator(treesDB, contractsDB, globals, /*doMerkleOperations=*/ true); + const simulator = new PublicTxSimulator(merkleTrees, contractsDB, globals, /*doMerkleOperations=*/ true); processor = new PublicProcessor( globals, - treesDB, + merkleTrees, contractsDB, simulator, new TestDateProvider(), diff --git a/yarn-project/simulator/src/public/public_processor/apps_tests/token.test.ts b/yarn-project/simulator/src/public/public_processor/apps_tests/token.test.ts index 9587c4c262de..605c2a193cff 100644 --- a/yarn-project/simulator/src/public/public_processor/apps_tests/token.test.ts +++ b/yarn-project/simulator/src/public/public_processor/apps_tests/token.test.ts @@ -9,8 +9,9 @@ import { GlobalVariables } from '@aztec/stdlib/tx'; import { getTelemetryClient } from '@aztec/telemetry-client'; import { NativeWorldStateService } from '@aztec/world-state'; -import { PublicTxSimulationTester, SimpleContractDataSource } from '../../../server.js'; -import { PublicContractsDB, PublicTreesDB } from '../../public_db_sources.js'; +import { PublicTxSimulationTester } from '../../../server.js'; +import { SimpleContractDataSource } from '../../fixtures/simple_contract_data_source.js'; +import { PublicContractsDB } from '../../public_db_sources.js'; import { PublicTxSimulator } from '../../public_tx_simulator/public_tx_simulator.js'; import { PublicProcessor } from '../public_processor.js'; @@ -22,7 +23,6 @@ describe('Public Processor app tests: TokenContract', () => { const sender = AztecAddress.fromNumber(111); let token: ContractInstanceWithAddress; - let treesDB: PublicTreesDB; let contractsDB: PublicContractsDB; let tester: PublicTxSimulationTester; let processor: PublicProcessor; @@ -34,13 +34,12 @@ describe('Public Processor app tests: TokenContract', () => { const contractDataSource = new SimpleContractDataSource(); const merkleTrees = await (await NativeWorldStateService.tmp()).fork(); - treesDB = new PublicTreesDB(merkleTrees); contractsDB = new PublicContractsDB(contractDataSource); - const simulator = new PublicTxSimulator(treesDB, contractsDB, globals, /*doMerkleOperations=*/ true); + const simulator = new PublicTxSimulator(merkleTrees, contractsDB, globals, /*doMerkleOperations=*/ true); processor = new PublicProcessor( globals, - treesDB, + merkleTrees, contractsDB, simulator, new TestDateProvider(), diff --git a/yarn-project/simulator/src/public/public_processor/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor/public_processor.test.ts index 3ede06b72da0..5f79b782ca52 100644 --- a/yarn-project/simulator/src/public/public_processor/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor/public_processor.test.ts @@ -7,24 +7,23 @@ import { AvmCircuitInputs, PublicDataWrite, RevertCode } from '@aztec/stdlib/avm import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { SimulationError } from '@aztec/stdlib/errors'; import { Gas, GasFees } from '@aztec/stdlib/gas'; -import type { TreeInfo } from '@aztec/stdlib/interfaces/server'; import { ProvingRequestType } from '@aztec/stdlib/proofs'; import { mockTx } from '@aztec/stdlib/testing'; +import { type MerkleTreeWriteOperations, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees'; import { GlobalVariables, Tx, type TxValidator } from '@aztec/stdlib/tx'; import { getTelemetryClient } from '@aztec/telemetry-client'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js'; +import { PublicContractsDB } from '../public_db_sources.js'; import type { PublicTxResult, PublicTxSimulator } from '../public_tx_simulator/public_tx_simulator.js'; import { PublicProcessor } from './public_processor.js'; describe('public_processor', () => { - let treesDB: MockProxy; + let merkleTree: MockProxy; let contractsDB: MockProxy; let publicTxSimulator: MockProxy; - let root: Buffer; let mockedEnqueuedCallsResult: PublicTxResult; let processor: PublicProcessor; @@ -39,12 +38,10 @@ describe('public_processor', () => { mockTx(seed, { numberOfNonRevertiblePublicCallRequests: 1, numberOfRevertiblePublicCallRequests: 1, feePayer }); beforeEach(() => { - treesDB = mock(); + merkleTree = mock(); contractsDB = mock(); publicTxSimulator = mock(); - root = Buffer.alloc(32, 5); - const avmCircuitInputs = AvmCircuitInputs.empty(); mockedEnqueuedCallsResult = { avmProvingRequest: { @@ -61,8 +58,13 @@ describe('public_processor', () => { processedPhases: [], }; - treesDB.getTreeInfo.mockResolvedValue({ root } as TreeInfo); - treesDB.storageRead.mockResolvedValue(Fr.ZERO); + merkleTree.getPreviousValueIndex.mockResolvedValue({ + index: 0n, + alreadyPresent: true, + }); + merkleTree.getLeafPreimage.mockResolvedValue( + new PublicDataTreeLeafPreimage(new PublicDataTreeLeaf(Fr.ZERO, Fr.ZERO), /*nextKey=*/ Fr.ZERO, /*nextIndex=*/ 0n), + ); publicTxSimulator.simulate.mockImplementation(() => { return Promise.resolve(mockedEnqueuedCallsResult); @@ -70,7 +72,7 @@ describe('public_processor', () => { processor = new PublicProcessor( globalVariables, - treesDB, + merkleTree, contractsDB, publicTxSimulator, new TestDateProvider(), @@ -100,7 +102,7 @@ describe('public_processor', () => { expect(processed[0].data).toEqual(tx.data); expect(failed).toEqual([]); - expect(treesDB.commitCheckpoint).toHaveBeenCalledTimes(1); + expect(merkleTree.commitCheckpoint).toHaveBeenCalledTimes(1); }); it('runs a tx with reverted enqueued public calls', async function () { @@ -115,7 +117,7 @@ describe('public_processor', () => { expect(processed[0].hash).toEqual(await tx.getTxHash()); expect(failed).toEqual([]); - expect(treesDB.commitCheckpoint).toHaveBeenCalledTimes(1); + expect(merkleTree.commitCheckpoint).toHaveBeenCalledTimes(1); }); it('returns failed txs without aborting entire operation', async function () { @@ -129,8 +131,8 @@ describe('public_processor', () => { expect(failed[0].tx).toEqual(tx); expect(failed[0].error).toEqual(new SimulationError(`Failed`, [])); - expect(treesDB.commitCheckpoint).toHaveBeenCalledTimes(0); - expect(treesDB.revertCheckpoint).toHaveBeenCalledTimes(1); + expect(merkleTree.commitCheckpoint).toHaveBeenCalledTimes(0); + expect(merkleTree.revertCheckpoint).toHaveBeenCalledTimes(1); }); it('does not attempt to overfill a block', async function () { @@ -175,7 +177,7 @@ describe('public_processor', () => { expect(processed[0].hash).toEqual(await txs[0].getTxHash()); expect(processed[1].hash).toEqual(await txs[1].getTxHash()); expect(failed).toEqual([]); - expect(treesDB.commitCheckpoint).toHaveBeenCalledTimes(2); + expect(merkleTree.commitCheckpoint).toHaveBeenCalledTimes(2); }); }); @@ -183,8 +185,14 @@ describe('public_processor', () => { const feePayer = AztecAddress.fromBigInt(123123n); const initialBalance = new Fr(1000); - beforeEach(() => { - treesDB.storageRead.mockResolvedValue(initialBalance); + beforeEach(async () => { + merkleTree.getLeafPreimage.mockResolvedValue( + new PublicDataTreeLeafPreimage( + new PublicDataTreeLeaf(await computeFeePayerBalanceLeafSlot(feePayer), initialBalance), + /*nextKey=*/ Fr.ZERO, + /*nextIndex=*/ 0n, + ), + ); }); it('injects balance update with no public calls', async function () { @@ -206,7 +214,7 @@ describe('public_processor', () => { ); expect(failed).toEqual([]); - expect(treesDB.storageWrite).toHaveBeenCalledTimes(1); + expect(merkleTree.sequentialInsert).toHaveBeenCalledTimes(1); }); it('rejects tx if fee payer has not enough balance', async function () { @@ -226,9 +234,9 @@ describe('public_processor', () => { expect(failed).toHaveLength(1); expect(failed[0].error.message).toMatch(/Not enough balance/i); - expect(treesDB.commitCheckpoint).toHaveBeenCalledTimes(0); - expect(treesDB.revertCheckpoint).toHaveBeenCalledTimes(1); - expect(treesDB.storageWrite).toHaveBeenCalledTimes(0); + expect(merkleTree.commitCheckpoint).toHaveBeenCalledTimes(0); + expect(merkleTree.revertCheckpoint).toHaveBeenCalledTimes(1); + expect(merkleTree.sequentialInsert).toHaveBeenCalledTimes(0); }); }); }); diff --git a/yarn-project/simulator/src/public/public_processor/public_processor.ts b/yarn-project/simulator/src/public/public_processor/public_processor.ts index ddbce629d9d6..fd5460e53fb5 100644 --- a/yarn-project/simulator/src/public/public_processor/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor/public_processor.ts @@ -44,7 +44,7 @@ export class PublicProcessorFactory { constructor( private contractDataSource: ContractDataSource, private dateProvider: DateProvider, - private telemetryClient: TelemetryClient = getTelemetryClient(), + protected telemetryClient: TelemetryClient = getTelemetryClient(), ) {} /** @@ -59,10 +59,9 @@ export class PublicProcessorFactory { globalVariables: GlobalVariables, skipFeeEnforcement: boolean, ): PublicProcessor { - const treesDB = new PublicTreesDB(merkleTree); const contractsDB = new PublicContractsDB(this.contractDataSource); const publicTxSimulator = this.createPublicTxSimulator( - treesDB, + merkleTree, contractsDB, globalVariables, /*doMerkleOperations=*/ true, @@ -71,7 +70,7 @@ export class PublicProcessorFactory { return new PublicProcessor( globalVariables, - treesDB, + merkleTree, contractsDB, publicTxSimulator, this.dateProvider, @@ -80,14 +79,14 @@ export class PublicProcessorFactory { } protected createPublicTxSimulator( - treesDB: PublicTreesDB, + merkleTree: MerkleTreeWriteOperations, contractsDB: PublicContractsDB, globalVariables: GlobalVariables, doMerkleOperations: boolean, skipFeeEnforcement: boolean, ): PublicTxSimulator { return new TelemetryPublicTxSimulator( - treesDB, + merkleTree, contractsDB, globalVariables, doMerkleOperations, @@ -110,9 +109,10 @@ class PublicProcessorTimeoutError extends Error { */ export class PublicProcessor implements Traceable { private metrics: PublicProcessorMetrics; + constructor( protected globalVariables: GlobalVariables, - protected treesDB: PublicTreesDB, + private merkleTree: MerkleTreeWriteOperations, protected contractsDB: PublicContractsDB, protected publicTxSimulator: PublicTxSimulator, private dateProvider: DateProvider, @@ -221,7 +221,7 @@ export class PublicProcessor implements Traceable { // We checkpoint the transaction here, then within the try/catch we // 1. Revert the checkpoint if the tx fails or needs to be discarded for any reason // 2. Commit the transaction in the finally block. Note that by using the ForkCheckpoint lifecycle only the first commit/revert takes effect - const checkpoint = await ForkCheckpoint.new(this.treesDB); + const checkpoint = await ForkCheckpoint.new(this.merkleTree); try { const [processedTx, returnValues] = await this.processTx(tx, deadline); @@ -240,16 +240,8 @@ export class PublicProcessor implements Traceable { continue; } - if (!tx.hasPublicCalls()) { - // If there are no public calls, perform all tree insertions for side effects from private - // When there are public calls, the PublicTxSimulator & AVM handle tree insertions. - await this.doTreeInsertionsForPrivateOnlyTx(processedTx); - // Add any contracts registered/deployed in this private-only tx to the block-level cache - // (add to tx-level cache and then commit to block-level cache) - await this.contractsDB.addNewContracts(tx); - this.contractsDB.commitContractsForTx(); - } - + // FIXME(fcarreiro): it's ugly to have to notify the validator of nullifiers. + // I'd rather pass the validators the processedTx as well and let them deal with it. nullifierCache?.addNullifiers(processedTx.txEffect.nullifiers.map(n => n.toBuffer())); result.push(processedTx); returns = returns.concat(returnValues); @@ -332,12 +324,12 @@ export class PublicProcessor implements Traceable { // b) always had a txHandler with the same db passed to it as this.db, which updated the db in buildBaseRollupHints in this loop // To see how this ^ happens, move back to one shared db in test_context and run orchestrator_multi_public_functions.test.ts // The below is taken from buildBaseRollupHints: - await this.treesDB.appendLeaves( + await this.merkleTree.appendLeaves( MerkleTreeId.NOTE_HASH_TREE, padArrayEnd(processedTx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX), ); try { - await this.treesDB.batchInsert( + await this.merkleTree.batchInsert( MerkleTreeId.NULLIFIER_TREE, padArrayEnd(processedTx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()), NULLIFIER_SUBTREE_HEIGHT, @@ -352,11 +344,6 @@ export class PublicProcessor implements Traceable { } } - // The only public data write should be for fee payment - await this.treesDB.sequentialInsert( - MerkleTreeId.PUBLIC_DATA_TREE, - processedTx.txEffect.publicDataWrites.map(x => x.toBuffer()), - ); const treeInsertionEnd = process.hrtime.bigint(); this.metrics.recordTreeInsertions(Number(treeInsertionEnd - treeInsertionStart) / 1_000); } @@ -398,14 +385,16 @@ export class PublicProcessor implements Traceable { * This is used in private only txs, since for txs with public calls * the avm handles the fee payment itself. */ - private async getFeePaymentPublicDataWrite(txFee: Fr, feePayer: AztecAddress): Promise { + private async performFeePaymentPublicDataWrite(txFee: Fr, feePayer: AztecAddress): Promise { const feeJuiceAddress = ProtocolContractAddress.FeeJuice; const balanceSlot = await computeFeePayerBalanceStorageSlot(feePayer); const leafSlot = await computeFeePayerBalanceLeafSlot(feePayer); + // This high-level db is used as a convenient helper. It could be done with the merkleTree directly. + const treesDB = new PublicTreesDB(this.merkleTree); this.log.debug(`Deducting ${txFee.toBigInt()} balance in Fee Juice for ${feePayer}`); - const balance = await this.treesDB.storageRead(feeJuiceAddress, balanceSlot); + const balance = await treesDB.storageRead(feeJuiceAddress, balanceSlot); if (balance.lt(txFee)) { throw new Error( @@ -414,7 +403,7 @@ export class PublicProcessor implements Traceable { } const updatedBalance = balance.sub(txFee); - await this.treesDB.storageWrite(feeJuiceAddress, balanceSlot, updatedBalance); + await treesDB.storageWrite(feeJuiceAddress, balanceSlot, updatedBalance); return new PublicDataWrite(leafSlot, updatedBalance); } @@ -426,7 +415,7 @@ export class PublicProcessor implements Traceable { const gasFees = this.globalVariables.gasFees; const transactionFee = tx.data.gasUsed.computeFee(gasFees); - const feePaymentPublicDataWrite = await this.getFeePaymentPublicDataWrite(transactionFee, tx.data.feePayer); + const feePaymentPublicDataWrite = await this.performFeePaymentPublicDataWrite(transactionFee, tx.data.feePayer); const processedTx = await makeProcessedTxFromPrivateOnlyTx( tx, @@ -445,6 +434,15 @@ export class PublicProcessor implements Traceable { .filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log)) .map(log => ContractClassRegisteredEvent.fromLog(log)), ); + + // Fee payment insertion has already been done. Do the rest. + await this.doTreeInsertionsForPrivateOnlyTx(processedTx); + + // Add any contracts registered/deployed in this private-only tx to the block-level cache + // (add to tx-level cache and then commit to block-level cache) + await this.contractsDB.addNewContracts(tx); + this.contractsDB.commitContractsForTx(); + return [processedTx, undefined]; } diff --git a/yarn-project/simulator/src/public/public_tx_simulator/measured_public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator/measured_public_tx_simulator.ts index 1127042018e8..fa2ce82e8616 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/measured_public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/measured_public_tx_simulator.ts @@ -2,11 +2,12 @@ import type { Fr } from '@aztec/foundation/fields'; import { Timer } from '@aztec/foundation/timer'; import type { Gas } from '@aztec/stdlib/gas'; import type { AvmSimulationStats } from '@aztec/stdlib/stats'; +import type { MerkleTreeWriteOperations } from '@aztec/stdlib/trees'; import { type GlobalVariables, PublicCallRequestWithCalldata, Tx, TxExecutionPhase } from '@aztec/stdlib/tx'; import type { AvmFinalizedCallResult } from '../avm/avm_contract_call_result.js'; import type { ExecutorMetricsInterface } from '../executor_metrics_interface.js'; -import type { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js'; +import type { PublicContractsDB } from '../public_db_sources.js'; import type { PublicPersistableStateManager } from '../state_manager/state_manager.js'; import { PublicTxContext } from './public_tx_context.js'; import { type ProcessedPhase, type PublicTxResult, PublicTxSimulator } from './public_tx_simulator.js'; @@ -16,14 +17,14 @@ import { type ProcessedPhase, type PublicTxResult, PublicTxSimulator } from './p */ export class MeasuredPublicTxSimulator extends PublicTxSimulator { constructor( - treesDB: PublicTreesDB, + merkleTree: MerkleTreeWriteOperations, contractsDB: PublicContractsDB, globalVariables: GlobalVariables, doMerkleOperations: boolean = false, skipFeeEnforcement: boolean = false, protected readonly metrics: ExecutorMetricsInterface, ) { - super(treesDB, contractsDB, globalVariables, doMerkleOperations, skipFeeEnforcement); + super(merkleTree, contractsDB, globalVariables, doMerkleOperations, skipFeeEnforcement); } public override async simulate(tx: Tx, txLabel: string = 'unlabeledTx'): Promise { diff --git a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_context.ts index e8fc9c2c0794..0bdb810ca8d8 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_context.ts @@ -9,13 +9,7 @@ import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { assertLength } from '@aztec/foundation/serialize'; -import { - type AvmCircuitPublicInputs, - AvmExecutionHints, - AvmTxHint, - PublicDataWrite, - RevertCode, -} from '@aztec/stdlib/avm'; +import { type AvmCircuitPublicInputs, PublicDataWrite, RevertCode } from '@aztec/stdlib/avm'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { SimulationError } from '@aztec/stdlib/errors'; import { computeTransactionFee } from '@aztec/stdlib/fees'; @@ -32,7 +26,6 @@ import { MerkleTreeId } from '@aztec/stdlib/trees'; import { type GlobalVariables, PublicCallRequestWithCalldata, - type StateReference, TreeSnapshots, type Tx, TxExecutionPhase, @@ -42,8 +35,7 @@ import { import { strict as assert } from 'assert'; import { inspect } from 'util'; -import type { PublicContractsDBInterface } from '../../server.js'; -import { HintingPublicContractsDB, HintingPublicTreesDB } from '../hinting_db_sources.js'; +import type { PublicContractsDBInterface } from '../db_interfaces.js'; import type { PublicTreesDB } from '../public_db_sources.js'; import { SideEffectArrayLengths, SideEffectTrace } from '../side_effect_trace.js'; import { PublicPersistableStateManager } from '../state_manager/state_manager.js'; @@ -66,12 +58,11 @@ export class PublicTxContext { private revertCode: RevertCode = RevertCode.OK; /* What caused a revert (if one occurred)? */ public revertReason: SimulationError | undefined; - private constructor( public readonly txHash: TxHash, public readonly state: PhaseStateManager, + private readonly startTreeSnapshots: TreeSnapshots, private readonly globalVariables: GlobalVariables, - private readonly startStateReference: StateReference, private readonly gasSettings: GasSettings, private readonly gasUsedByPrivate: Gas, private readonly gasAllocatedToPublic: Gas, @@ -82,7 +73,6 @@ export class PublicTxContext { public readonly revertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, public readonly feePayer: AztecAddress, private readonly trace: SideEffectTrace, - public readonly hints: AvmExecutionHints, // This is public due to enqueued call hinting. ) { this.log = createLogger(`simulator:public_tx_context`); } @@ -109,22 +99,10 @@ export class PublicTxContext { const firstNullifier = nonRevertibleAccumulatedDataFromPrivate.nullifiers[0]; - // We wrap the DB to collect AVM hints. - const hints = new AvmExecutionHints(await AvmTxHint.fromTx(tx)); - const hintingContractsDB = new HintingPublicContractsDB(contractsDB, hints); - const hintingTreesDB = new HintingPublicTreesDB(treesDB, hints); - const startStateReference = await treesDB.getStateReference(); - hints.startingTreeRoots = new TreeSnapshots( - startStateReference.l1ToL2MessageTree, - startStateReference.partial.noteHashTree, - startStateReference.partial.nullifierTree, - startStateReference.partial.publicDataTree, - ); - // Transaction level state manager that will be forked for revertible phases. const txStateManager = PublicPersistableStateManager.create( - hintingTreesDB, - hintingContractsDB, + treesDB, + contractsDB, trace, doMerkleOperations, firstNullifier, @@ -139,8 +117,8 @@ export class PublicTxContext { return new PublicTxContext( await tx.getTxHash(), new PhaseStateManager(txStateManager), + await txStateManager.getTreeSnapshots(), globalVariables, - await treesDB.getStateReference(), gasSettings, gasUsedByPrivate, gasAllocatedToPublic, @@ -151,7 +129,6 @@ export class PublicTxContext { tx.data.forPublic!.revertibleAccumulatedData, tx.data.feePayer, trace, - hints, ); } @@ -324,20 +301,12 @@ export class PublicTxContext { assert(this.halted, 'Can only get AvmCircuitPublicInputs after tx execution ends'); const stateManager = this.state.getActiveStateManager(); - const startTreeSnapshots = new TreeSnapshots( - this.startStateReference.l1ToL2MessageTree, - this.startStateReference.partial.noteHashTree, - this.startStateReference.partial.nullifierTree, - this.startStateReference.partial.publicDataTree, - ); - // FIXME: We are first creating the PIs with the wrong endTreeSnapshots, then patching them. // This is because we need to know the lengths of the accumulated data arrays to pad them. // We should refactor this to avoid this hack. // We should just get the info we need from the trace, and create the rest of the PIs here. const avmCircuitPublicInputs = this.trace.toAvmCircuitPublicInputs( this.globalVariables, - startTreeSnapshots, /*startGasUsed=*/ this.gasUsedByPrivate, this.gasSettings, this.feePayer, @@ -351,6 +320,7 @@ export class PublicTxContext { /*transactionFee=*/ this.getTransactionFeeUnsafe(), /*reverted=*/ !this.revertCode.isOK(), ); + avmCircuitPublicInputs.startTreeSnapshots = this.startTreeSnapshots; const getArrayLengths = (from: PrivateToPublicAccumulatedData) => new PrivateToAvmAccumulatedDataArrayLengths( @@ -400,18 +370,11 @@ export class PublicTxContext { ); const numNoteHashesToPad = MAX_NOTE_HASHES_PER_TX - countAccumulatedItems(avmCircuitPublicInputs.accumulatedData.noteHashes); - await stateManager.deprecatedGetTreesForPIGeneration().padTree(MerkleTreeId.NOTE_HASH_TREE, numNoteHashesToPad); + await stateManager.padTree(MerkleTreeId.NOTE_HASH_TREE, numNoteHashesToPad); const numNullifiersToPad = MAX_NULLIFIERS_PER_TX - countAccumulatedItems(avmCircuitPublicInputs.accumulatedData.nullifiers); - await stateManager.deprecatedGetTreesForPIGeneration().padTree(MerkleTreeId.NULLIFIER_TREE, numNullifiersToPad); - - const paddedState = await stateManager.deprecatedGetTreesForPIGeneration().getStateReference(); - avmCircuitPublicInputs.endTreeSnapshots = new TreeSnapshots( - paddedState.l1ToL2MessageTree, - paddedState.partial.noteHashTree, - paddedState.partial.nullifierTree, - paddedState.partial.publicDataTree, - ); + await stateManager.padTree(MerkleTreeId.NULLIFIER_TREE, numNullifiersToPad); + avmCircuitPublicInputs.endTreeSnapshots = await stateManager.getTreeSnapshots(); return avmCircuitPublicInputs; } diff --git a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.test.ts b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.test.ts index 22259665ca18..0a78af4af7af 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.test.ts @@ -38,7 +38,7 @@ import { mock } from 'jest-mock-extended'; import { AvmFinalizedCallResult } from '../avm/avm_contract_call_result.js'; import type { InstructionSet } from '../avm/serialization/bytecode_serialization.js'; -import { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js'; +import { PublicContractsDB } from '../public_db_sources.js'; import { PublicPersistableStateManager } from '../state_manager/state_manager.js'; import { type PublicTxResult, PublicTxSimulator } from './public_tx_simulator.js'; @@ -60,7 +60,6 @@ describe('public_tx_simulator', () => { let merkleTrees: MerkleTreeWriteOperations; let merkleTreesCopy: MerkleTreeWriteOperations; - let treesDB: PublicTreesDB; let contractsDB: PublicContractsDB; let publicDataTree: AppendOnlyTree; @@ -243,7 +242,7 @@ describe('public_tx_simulator', () => { skipFeeEnforcement?: boolean; }) => { const simulator = new PublicTxSimulator( - treesDB, + merkleTrees, contractsDB, GlobalVariables.from({ ...GlobalVariables.empty(), gasFees }), doMerkleOperations, @@ -279,7 +278,6 @@ describe('public_tx_simulator', () => { beforeEach(async () => { merkleTrees = await (await NativeWorldStateService.tmp()).fork(); merkleTreesCopy = await (await NativeWorldStateService.tmp()).fork(); - treesDB = new PublicTreesDB(merkleTrees); contractsDB = new PublicContractsDB(mock()); treeStore = openTmpStore(); diff --git a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts index fc36712d310a..3e1291c53d76 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts @@ -7,11 +7,13 @@ import { AvmCircuitPublicInputs, AvmExecutionHints, type AvmProvingRequest, + AvmTxHint, type RevertCode, } from '@aztec/stdlib/avm'; import { SimulationError } from '@aztec/stdlib/errors'; import type { Gas, GasUsed } from '@aztec/stdlib/gas'; import { ProvingRequestType } from '@aztec/stdlib/proofs'; +import type { MerkleTreeWriteOperations } from '@aztec/stdlib/trees'; import { type GlobalVariables, NestedProcessReturnValues, @@ -22,10 +24,11 @@ import { import { strict as assert } from 'assert'; -import { getPublicFunctionDebugName } from '../../common/debug_fn_name.js'; import type { AvmFinalizedCallResult } from '../avm/avm_contract_call_result.js'; import { AvmSimulator } from '../avm/index.js'; -import type { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js'; +import { getPublicFunctionDebugName } from '../debug_fn_name.js'; +import { HintingMerkleWriteOperations, HintingPublicContractsDB } from '../hinting_db_sources.js'; +import { type PublicContractsDB, PublicTreesDB } from '../public_db_sources.js'; import { NullifierCollisionError } from '../state_manager/nullifiers.js'; import type { PublicPersistableStateManager } from '../state_manager/state_manager.js'; import { PublicTxContext } from './public_tx_context.js'; @@ -52,8 +55,8 @@ export class PublicTxSimulator { protected log: Logger; constructor( - private treesDB: PublicTreesDB, - protected contractsDB: PublicContractsDB, + private merkleTree: MerkleTreeWriteOperations, + private contractsDB: PublicContractsDB, private globalVariables: GlobalVariables, private doMerkleOperations: boolean = false, private skipFeeEnforcement: boolean = false, @@ -71,9 +74,15 @@ export class PublicTxSimulator { const txHash = await this.computeTxHash(tx); this.log.debug(`Simulating ${tx.publicFunctionCalldata.length} public calls for tx ${txHash}`, { txHash }); + // Create hinting DBs. + const hints = new AvmExecutionHints(await AvmTxHint.fromTx(tx)); + const hintingMerkleTree = await HintingMerkleWriteOperations.create(this.merkleTree, hints); + const hintingTreesDB = new PublicTreesDB(hintingMerkleTree); + const hintingContractsDB = new HintingPublicContractsDB(this.contractsDB, hints); + const context = await PublicTxContext.create( - this.treesDB, - this.contractsDB, + hintingTreesDB, + hintingContractsDB, tx, this.globalVariables, this.doMerkleOperations, @@ -107,7 +116,7 @@ export class PublicTxSimulator { await this.payFee(context); const publicInputs = await context.generateAvmCircuitPublicInputs(); - const avmProvingRequest = PublicTxSimulator.generateProvingRequest(publicInputs, context.hints); + const avmProvingRequest = PublicTxSimulator.generateProvingRequest(publicInputs, hints); const revertCode = context.getFinalRevertCode(); @@ -267,7 +276,7 @@ export class PublicTxSimulator { stateManager.traceEnqueuedCall(callRequest.request); const result = await this.simulateEnqueuedCallInternal( - context.state.getActiveStateManager(), + stateManager, callRequest, allocatedGas, /*transactionFee=*/ context.getTransactionFee(phase), @@ -335,7 +344,11 @@ export class PublicTxSimulator { protected async insertNonRevertiblesFromPrivate(context: PublicTxContext, tx: Tx) { const stateManager = context.state.getActiveStateManager(); try { - await stateManager.writeSiloedNullifiersFromPrivate(context.nonRevertibleAccumulatedDataFromPrivate.nullifiers); + for (const siloedNullifier of context.nonRevertibleAccumulatedDataFromPrivate.nullifiers.filter( + n => !n.isEmpty(), + )) { + await stateManager.writeSiloedNullifier(siloedNullifier); + } } catch (e) { if (e instanceof NullifierCollisionError) { throw new NullifierCollisionError( @@ -364,7 +377,9 @@ export class PublicTxSimulator { await context.state.fork(); const stateManager = context.state.getActiveStateManager(); try { - await stateManager.writeSiloedNullifiersFromPrivate(context.revertibleAccumulatedDataFromPrivate.nullifiers); + for (const siloedNullifier of context.revertibleAccumulatedDataFromPrivate.nullifiers.filter(n => !n.isEmpty())) { + await stateManager.writeSiloedNullifier(siloedNullifier); + } } catch (e) { if (e instanceof NullifierCollisionError) { // Instead of throwing, revert the app_logic phase diff --git a/yarn-project/simulator/src/public/public_tx_simulator/telemetry_public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator/telemetry_public_tx_simulator.ts index ddab1ea4bfed..34690b694320 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/telemetry_public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/telemetry_public_tx_simulator.ts @@ -1,11 +1,12 @@ import type { Fr } from '@aztec/foundation/fields'; import type { Gas } from '@aztec/stdlib/gas'; +import type { MerkleTreeWriteOperations } from '@aztec/stdlib/trees'; import { type GlobalVariables, PublicCallRequestWithCalldata, TxExecutionPhase } from '@aztec/stdlib/tx'; import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client'; import type { AvmFinalizedCallResult } from '../avm/avm_contract_call_result.js'; import { ExecutorMetrics } from '../executor_metrics.js'; -import type { PublicContractsDB, PublicTreesDB } from '../public_db_sources.js'; +import type { PublicContractsDB } from '../public_db_sources.js'; import type { PublicPersistableStateManager } from '../state_manager/state_manager.js'; import { MeasuredPublicTxSimulator } from './measured_public_tx_simulator.js'; import { PublicTxContext } from './public_tx_context.js'; @@ -18,7 +19,7 @@ export class TelemetryPublicTxSimulator extends MeasuredPublicTxSimulator { public readonly tracer: Tracer; constructor( - treesDB: PublicTreesDB, + merkleTree: MerkleTreeWriteOperations, contractsDB: PublicContractsDB, globalVariables: GlobalVariables, doMerkleOperations: boolean = false, @@ -26,7 +27,7 @@ export class TelemetryPublicTxSimulator extends MeasuredPublicTxSimulator { telemetryClient: TelemetryClient = getTelemetryClient(), ) { const metrics = new ExecutorMetrics(telemetryClient, 'PublicTxSimulator'); - super(treesDB, contractsDB, globalVariables, doMerkleOperations, skipFeeEnforcement, metrics); + super(merkleTree, contractsDB, globalVariables, doMerkleOperations, skipFeeEnforcement, metrics); this.tracer = metrics.tracer; } diff --git a/yarn-project/simulator/src/public/side_effect_trace.ts b/yarn-project/simulator/src/public/side_effect_trace.ts index 8ce1504e53f3..ff13c95e3430 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.ts @@ -32,7 +32,7 @@ import { } from '@aztec/stdlib/kernel'; import { PublicLog } from '@aztec/stdlib/logs'; import { L2ToL1Message, ScopedL2ToL1Message } from '@aztec/stdlib/messaging'; -import type { GlobalVariables, TreeSnapshots } from '@aztec/stdlib/tx'; +import { type GlobalVariables, TreeSnapshots } from '@aztec/stdlib/tx'; import { strict as assert } from 'assert'; @@ -277,8 +277,6 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { public toAvmCircuitPublicInputs( /** Globals. */ globalVariables: GlobalVariables, - /** Start tree snapshots. */ - startTreeSnapshots: TreeSnapshots, /** Gas used at start of TX. */ startGasUsed: Gas, /** How much gas was available for this public execution. */ @@ -305,7 +303,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface { ): AvmCircuitPublicInputs { return new AvmCircuitPublicInputs( globalVariables, - startTreeSnapshots, + TreeSnapshots.empty(), // will be patched later. startGasUsed, gasLimits, feePayer, diff --git a/yarn-project/simulator/src/public/state_manager/state_manager.test.ts b/yarn-project/simulator/src/public/state_manager/state_manager.test.ts index 46040f6ddcae..9f146d7dd561 100644 --- a/yarn-project/simulator/src/public/state_manager/state_manager.test.ts +++ b/yarn-project/simulator/src/public/state_manager/state_manager.test.ts @@ -105,13 +105,13 @@ describe('state_manager', () => { }); it('checkL1ToL2MessageExists works for missing message', async () => { - const exists = await persistableState.checkL1ToL2MessageExists(address, utxo, leafIndex); + const exists = await persistableState.checkL1ToL2MessageExists(utxo, leafIndex); expect(exists).toEqual(false); }); it('checkL1ToL2MessageExists works for existing message', async () => { mockL1ToL2MessageExists(treesDB, leafIndex, utxo); - const exists = await persistableState.checkL1ToL2MessageExists(address, utxo, leafIndex); + const exists = await persistableState.checkL1ToL2MessageExists(utxo, leafIndex); expect(exists).toEqual(true); }); diff --git a/yarn-project/simulator/src/public/state_manager/state_manager.ts b/yarn-project/simulator/src/public/state_manager/state_manager.ts index d74043124f7b..9eafa0270fcd 100644 --- a/yarn-project/simulator/src/public/state_manager/state_manager.ts +++ b/yarn-project/simulator/src/public/state_manager/state_manager.ts @@ -11,26 +11,20 @@ import { Fr } from '@aztec/foundation/fields'; import { jsonStringify } from '@aztec/foundation/json-rpc'; import { createLogger } from '@aztec/foundation/log'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; -import { PublicDataWrite } from '@aztec/stdlib/avm'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractClassPublicWithCommitment, ContractInstanceWithAddress } from '@aztec/stdlib/contract'; import { SerializableContractInstance } from '@aztec/stdlib/contract'; -import { - computeNoteHashNonce, - computePublicDataTreeLeafSlot, - computeUniqueNoteHash, - siloNoteHash, - siloNullifier, -} from '@aztec/stdlib/hash'; +import { computeNoteHashNonce, computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import type { PublicCallRequest } from '@aztec/stdlib/kernel'; import { SharedMutableValues, SharedMutableValuesWithHash } from '@aztec/stdlib/shared-mutable'; import { MerkleTreeId } from '@aztec/stdlib/trees'; +import type { TreeSnapshots } from '@aztec/stdlib/tx'; import { strict as assert } from 'assert'; -import { getPublicFunctionDebugName } from '../../common/debug_fn_name.js'; -import type { PublicContractsDBInterface } from '../../server.js'; import type { AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; +import type { PublicContractsDBInterface } from '../db_interfaces.js'; +import { getPublicFunctionDebugName } from '../debug_fn_name.js'; import type { PublicTreesDB } from '../public_db_sources.js'; import type { PublicSideEffectTraceInterface } from '../side_effect_trace_interface.js'; import { NullifierCollisionError, NullifierManager } from './nullifiers.js'; @@ -83,12 +77,6 @@ export class PublicPersistableStateManager { ); } - // DO NOT USE! - // FIXME(fcarreiro): refactor and remove this. - public deprecatedGetTreesForPIGeneration() { - return this.treesDB; - } - /** * Create a new state manager forked from this one */ @@ -131,14 +119,8 @@ export class PublicPersistableStateManager { this.nullifiers.acceptAndMerge(forkedState.nullifiers); this.trace.merge(forkedState.trace, reverted); if (reverted) { + this.log.trace('Reverting forked state...'); await this.treesDB.revertCheckpoint(); - if (this.doMerkleOperations) { - this.log.trace( - `Rolled back nullifier tree to root ${new Fr( - (await this.treesDB.getTreeInfo(MerkleTreeId.NULLIFIER_TREE)).root, - )}`, - ); - } } else { this.log.trace('Merging forked state into parent...'); await this.treesDB.commitCheckpoint(); @@ -153,15 +135,11 @@ export class PublicPersistableStateManager { * @param value - the value being written to the slot */ public async writeStorage(contractAddress: AztecAddress, slot: Fr, value: Fr, protocolWrite = false): Promise { - const leafSlot = await computePublicDataTreeLeafSlot(contractAddress, slot); - this.log.trace(`Storage write (address=${contractAddress}, slot=${slot}): value=${value}, leafSlot=${leafSlot}`); + this.log.trace(`Storage write (address=${contractAddress}, slot=${slot}): value=${value}`); if (this.doMerkleOperations) { // write to native merkle trees - const publicDataWrite = new PublicDataWrite(leafSlot, value); - const result = await this.treesDB.sequentialInsert(MerkleTreeId.PUBLIC_DATA_TREE, [publicDataWrite.toBuffer()]); - assert(result !== undefined, 'Public data tree insertion error. You might want to disable doMerkleOperations.'); - this.log.trace(`Inserted public data tree leaf at leafSlot ${leafSlot}, value: ${value}`); + await this.treesDB.storageWrite(contractAddress, slot, value); } else { // Cache storage writes for later reference/reads this.publicStorage.write(contractAddress, slot, value); @@ -179,8 +157,7 @@ export class PublicPersistableStateManager { */ public async readStorage(contractAddress: AztecAddress, slot: Fr): Promise { if (this.doMerkleOperations) { - const value = await this.treesDB.storageRead(contractAddress, slot); - return value; + return await this.treesDB.storageRead(contractAddress, slot); } else { // TODO(fcarreiro): I don't get this. PublicStorage CAN end up reading the tree. Why is it in the "dont do merkle operations" branch? const read = await this.publicStorage.read(contractAddress, slot); @@ -201,8 +178,8 @@ export class PublicPersistableStateManager { * @returns true if the note hash exists at the given leaf index, false otherwise */ public async checkNoteHashExists(contractAddress: AztecAddress, noteHash: Fr, leafIndex: Fr): Promise { - const gotLeafValue = (await this.treesDB.getNoteHash(leafIndex.toBigInt())) ?? Fr.ZERO; - const exists = gotLeafValue.equals(noteHash); + const gotLeafValue = await this.treesDB.getNoteHash(leafIndex.toBigInt()); + const exists = gotLeafValue !== undefined && gotLeafValue.equals(noteHash); this.log.trace( `noteHashes(${contractAddress})@${noteHash} ?? leafIndex: ${leafIndex} | gotLeafValue: ${gotLeafValue}, exists: ${exists}.`, ); @@ -236,7 +213,7 @@ export class PublicPersistableStateManager { public async writeUniqueNoteHash(uniqueNoteHash: Fr): Promise { this.log.trace(`noteHashes += @${uniqueNoteHash}.`); if (this.doMerkleOperations) { - await this.treesDB.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash]); + await this.treesDB.writeNoteHash(uniqueNoteHash); } this.trace.traceNewNoteHash(uniqueNoteHash); } @@ -256,7 +233,6 @@ export class PublicPersistableStateManager { this.log.trace(`Checked siloed nullifier ${siloedNullifier} (exists=${exists})`); return Promise.resolve(exists); } else { - // TODO: same here, this CAN hit the db. const { exists, cacheHit } = await this.nullifiers.checkExists(siloedNullifier); this.log.trace(`Checked siloed nullifier ${siloedNullifier} (exists=${exists}), cacheHit=${cacheHit}`); return Promise.resolve(exists); @@ -290,7 +266,7 @@ export class PublicPersistableStateManager { `Siloed nullifier ${siloedNullifier} already exists in parent cache or host.`, ); } else { - await this.treesDB.sequentialInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()]); + await this.treesDB.writeNullifier(siloedNullifier); } } else { // Cache pending nullifiers for later access @@ -300,25 +276,15 @@ export class PublicPersistableStateManager { this.trace.traceNewNullifier(siloedNullifier); } - public async writeSiloedNullifiersFromPrivate(siloedNullifiers: Fr[]) { - for (const siloedNullifier of siloedNullifiers.filter(n => !n.isEmpty())) { - await this.writeSiloedNullifier(siloedNullifier); - } - } - /** * Check if an L1 to L2 message exists, trace the check. * @param msgHash - the message hash to check existence of * @param msgLeafIndex - the message leaf index to use in the check * @returns exists - whether the message exists in the L1 to L2 Messages tree */ - public async checkL1ToL2MessageExists( - contractAddress: AztecAddress, - msgHash: Fr, - msgLeafIndex: Fr, - ): Promise { - const valueAtIndex = (await this.treesDB.getL1ToL2LeafValue(msgLeafIndex.toBigInt())) ?? Fr.ZERO; - const exists = valueAtIndex.equals(msgHash); + public async checkL1ToL2MessageExists(msgHash: Fr, msgLeafIndex: Fr): Promise { + const valueAtIndex = await this.treesDB.getL1ToL2LeafValue(msgLeafIndex.toBigInt()); + const exists = valueAtIndex !== undefined && valueAtIndex.equals(msgHash); this.log.trace( `l1ToL2Messages(@${msgLeafIndex}) ?? exists: ${exists}, expected: ${msgHash}, found: ${valueAtIndex}.`, ); @@ -375,7 +341,7 @@ export class PublicPersistableStateManager { ); assert( exists == nullifierExistsInTree, - 'treesDB contains contract instance, but nullifier tree does not contain contract address (or vice versa).... This is a bug!', + `Contract instance for address ${contractAddress} in DB: ${exists} != nullifier tree: ${nullifierExistsInTree}. This is a bug!`, ); // All that is left is tocheck that the contract updatability information is correct. @@ -501,6 +467,14 @@ export class PublicPersistableStateManager { public async getPublicFunctionDebugName(avmEnvironment: AvmExecutionEnvironment): Promise { return await getPublicFunctionDebugName(this.contractsDB, avmEnvironment.address, avmEnvironment.calldata); } + + public async padTree(treeId: MerkleTreeId, leavesToInsert: number): Promise { + await this.treesDB.padTree(treeId, leavesToInsert); + } + + public async getTreeSnapshots(): Promise { + return await this.treesDB.getTreeSnapshots(); + } } function contractAddressIsCanonical(contractAddress: AztecAddress): boolean { diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index ce8d0396bc78..33e2ff910d59 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -46,7 +46,6 @@ import { createTxForPublicCalls } from '@aztec/simulator/public/fixtures'; import { ExecutionError, PublicContractsDB, - PublicTreesDB, type PublicTxResult, PublicTxSimulator, createSimulationError, @@ -945,9 +944,13 @@ export class TXE implements TypedOracle { // See note at revert below. const checkpoint = await ForkCheckpoint.new(db); try { - const treesDB = new PublicTreesDB(db); const contractsDB = new PublicContractsDB(new TXEPublicContractDataSource(this)); - const simulator = new PublicTxSimulator(treesDB, contractsDB, globalVariables, /*doMerkleOperations=*/ true); + const simulator = new PublicTxSimulator( + this.baseFork, + contractsDB, + globalVariables, + /*doMerkleOperations=*/ false, + ); const { usedTxRequestHashForNonces } = this.noteCache.finish(); const firstNullifier = usedTxRequestHashForNonces