From d86a9d98de06693538f73828b1291f9befd2529c Mon Sep 17 00:00:00 2001 From: David Banks <47112877+dbanks12@users.noreply.github.com> Date: Mon, 18 Nov 2024 07:56:28 -0500 Subject: [PATCH] refactor: stop calling public kernels (#9971) --- .../aztec-node/src/aztec-node/server.ts | 8 +- yarn-project/circuit-types/src/tx/tx.ts | 30 +- .../prover-client/src/mocks/test_context.ts | 3 - yarn-project/prover-node/src/prover-node.ts | 8 +- .../src/client/sequencer-client.ts | 4 +- .../simulator/src/avm/avm_simulator.test.ts | 2 +- .../simulator/src/avm/journal/journal.ts | 52 ++- .../src/avm/opcodes/external_calls.ts | 6 +- .../src/public/dual_side_effect_trace.ts | 19 +- .../enqueued_call_side_effect_trace.test.ts | 6 +- .../public/enqueued_call_side_effect_trace.ts | 77 ++--- yarn-project/simulator/src/public/executor.ts | 4 +- .../simulator/src/public/public_processor.ts | 28 +- .../simulator/src/public/public_tx_context.ts | 298 +++++++++++------- .../src/public/public_tx_simulator.test.ts | 7 - .../src/public/public_tx_simulator.ts | 226 ++++++------- .../simulator/src/public/side_effect_trace.ts | 10 +- .../src/public/side_effect_trace_interface.ts | 5 +- yarn-project/simulator/src/public/utils.ts | 259 ++++++++------- 19 files changed, 545 insertions(+), 507 deletions(-) diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index eec371b96fb..f3cc8bda08f 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -71,7 +71,7 @@ import { } from '@aztec/p2p'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; import { GlobalVariableBuilder, SequencerClient } from '@aztec/sequencer-client'; -import { PublicProcessorFactory, WASMSimulator, createSimulationProvider } from '@aztec/simulator'; +import { PublicProcessorFactory, createSimulationProvider } from '@aztec/simulator'; import { type TelemetryClient } from '@aztec/telemetry-client'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { createValidatorClient } from '@aztec/validator-client'; @@ -733,11 +733,7 @@ export class AztecNodeService implements AztecNode { feeRecipient, ); const prevHeader = (await this.blockSource.getBlock(-1))?.header; - const publicProcessorFactory = new PublicProcessorFactory( - this.contractDataSource, - new WASMSimulator(), - this.telemetry, - ); + const publicProcessorFactory = new PublicProcessorFactory(this.contractDataSource, this.telemetry); const fork = await this.worldStateSynchronizer.fork(); diff --git a/yarn-project/circuit-types/src/tx/tx.ts b/yarn-project/circuit-types/src/tx/tx.ts index f53f70d7dcc..ae0c49e5011 100644 --- a/yarn-project/circuit-types/src/tx/tx.ts +++ b/yarn-project/circuit-types/src/tx/tx.ts @@ -2,7 +2,8 @@ import { ClientIvcProof, ContractClassRegisteredEvent, PrivateKernelTailCircuitPublicInputs, - type PublicKernelCircuitPublicInputs, + type PrivateToPublicAccumulatedData, + type ScopedLogHash, } from '@aztec/circuits.js'; import { type Buffer32 } from '@aztec/foundation/buffer'; import { arraySerializedSizeOfNonEmpty } from '@aztec/foundation/collection'; @@ -344,29 +345,26 @@ export class Tx extends Gossipable { * @param logHashes the individual log hashes we want to keep * @param out the output to put passing logs in, to keep this function abstract */ - public filterRevertedLogs(kernelOutput: PublicKernelCircuitPublicInputs) { + public filterRevertedLogs( + privateNonRevertible: PrivateToPublicAccumulatedData, + unencryptedLogsHashes: ScopedLogHash[], + ) { this.encryptedLogs = this.encryptedLogs.filterScoped( - kernelOutput.endNonRevertibleData.encryptedLogsHashes, + privateNonRevertible.encryptedLogsHashes, EncryptedTxL2Logs.empty(), ); - this.unencryptedLogs = this.unencryptedLogs.filterScoped( - kernelOutput.endNonRevertibleData.unencryptedLogsHashes, - UnencryptedTxL2Logs.empty(), - ); - this.noteEncryptedLogs = this.noteEncryptedLogs.filter( - kernelOutput.endNonRevertibleData.noteEncryptedLogsHashes, + privateNonRevertible.noteEncryptedLogsHashes, EncryptedNoteTxL2Logs.empty(), ); - // See comment in enqueued_calls_processor.ts -> tx.filterRevertedLogs() - if (this.data.forPublic) { - this.contractClassLogs = this.contractClassLogs.filterScoped( - this.data.forPublic?.nonRevertibleAccumulatedData.contractClassLogsHashes, - ContractClassTxL2Logs.empty(), - ); - } + this.contractClassLogs = this.contractClassLogs.filterScoped( + privateNonRevertible.contractClassLogsHashes, + ContractClassTxL2Logs.empty(), + ); + + this.unencryptedLogs = this.unencryptedLogs.filterScoped(unencryptedLogsHashes, UnencryptedTxL2Logs.empty()); } } diff --git a/yarn-project/prover-client/src/mocks/test_context.ts b/yarn-project/prover-client/src/mocks/test_context.ts index f04621c16f2..75360e62178 100644 --- a/yarn-project/prover-client/src/mocks/test_context.ts +++ b/yarn-project/prover-client/src/mocks/test_context.ts @@ -17,7 +17,6 @@ import { PublicExecutionResultBuilder, type PublicExecutor, PublicProcessor, - RealPublicKernelCircuitSimulator, type SimulationProvider, WASMSimulator, type WorldStateDB, @@ -69,7 +68,6 @@ export class TestContext { const publicExecutor = mock(); const worldStateDB = mock(); - const publicKernel = new RealPublicKernelCircuitSimulator(new WASMSimulator()); const telemetry = new NoopTelemetryClient(); // Separated dbs for public processor and prover - see public_processor for context @@ -89,7 +87,6 @@ export class TestContext { const processor = PublicProcessor.create( publicDb, publicExecutor, - publicKernel, globalVariables, Header.empty(), worldStateDB, diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 1b090315c8f..1c0c5f6611f 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -56,7 +56,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr private readonly contractDataSource: ContractDataSource, private readonly worldState: WorldStateSynchronizer, private readonly coordination: ProverCoordination & Maybe, - private readonly simulator: SimulationProvider, + private readonly _simulator: SimulationProvider, private readonly quoteProvider: QuoteProvider, private readonly quoteSigner: QuoteSigner, private readonly claimsMonitor: ClaimsMonitor, @@ -243,11 +243,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr const proverDb = await this.worldState.fork(fromBlock - 1); // Create a processor using the forked world state - const publicProcessorFactory = new PublicProcessorFactory( - this.contractDataSource, - this.simulator, - this.telemetryClient, - ); + const publicProcessorFactory = new PublicProcessorFactory(this.contractDataSource, this.telemetryClient); const cleanUp = async () => { await publicDb.close(); diff --git a/yarn-project/sequencer-client/src/client/sequencer-client.ts b/yarn-project/sequencer-client/src/client/sequencer-client.ts index a9618e8b980..eae0bd3729d 100644 --- a/yarn-project/sequencer-client/src/client/sequencer-client.ts +++ b/yarn-project/sequencer-client/src/client/sequencer-client.ts @@ -40,13 +40,13 @@ export class SequencerClient { contractDataSource: ContractDataSource, l2BlockSource: L2BlockSource, l1ToL2MessageSource: L1ToL2MessageSource, - simulationProvider: SimulationProvider, + _simulationProvider: SimulationProvider, telemetryClient: TelemetryClient, ) { const publisher = new L1Publisher(config, telemetryClient); const globalsBuilder = new GlobalVariableBuilder(config); - const publicProcessorFactory = new PublicProcessorFactory(contractDataSource, simulationProvider, telemetryClient); + const publicProcessorFactory = new PublicProcessorFactory(contractDataSource, telemetryClient); const rollup = publisher.getRollupContract(); const [l1GenesisTime, slotDuration] = await Promise.all([ diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index f05c18b3e01..7291fad8f21 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -138,7 +138,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { const contractClass = makeContractClassPublic(0, publicFn); const contractInstance = makeContractInstanceFromClassId(contractClass.id); - // The values here should match those in `avm_simulator.test.ts` + // The values here should match those in getContractInstance test case const instanceGet = new SerializableContractInstance({ version: 1, salt: new Fr(0x123), diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index ac5e8810474..855b52b82a4 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -39,6 +39,9 @@ export class AvmPersistableStateManager { /** Interface to perform merkle tree operations */ public merkleTrees: MerkleTreeWriteOperations; + /** Make sure a forked state is never merged twice. */ + private alreadyMergedIntoParent = false; + constructor( /** Reference to node storage */ private readonly worldStateDB: WorldStateDB, @@ -79,16 +82,46 @@ export class AvmPersistableStateManager { /** * Create a new state manager forked from this one */ - public fork(incrementSideEffectCounter: boolean = false) { + public fork() { return new AvmPersistableStateManager( this.worldStateDB, - this.trace.fork(incrementSideEffectCounter), + this.trace.fork(), this.publicStorage.fork(), this.nullifiers.fork(), this.doMerkleOperations, ); } + /** + * Accept forked world state modifications & traced side effects / hints + */ + public merge(forkedState: AvmPersistableStateManager) { + this._merge(forkedState, /*reverted=*/ false); + } + + /** + * Reject forked world state modifications & traced side effects, keep traced hints + */ + public reject(forkedState: AvmPersistableStateManager) { + this._merge(forkedState, /*reverted=*/ true); + } + + /** + * Commit cached storage writes to the DB. + * Keeps public storage up to date from tx to tx within a block. + */ + public async commitStorageWritesToDB() { + await this.publicStorage.commitToDB(); + } + + private _merge(forkedState: AvmPersistableStateManager, reverted: boolean) { + // sanity check to avoid merging the same forked trace twice + assert(!this.alreadyMergedIntoParent, 'Cannot merge forked state that has already been merged into its parent!'); + this.publicStorage.acceptAndMerge(forkedState.publicStorage); + this.nullifiers.acceptAndMerge(forkedState.nullifiers); + this.trace.merge(forkedState.trace, reverted); + } + /** * Write to public storage, journal/trace the write. * @@ -427,21 +460,6 @@ export class AvmPersistableStateManager { } } - /** - * Accept nested world state modifications - */ - public mergeForkedState(forkedState: AvmPersistableStateManager) { - this.publicStorage.acceptAndMerge(forkedState.publicStorage); - this.nullifiers.acceptAndMerge(forkedState.nullifiers); - this.trace.mergeSuccessfulForkedTrace(forkedState.trace); - } - - public rejectForkedState(forkedState: AvmPersistableStateManager) { - this.publicStorage.acceptAndMerge(forkedState.publicStorage); - this.nullifiers.acceptAndMerge(forkedState.nullifiers); - this.trace.mergeRevertedForkedTrace(forkedState.trace); - } - /** * Get a contract's bytecode from the contracts DB, also trace the contract class and instance */ diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.ts index 7bfbe5e8c71..318d8c5ffcb 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.ts @@ -97,9 +97,11 @@ abstract class ExternalCall extends Instruction { // Refund unused gas context.machineState.refundGas(gasLeftToGas(nestedContext.machineState)); - // Accept the nested call's state and trace the nested call + // Merge nested call's state and trace based on whether it succeeded. if (success) { - context.persistableState.mergeForkedState(nestedContext.persistableState); + context.persistableState.merge(nestedContext.persistableState); + } else { + context.persistableState.reject(nestedContext.persistableState); } await context.persistableState.traceNestedCall( /*nestedState=*/ nestedContext.persistableState, diff --git a/yarn-project/simulator/src/public/dual_side_effect_trace.ts b/yarn-project/simulator/src/public/dual_side_effect_trace.ts index 15fa4b76456..83928af6777 100644 --- a/yarn-project/simulator/src/public/dual_side_effect_trace.ts +++ b/yarn-project/simulator/src/public/dual_side_effect_trace.ts @@ -27,11 +27,12 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface { public readonly enqueuedCallTrace: PublicEnqueuedCallSideEffectTrace, ) {} - public fork(incrementSideEffectCounter: boolean = false) { - return new DualSideEffectTrace( - this.innerCallTrace.fork(incrementSideEffectCounter), - this.enqueuedCallTrace.fork(incrementSideEffectCounter), - ); + public fork() { + return new DualSideEffectTrace(this.innerCallTrace.fork(), this.enqueuedCallTrace.fork()); + } + + public merge(nestedTrace: this, reverted: boolean = false) { + this.enqueuedCallTrace.merge(nestedTrace.enqueuedCallTrace, reverted); } public getCounter() { @@ -232,14 +233,6 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface { this.enqueuedCallTrace.traceEnqueuedCall(publicCallRequest, calldata, reverted); } - public mergeSuccessfulForkedTrace(nestedTrace: this) { - this.enqueuedCallTrace.mergeSuccessfulForkedTrace(nestedTrace.enqueuedCallTrace); - } - - public mergeRevertedForkedTrace(nestedTrace: this) { - this.enqueuedCallTrace.mergeRevertedForkedTrace(nestedTrace.enqueuedCallTrace); - } - /** * Convert this trace to a PublicExecutionResult for use externally to the simulator. */ diff --git a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts index dc1ccb6bc45..a5e3460a082 100644 --- a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts +++ b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts @@ -508,11 +508,7 @@ describe('Enqueued-call Side Effect Trace', () => { nestedTrace.traceGetContractInstance(address, /*exists=*/ false, contractInstance); testCounter++; - if (reverted) { - trace.mergeRevertedForkedTrace(nestedTrace); - } else { - trace.mergeSuccessfulForkedTrace(nestedTrace); - } + trace.merge(nestedTrace, reverted); // parent trace adopts nested call's counter expect(trace.getCounter()).toBe(testCounter); diff --git a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts index b5f87c521b9..43f24639abb 100644 --- a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts +++ b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts @@ -70,7 +70,6 @@ import { makeTuple } from '@aztec/foundation/array'; import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { type Tuple } from '@aztec/foundation/serialize'; import { assert } from 'console'; @@ -140,6 +139,9 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI private avmCircuitHints: AvmExecutionHints; + /** Make sure a forked trace is never merged twice. */ + private alreadyMergedIntoParent = false; + constructor( /** The counter of this trace's first side effect. */ public readonly startSideEffectCounter: number = 0, @@ -154,9 +156,9 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI this.avmCircuitHints = AvmExecutionHints.empty(); } - public fork(incrementSideEffectCounter: boolean = false) { + public fork() { return new PublicEnqueuedCallSideEffectTrace( - incrementSideEffectCounter ? this.sideEffectCounter + 1 : this.sideEffectCounter, + this.sideEffectCounter, new PublicValidationRequestArrayLengths( this.previousValidationRequestArrayLengths.noteHashReadRequests + this.noteHashReadRequests.length, this.previousValidationRequestArrayLengths.nullifierReadRequests + this.nullifierReadRequests.length, @@ -178,6 +180,30 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI ); } + public merge(forkedTrace: this, reverted: boolean = false) { + // sanity check to avoid merging the same forked trace twice + assert(!this.alreadyMergedIntoParent, 'Cannot merge a forked trace that has already been merged into its parent!'); + forkedTrace.alreadyMergedIntoParent = true; + + // TODO(dbanks12): accept & merge forked trace's hints! + this.sideEffectCounter = forkedTrace.sideEffectCounter; + this.enqueuedCalls.push(...forkedTrace.enqueuedCalls); + + if (!reverted) { + this.publicDataReads.push(...forkedTrace.publicDataReads); + this.publicDataWrites.push(...forkedTrace.publicDataWrites); + this.noteHashReadRequests.push(...forkedTrace.noteHashReadRequests); + this.noteHashes.push(...forkedTrace.noteHashes); + this.nullifierReadRequests.push(...forkedTrace.nullifierReadRequests); + this.nullifierNonExistentReadRequests.push(...forkedTrace.nullifierNonExistentReadRequests); + this.nullifiers.push(...forkedTrace.nullifiers); + this.l1ToL2MsgReadRequests.push(...forkedTrace.l1ToL2MsgReadRequests); + this.l2ToL1Messages.push(...forkedTrace.l2ToL1Messages); + this.unencryptedLogs.push(...forkedTrace.unencryptedLogs); + this.unencryptedLogsHashes.push(...forkedTrace.unencryptedLogsHashes); + } + } + public getCounter() { return this.sideEffectCounter; } @@ -434,7 +460,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI } // This tracing function gets called everytime we start simulation/execution. - // This happens both when starting a new top-level trace and the start of every nested trace + // This happens both when starting a new top-level trace and the start of every forked trace // We use this to collect the AvmContractBytecodeHints public traceGetBytecode( contractAddress: AztecAddress, @@ -473,7 +499,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI */ public traceNestedCall( /** The trace of the nested call. */ - nestedCallTrace: this, + _nestedCallTrace: this, /** The execution environment of the nested call. */ nestedEnvironment: AvmExecutionEnvironment, /** How much gas was available for this public execution. */ @@ -523,38 +549,6 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI this.avmCircuitHints.enqueuedCalls.items.push(new AvmEnqueuedCallHint(publicCallRequest.contractAddress, calldata)); } - public mergeSuccessfulForkedTrace(nestedTrace: this) { - // TODO(dbanks12): accept & merge nested trace's hints! - this.sideEffectCounter = nestedTrace.sideEffectCounter; - - this.enqueuedCalls.push(...nestedTrace.enqueuedCalls); - - this.publicDataReads.push(...nestedTrace.publicDataReads); - this.publicDataWrites.push(...nestedTrace.publicDataWrites); - this.noteHashReadRequests.push(...nestedTrace.noteHashReadRequests); - this.noteHashes.push(...nestedTrace.noteHashes); - this.nullifierReadRequests.push(...nestedTrace.nullifierReadRequests); - this.nullifierNonExistentReadRequests.push(...nestedTrace.nullifierNonExistentReadRequests); - this.log.debug(`Merging nullifiers: ${nestedTrace.nullifiers.length}`); - this.log.debug(`Into parent nullifiers: ${this.nullifiers.length}`); - this.nullifiers.push(...nestedTrace.nullifiers); - this.log.debug(`After merge: ${JSON.stringify(this.nullifiers)}`); - this.l1ToL2MsgReadRequests.push(...nestedTrace.l1ToL2MsgReadRequests); - this.l2ToL1Messages.push(...nestedTrace.l2ToL1Messages); - this.unencryptedLogs.push(...nestedTrace.unencryptedLogs); - this.unencryptedLogsHashes.push(...nestedTrace.unencryptedLogsHashes); - } - - /** - * Discard accumulated side effects, but keep hints. - */ - public mergeRevertedForkedTrace(nestedTrace: this) { - // TODO(dbanks12): accept & merge nested trace's hints! - this.sideEffectCounter = nestedTrace.sideEffectCounter; - - this.enqueuedCalls.push(...nestedTrace.enqueuedCalls); - } - public getSideEffects(): SideEffects { return { enqueuedCalls: this.enqueuedCalls, @@ -617,7 +611,6 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI /** The call's results */ avmCallResults: AvmContractCallResult, ): VMCircuitPublicInputs { - this.log.debug(`Creating public inputs with call result: ${avmCallResults.reverted}`); return new VMCircuitPublicInputs( /*constants=*/ constants, /*callRequest=*/ callRequest, @@ -644,9 +637,9 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI /** How much gas was available for this public execution. */ gasLimits: GasSettings, /** Call requests for setup phase. */ - publicSetupCallRequests: Tuple, + publicSetupCallRequests: PublicCallRequest[], /** Call requests for app logic phase. */ - publicAppLogicCallRequests: Tuple, + publicAppLogicCallRequests: PublicCallRequest[], /** Call request for teardown phase. */ publicTeardownCallRequest: PublicCallRequest, /** End tree snapshots. */ @@ -666,8 +659,8 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI startTreeSnapshots, startGasUsed, gasLimits, - publicSetupCallRequests, - publicAppLogicCallRequests, + padArrayEnd(publicSetupCallRequests, PublicCallRequest.empty(), MAX_ENQUEUED_CALLS_PER_TX), + padArrayEnd(publicAppLogicCallRequests, PublicCallRequest.empty(), MAX_ENQUEUED_CALLS_PER_TX), publicTeardownCallRequest, /*previousNonRevertibleAccumulatedDataArrayLengths=*/ PrivateToAvmAccumulatedDataArrayLengths.empty(), /*previousRevertibleAccumulatedDataArrayLengths=*/ PrivateToAvmAccumulatedDataArrayLengths.empty(), diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index 3a90b863bdb..162d20b7a4c 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -43,7 +43,7 @@ export class PublicExecutor { */ public async simulate( stateManager: AvmPersistableStateManager, - executionRequest: PublicExecutionRequest, // TODO(dbanks12): CallRequest instead? + executionRequest: PublicExecutionRequest, globalVariables: GlobalVariables, allocatedGas: Gas, transactionFee: Fr = Fr.ZERO, @@ -105,6 +105,8 @@ export class PublicExecutor { * @param transactionFee - Fee offered for this TX. * @param startSideEffectCounter - The start counter to initialize the side effect trace with. * @returns The result of execution including side effect vectors. + * FIXME: this function is only used by the TXE. Ideally we would not support this as an external interface. + * Avoid using this interface as it it shouldn't really exist in the first place. */ public async simulateIsolatedEnqueuedCall( executionRequest: PublicExecutionRequest, diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index 55fce164cfb..f1d065bb166 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -31,12 +31,9 @@ import { Timer } from '@aztec/foundation/timer'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec/telemetry-client'; -import { type SimulationProvider } from '../providers/index.js'; import { PublicExecutor } from './executor.js'; import { computeFeePayerBalanceLeafSlot, computeFeePayerBalanceStorageSlot } from './fee_payment.js'; import { WorldStateDB } from './public_db_sources.js'; -import { RealPublicKernelCircuitSimulator } from './public_kernel.js'; -import { type PublicKernelCircuitSimulator } from './public_kernel_circuit_simulator.js'; import { PublicProcessorMetrics } from './public_processor_metrics.js'; import { PublicTxSimulator } from './public_tx_simulator.js'; @@ -44,11 +41,7 @@ import { PublicTxSimulator } from './public_tx_simulator.js'; * Creates new instances of PublicProcessor given the provided merkle tree db and contract data source. */ export class PublicProcessorFactory { - constructor( - private contractDataSource: ContractDataSource, - private simulator: SimulationProvider, - private telemetryClient: TelemetryClient, - ) {} + constructor(private contractDataSource: ContractDataSource, private telemetryClient: TelemetryClient) {} /** * Creates a new instance of a PublicProcessor. @@ -66,12 +59,10 @@ export class PublicProcessorFactory { const worldStateDB = new WorldStateDB(merkleTree, this.contractDataSource); const publicExecutor = new PublicExecutor(worldStateDB, telemetryClient); - const publicKernelSimulator = new RealPublicKernelCircuitSimulator(this.simulator); return PublicProcessor.create( merkleTree, publicExecutor, - publicKernelSimulator, globalVariables, historicalHeader, worldStateDB, @@ -91,7 +82,7 @@ export class PublicProcessor { protected globalVariables: GlobalVariables, protected historicalHeader: Header, protected worldStateDB: WorldStateDB, - protected enqueuedCallsProcessor: PublicTxSimulator, + protected publicTxSimulator: PublicTxSimulator, telemetryClient: TelemetryClient, private log = createDebugLogger('aztec:sequencer:public-processor'), ) { @@ -101,29 +92,20 @@ export class PublicProcessor { static create( db: MerkleTreeWriteOperations, publicExecutor: PublicExecutor, - publicKernelSimulator: PublicKernelCircuitSimulator, globalVariables: GlobalVariables, historicalHeader: Header, worldStateDB: WorldStateDB, telemetryClient: TelemetryClient, ) { - const enqueuedCallsProcessor = PublicTxSimulator.create( + const publicTxSimulator = PublicTxSimulator.create( db, publicExecutor, - publicKernelSimulator, globalVariables, historicalHeader, worldStateDB, ); - return new PublicProcessor( - db, - globalVariables, - historicalHeader, - worldStateDB, - enqueuedCallsProcessor, - telemetryClient, - ); + return new PublicProcessor(db, globalVariables, historicalHeader, worldStateDB, publicTxSimulator, telemetryClient); } get tracer(): Tracer { @@ -299,7 +281,7 @@ export class PublicProcessor { const timer = new Timer(); const { avmProvingRequest, gasUsed, revertCode, revertReason, processedPhases } = - await this.enqueuedCallsProcessor.process(tx); + await this.publicTxSimulator.process(tx); if (!avmProvingRequest) { this.metrics.recordFailedTx(); diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index 8e2a898ffbb..29810f7fe7c 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -5,23 +5,25 @@ import { type SimulationError, type Tx, TxExecutionPhase, + TxHash, } from '@aztec/circuit-types'; import { + type AvmCircuitPublicInputs, CombinedConstantData, Fr, Gas, type GasSettings, type GlobalVariables, + type PrivateToPublicAccumulatedData, PublicAccumulatedDataArrayLengths, type PublicCallRequest, - type PublicKernelCircuitPublicInputs, PublicValidationRequestArrayLengths, RevertCode, type StateReference, } from '@aztec/circuits.js'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; -import { assert } from 'console'; +import { strict as assert } from 'assert'; import { inspect } from 'util'; import { AvmPersistableStateManager } from '../avm/index.js'; @@ -29,63 +31,36 @@ import { DualSideEffectTrace } from './dual_side_effect_trace.js'; import { PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js'; import { type WorldStateDB } from './public_db_sources.js'; import { PublicSideEffectTrace } from './side_effect_trace.js'; -import { getCallRequestsByPhase, getExecutionRequestsByPhase, getPublicKernelCircuitPublicInputs } from './utils.js'; - -class PhaseStateManager { - private currentlyActiveStateManager: AvmPersistableStateManager | undefined; - - constructor(private readonly txStateManager: AvmPersistableStateManager) {} - - fork() { - assert(!this.currentlyActiveStateManager, 'Cannot fork when already forked'); - this.currentlyActiveStateManager = this.txStateManager.fork(); - } - - getActiveStateManager() { - return this.currentlyActiveStateManager || this.txStateManager; - } - - isForked() { - return !!this.currentlyActiveStateManager; - } - - mergeForkedState() { - assert(this.currentlyActiveStateManager, 'No forked state to merge'); - this.txStateManager.mergeForkedState(this.currentlyActiveStateManager!); - // Drop the forked state manager now that it is merged - this.currentlyActiveStateManager = undefined; - } - - discardForkedState() { - assert(this.currentlyActiveStateManager, 'No forked state to discard'); - this.txStateManager.rejectForkedState(this.currentlyActiveStateManager!); - // Drop the forked state manager. We don't want it! - this.currentlyActiveStateManager = undefined; - } -} +import { + convertPrivateToPublicAccumulatedData, + generateAvmCircuitPublicInputs, + getCallRequestsByPhase, + getExecutionRequestsByPhase, +} from './utils.js'; export class PublicTxContext { private log: DebugLogger; - private currentPhase: TxExecutionPhase = TxExecutionPhase.SETUP; - /* Gas used including private, teardown gas _limit_, setup and app logic */ private gasUsed: Gas; /* Gas actually used during teardown (different from limit) */ public teardownGasUsed: Gas = Gas.empty(); - public revertCode: RevertCode = RevertCode.OK; + /* Entire transaction execution is done. */ + private halted = false; + /* Where did reverts happen (if at all)? */ + private revertCode: RevertCode = RevertCode.OK; + /* What caused a revert (if one occurred)? */ public revertReason: SimulationError | undefined; public avmProvingRequest: AvmProvingRequest | undefined; // tmp hack constructor( public readonly state: PhaseStateManager, - public readonly tx: Tx, // tmp hack - public readonly globalVariables: GlobalVariables, - public readonly constants: CombinedConstantData, // tmp hack - public readonly startStateReference: StateReference, - startGasUsed: Gas, + private readonly globalVariables: GlobalVariables, + public readonly constants: CombinedConstantData, // FIXME(dbanks12): remove + private readonly startStateReference: StateReference, + private readonly startGasUsed: Gas, private readonly gasSettings: GasSettings, private readonly setupCallRequests: PublicCallRequest[], private readonly appLogicCallRequests: PublicCallRequest[], @@ -93,8 +68,9 @@ export class PublicTxContext { private readonly setupExecutionRequests: PublicExecutionRequest[], private readonly appLogicExecutionRequests: PublicExecutionRequest[], private readonly teardownExecutionRequests: PublicExecutionRequest[], - public latestPublicKernelOutput: PublicKernelCircuitPublicInputs, - public trace: PublicEnqueuedCallSideEffectTrace, + private readonly nonRevertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, + private readonly revertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, + public trace: PublicEnqueuedCallSideEffectTrace, // FIXME(dbanks12): should be private ) { this.log = createDebugLogger(`aztec:public_tx_context`); this.gasUsed = startGasUsed; @@ -106,29 +82,25 @@ export class PublicTxContext { tx: Tx, globalVariables: GlobalVariables, ) { - const privateKernelOutput = tx.data; - const latestPublicKernelOutput = getPublicKernelCircuitPublicInputs(privateKernelOutput, globalVariables); + const nonRevertibleAccumulatedDataFromPrivate = convertPrivateToPublicAccumulatedData( + tx.data.forPublic!.nonRevertibleAccumulatedData, + ); + const revertibleAccumulatedDataFromPrivate = convertPrivateToPublicAccumulatedData( + tx.data.forPublic!.revertibleAccumulatedData, + ); - const nonRevertibleNullifiersFromPrivate = latestPublicKernelOutput.endNonRevertibleData.nullifiers + const nonRevertibleNullifiersFromPrivate = nonRevertibleAccumulatedDataFromPrivate.nullifiers .filter(n => !n.isEmpty()) .map(n => n.value); - const _revertibleNullifiersFromPrivate = latestPublicKernelOutput.end.nullifiers + const _revertibleNullifiersFromPrivate = revertibleAccumulatedDataFromPrivate.nullifiers .filter(n => !n.isEmpty()) .map(n => n.value); - // During SETUP, non revertible side effects from private are our "previous data" - const prevAccumulatedData = latestPublicKernelOutput.endNonRevertibleData; - const previousValidationRequestArrayLengths = PublicValidationRequestArrayLengths.new( - latestPublicKernelOutput.validationRequests, - ); - - const previousAccumulatedDataArrayLengths = PublicAccumulatedDataArrayLengths.new(prevAccumulatedData); - const innerCallTrace = new PublicSideEffectTrace(); const enqueuedCallTrace = new PublicEnqueuedCallSideEffectTrace( /*startSideEffectCounter=*/ 0, - previousValidationRequestArrayLengths, - previousAccumulatedDataArrayLengths, + PublicValidationRequestArrayLengths.empty(), + PublicAccumulatedDataArrayLengths.new(nonRevertibleAccumulatedDataFromPrivate), ); const trace = new DualSideEffectTrace(innerCallTrace, enqueuedCallTrace); @@ -141,7 +113,6 @@ export class PublicTxContext { return new PublicTxContext( new PhaseStateManager(txStateManager), - tx, globalVariables, CombinedConstantData.combine(tx.data.constants, globalVariables), await db.getStateReference(), @@ -153,54 +124,44 @@ export class PublicTxContext { getExecutionRequestsByPhase(tx, TxExecutionPhase.SETUP), getExecutionRequestsByPhase(tx, TxExecutionPhase.APP_LOGIC), getExecutionRequestsByPhase(tx, TxExecutionPhase.TEARDOWN), - latestPublicKernelOutput, + tx.data.forPublic!.nonRevertibleAccumulatedData, + tx.data.forPublic!.revertibleAccumulatedData, enqueuedCallTrace, ); } - getCurrentPhase(): TxExecutionPhase { - return this.currentPhase; - } - - hasPhase(phase: TxExecutionPhase = this.currentPhase): boolean { - if (phase === TxExecutionPhase.SETUP) { - return this.setupCallRequests.length > 0; - } else if (phase === TxExecutionPhase.APP_LOGIC) { - return this.appLogicCallRequests.length > 0; - } else { - // phase === TxExecutionPhase.TEARDOWN - return this.teardownCallRequests.length > 0; - } + /** + * Signal that the entire transaction execution is done. + * All phases have been processed. + * Actual transaction fee and actual total consumed gas can now be queried. + */ + halt() { + this.halted = true; } - progressToNextPhase() { - assert(this.currentPhase !== TxExecutionPhase.TEARDOWN, 'Cannot progress past teardown'); - if (this.currentPhase === TxExecutionPhase.SETUP) { - this.currentPhase = TxExecutionPhase.APP_LOGIC; - } else { - this.currentPhase = TxExecutionPhase.TEARDOWN; - } - } + /** + * Revert execution a phase. Populate revertReason & revertCode. + * If in setup, throw an error (transaction will be thrown out). + * NOTE: this does not "halt" the entire transaction execution. + */ + revert(phase: TxExecutionPhase, revertReason: SimulationError | undefined = undefined, culprit = '') { + this.log.debug(`${TxExecutionPhase[phase]} phase reverted! ${culprit} failed with reason: ${revertReason}`); - revert(revertReason: SimulationError | undefined = undefined, culprit = '') { - this.log.debug( - `${TxExecutionPhase[this.currentPhase]} phase reverted! ${culprit} failed with reason: ${revertReason}`, - ); if (revertReason && !this.revertReason) { // don't override revertReason // (if app logic and teardown both revert, we want app logic's reason) this.revertReason = revertReason; } - if (this.currentPhase === TxExecutionPhase.SETUP) { + if (phase === TxExecutionPhase.SETUP) { this.log.debug(`Setup phase reverted! The transaction will be thrown out.`); if (revertReason) { throw revertReason; } else { throw new Error(`Setup phase reverted! The transaction will be thrown out. ${culprit} failed`); } - } else if (this.currentPhase === TxExecutionPhase.APP_LOGIC) { + } else if (phase === TxExecutionPhase.APP_LOGIC) { this.revertCode = RevertCode.APP_LOGIC_REVERTED; - } else if (this.currentPhase === TxExecutionPhase.TEARDOWN) { + } else if (phase === TxExecutionPhase.TEARDOWN) { if (this.revertCode.equals(RevertCode.APP_LOGIC_REVERTED)) { this.revertCode = RevertCode.BOTH_REVERTED; } else { @@ -209,8 +170,47 @@ export class PublicTxContext { } } - getCallRequestsForCurrentPhase(): PublicCallRequest[] { - switch (this.currentPhase) { + /** + * Get the revert code. + * @returns The revert code. + */ + getFinalRevertCode(): RevertCode { + assert(this.halted, 'Cannot know the final revert code until tx execution ends'); + return this.revertCode; + } + + /** + * Construct & return transaction hash. + * @returns The transaction's hash. + */ + getTxHash(): TxHash { + // Private kernel functions are executed client side and for this reason tx hash is already set as first nullifier + const firstNullifier = this.nonRevertibleAccumulatedDataFromPrivate.nullifiers[0]; + if (!firstNullifier || firstNullifier.isZero()) { + throw new Error(`Cannot get tx hash since first nullifier is missing`); + } + return new TxHash(firstNullifier.toBuffer()); + } + + /** + * Are there any call requests for the speciiied phase? + */ + hasPhase(phase: TxExecutionPhase): boolean { + if (phase === TxExecutionPhase.SETUP) { + return this.setupCallRequests.length > 0; + } else if (phase === TxExecutionPhase.APP_LOGIC) { + return this.appLogicCallRequests.length > 0; + } else { + // phase === TxExecutionPhase.TEARDOWN + return this.teardownCallRequests.length > 0; + } + } + + /** + * Get the call requests for the specified phase (including args hashes). + */ + getCallRequestsForPhase(phase: TxExecutionPhase): PublicCallRequest[] { + switch (phase) { case TxExecutionPhase.SETUP: return this.setupCallRequests; case TxExecutionPhase.APP_LOGIC: @@ -220,8 +220,11 @@ export class PublicTxContext { } } - getExecutionRequestsForCurrentPhase(): PublicExecutionRequest[] { - switch (this.currentPhase) { + /** + * Get the call requests for the specified phase (including actual args). + */ + getExecutionRequestsForPhase(phase: TxExecutionPhase): PublicExecutionRequest[] { + switch (phase) { case TxExecutionPhase.SETUP: return this.setupExecutionRequests; case TxExecutionPhase.APP_LOGIC: @@ -231,17 +234,22 @@ export class PublicTxContext { } } - getGasLeftForCurrentPhase(): Gas { - if (this.currentPhase === TxExecutionPhase.TEARDOWN) { + /** + * How much gas is left for the specified phase? + */ + getGasLeftForPhase(phase: TxExecutionPhase): Gas { + if (phase === TxExecutionPhase.TEARDOWN) { return this.gasSettings.teardownGasLimits; } else { return this.gasSettings.gasLimits.sub(this.gasUsed); } } - consumeGas(gas: Gas) { - if (this.currentPhase === TxExecutionPhase.TEARDOWN) { - // track teardown gas used separately + /** + * Consume gas. Track gas for teardown phase separately. + */ + consumeGas(phase: TxExecutionPhase, gas: Gas) { + if (phase === TxExecutionPhase.TEARDOWN) { this.teardownGasUsed = this.teardownGasUsed.add(gas); } else { this.gasUsed = this.gasUsed.add(gas); @@ -251,33 +259,39 @@ export class PublicTxContext { /** * Compute the gas used using the actual gas used during teardown instead * of the teardown gas limit. - * Note that this.startGasUsed comes from private and private includes - * teardown gas limit in its output gasUsed. + * Note that this.gasUsed is initialized from private's gasUsed which includes + * teardown gas limit. */ getActualGasUsed(): Gas { - assert(this.currentPhase === TxExecutionPhase.TEARDOWN, 'Can only compute actual gas used after app logic'); + assert(this.halted, 'Can only compute actual gas used after tx execution ends'); const requireTeardown = this.teardownCallRequests.length > 0; const teardownGasLimits = requireTeardown ? this.gasSettings.teardownGasLimits : Gas.empty(); return this.gasUsed.sub(teardownGasLimits).add(this.teardownGasUsed); } + /** + * The gasUsed as if the entire teardown gas limit was consumed. + */ getGasUsedForFee(): Gas { return this.gasUsed; } - getTransactionFeeAtCurrentPhase(): Fr { - if (this.currentPhase === TxExecutionPhase.TEARDOWN) { + /** + * Get the transaction fee as is available to the specified phase. + * Only teardown should have access to the actual transaction fee. + */ + getTransactionFee(phase: TxExecutionPhase): Fr { + if (phase === TxExecutionPhase.TEARDOWN) { return this.getTransactionFeeUnsafe(); } else { return Fr.zero(); } } - getTransactionFee(): Fr { - assert(this.currentPhase === TxExecutionPhase.TEARDOWN, 'Transaction fee is only known during/after teardown'); - return this.getTransactionFeeUnsafe(); - } - + /** + * Compute the transaction fee. + * Should only be called during or after teardown. + */ private getTransactionFeeUnsafe(): Fr { const txFee = this.gasUsed.computeFee(this.globalVariables.gasFees); this.log.debug(`Computed tx fee`, { @@ -287,4 +301,70 @@ export class PublicTxContext { }); return txFee; } + + /** + * Generate the public inputs for the AVM circuit. + */ + private generateAvmCircuitPublicInputs(endStateReference: StateReference): AvmCircuitPublicInputs { + assert(this.halted, 'Can only get AvmCircuitPublicInputs after tx execution ends'); + return generateAvmCircuitPublicInputs( + this.trace, + this.globalVariables, + this.startStateReference, + this.startGasUsed, + this.gasSettings, + this.setupCallRequests, + this.appLogicCallRequests, + this.teardownCallRequests, + this.nonRevertibleAccumulatedDataFromPrivate, + this.revertibleAccumulatedDataFromPrivate, + endStateReference, + /*endGasUsed=*/ this.gasUsed, + this.getTransactionFeeUnsafe(), + this.revertCode, + ); + } + + /** + * Generate the proving request for the AVM circuit. + */ + generateProvingRequest(endStateReference: StateReference): AvmProvingRequest { + // TODO(dbanks12): Once we actually have tx-level proving, this will generate the entire + // proving request for the first time + this.avmProvingRequest!.inputs.output = this.generateAvmCircuitPublicInputs(endStateReference); + return this.avmProvingRequest!; + } +} + +class PhaseStateManager { + private currentlyActiveStateManager: AvmPersistableStateManager | undefined; + + constructor(private readonly txStateManager: AvmPersistableStateManager) {} + + fork() { + assert(!this.currentlyActiveStateManager, 'Cannot fork when already forked'); + this.currentlyActiveStateManager = this.txStateManager.fork(); + } + + getActiveStateManager() { + return this.currentlyActiveStateManager || this.txStateManager; + } + + isForked() { + return !!this.currentlyActiveStateManager; + } + + mergeForkedState() { + assert(this.currentlyActiveStateManager, 'No forked state to merge'); + this.txStateManager.merge(this.currentlyActiveStateManager!); + // Drop the forked state manager now that it is merged + this.currentlyActiveStateManager = undefined; + } + + discardForkedState() { + assert(this.currentlyActiveStateManager, 'No forked state to discard'); + this.txStateManager.reject(this.currentlyActiveStateManager!); + // Drop the forked state manager. We don't want it! + this.currentlyActiveStateManager = undefined; + } } diff --git a/yarn-project/simulator/src/public/public_tx_simulator.test.ts b/yarn-project/simulator/src/public/public_tx_simulator.test.ts index 08780d7008c..e2251e69ea4 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.test.ts @@ -31,11 +31,8 @@ import { type MockProxy, mock } from 'jest-mock-extended'; import { type AvmPersistableStateManager } from '../avm/journal/journal.js'; import { PublicExecutionResultBuilder } from '../mocks/fixtures.js'; -import { WASMSimulator } from '../providers/acvm_wasm.js'; import { type PublicExecutor } from './executor.js'; import { type WorldStateDB } from './public_db_sources.js'; -import { RealPublicKernelCircuitSimulator } from './public_kernel.js'; -import { type PublicKernelCircuitSimulator } from './public_kernel_circuit_simulator.js'; import { PublicTxSimulator } from './public_tx_simulator.js'; describe('public_tx_simulator', () => { @@ -53,7 +50,6 @@ describe('public_tx_simulator', () => { let db: MockProxy; let publicExecutor: MockProxy; - let publicKernel: PublicKernelCircuitSimulator; let worldStateDB: MockProxy; let root: Buffer; @@ -163,12 +159,9 @@ describe('public_tx_simulator', () => { db.getPreviousValueIndex.mockResolvedValue({ index: 0n, alreadyPresent: true }); db.getLeafPreimage.mockResolvedValue(new PublicDataTreeLeafPreimage(new Fr(0), new Fr(0), new Fr(0), 0n)); - publicKernel = new RealPublicKernelCircuitSimulator(new WASMSimulator()); - processor = PublicTxSimulator.create( db, publicExecutor, - publicKernel, GlobalVariables.from({ ...GlobalVariables.empty(), gasFees }), Header.empty(), worldStateDB, diff --git a/yarn-project/simulator/src/public/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator.ts index e694b45dc9c..a67a223430a 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.ts @@ -6,6 +6,7 @@ import { type SimulationError, type Tx, TxExecutionPhase, + UnencryptedFunctionL2Logs, } from '@aztec/circuit-types'; import { type GlobalVariables, type Header, type RevertCode } from '@aztec/circuits.js'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; @@ -14,10 +15,7 @@ import { Timer } from '@aztec/foundation/timer'; import { EnqueuedCallSimulator } from './enqueued_call_simulator.js'; import { type PublicExecutor } from './executor.js'; import { type WorldStateDB } from './public_db_sources.js'; -import { type PublicKernelCircuitSimulator } from './public_kernel_circuit_simulator.js'; -import { PublicKernelTailSimulator } from './public_kernel_tail_simulator.js'; import { PublicTxContext } from './public_tx_context.js'; -import { generateAvmCircuitPublicInputs, runMergeKernelCircuit } from './utils.js'; export type ProcessedPhase = { phase: TxExecutionPhase; @@ -42,19 +40,16 @@ export class PublicTxSimulator { constructor( private db: MerkleTreeReadOperations, - private publicKernelSimulator: PublicKernelCircuitSimulator, private globalVariables: GlobalVariables, private worldStateDB: WorldStateDB, private enqueuedCallSimulator: EnqueuedCallSimulator, - private publicKernelTailSimulator: PublicKernelTailSimulator, ) { - this.log = createDebugLogger(`aztec:sequencer`); + this.log = createDebugLogger(`aztec:public_tx_simulator`); } static create( db: MerkleTreeReadOperations, publicExecutor: PublicExecutor, - publicKernelSimulator: PublicKernelCircuitSimulator, globalVariables: GlobalVariables, historicalHeader: Header, worldStateDB: WorldStateDB, @@ -69,16 +64,7 @@ export class PublicTxSimulator { realAvmProvingRequests, ); - const publicKernelTailSimulator = PublicKernelTailSimulator.create(db, publicKernelSimulator); - - return new PublicTxSimulator( - db, - publicKernelSimulator, - globalVariables, - worldStateDB, - enqueuedCallSimulator, - publicKernelTailSimulator, - ); + return new PublicTxSimulator(db, globalVariables, worldStateDB, enqueuedCallSimulator); } async process(tx: Tx): Promise { @@ -86,101 +72,112 @@ export class PublicTxSimulator { const context = await PublicTxContext.create(this.db, this.worldStateDB, tx, this.globalVariables); - const setupResult = await this.processSetupPhase(context); - const appLogicResult = await this.processAppLogicPhase(context); - const teardownResult = await this.processTeardownPhase(context); + // add new contracts to the contracts db so that their functions may be found and called + // TODO(#4073): This is catching only private deployments, when we add public ones, we'll + // have to capture contracts emitted in that phase as well. + // TODO(@spalladino): Should we allow emitting contracts in the fee preparation phase? + // TODO(#6464): Should we allow emitting contracts in the private setup phase? + // if so, this should only add contracts that were deployed during private app logic. + // FIXME: we shouldn't need to directly modify worldStateDb here! + await this.worldStateDB.addNewContracts(tx); + + const processedPhases: ProcessedPhase[] = []; + if (context.hasPhase(TxExecutionPhase.SETUP)) { + const setupResult: ProcessedPhase = await this.processSetupPhase(context); + processedPhases.push(setupResult); + } + if (context.hasPhase(TxExecutionPhase.APP_LOGIC)) { + const appLogicResult: ProcessedPhase = await this.processAppLogicPhase(context); + processedPhases.push(appLogicResult); + } + if (context.hasPhase(TxExecutionPhase.TEARDOWN)) { + const teardownResult: ProcessedPhase = await this.processTeardownPhase(context); + processedPhases.push(teardownResult); + } + context.halt(); - const processedPhases = [setupResult, appLogicResult, teardownResult].filter( - result => result !== undefined, - ) as ProcessedPhase[]; + const endStateReference = await this.db.getStateReference(); - const _endStateReference = await this.db.getStateReference(); - const transactionFee = context.getTransactionFee(); + const avmProvingRequest = context.generateProvingRequest(endStateReference); + const avmCircuitPublicInputs = avmProvingRequest.inputs.output!; - const tailKernelOutput = await this.publicKernelTailSimulator.simulate(context.latestPublicKernelOutput); + const revertCode = context.getFinalRevertCode(); + if (!revertCode.isOK()) { + // TODO(#6464): Should we allow emitting contracts in the private setup phase? + // if so, this is removing contracts deployed in private setup + // You can't submit contracts in public, so this is only relevant for private-created side effects + // FIXME: we shouldn't need to directly modify worldStateDb here! + await this.worldStateDB.removeNewContracts(tx); + // FIXME(dbanks12): should not be changing immutable tx + tx.filterRevertedLogs( + tx.data.forPublic!.nonRevertibleAccumulatedData, + avmCircuitPublicInputs.accumulatedData.unencryptedLogsHashes, + ); + } + // FIXME(dbanks12): should not be changing immutable tx + tx.unencryptedLogs.addFunctionLogs([new UnencryptedFunctionL2Logs(context.trace.getUnencryptedLogs())]); - context.avmProvingRequest!.inputs.output = generateAvmCircuitPublicInputs( - tx, - tailKernelOutput, - context.getGasUsedForFee(), - transactionFee, - ); + await context.state.getActiveStateManager().commitStorageWritesToDB(); - const gasUsed = { - totalGas: context.getActualGasUsed(), - teardownGas: context.teardownGasUsed, - }; return { - avmProvingRequest: context.avmProvingRequest!, - gasUsed, - revertCode: context.revertCode, + avmProvingRequest, + gasUsed: { totalGas: context.getActualGasUsed(), teardownGas: context.teardownGasUsed }, + revertCode, revertReason: context.revertReason, processedPhases: processedPhases, }; } - private async processSetupPhase(context: PublicTxContext): Promise { - // Start in phase TxExecutionPhase.SETUP; - if (context.hasPhase()) { - return await this.processPhase(context); - } + private async processSetupPhase(context: PublicTxContext): Promise { + return await this.processPhase(TxExecutionPhase.SETUP, context); } - private async processAppLogicPhase(context: PublicTxContext): Promise { - context.progressToNextPhase(); // to app logic - if (context.hasPhase()) { - // Fork the state manager so that we can rollback state if app logic or teardown reverts. - // Don't need to fork for setup since it's non-revertible (if setup fails, transaction is thrown out). - context.state.fork(); + private async processAppLogicPhase(context: PublicTxContext): Promise { + // Fork the state manager so that we can rollback state if app logic or teardown reverts. + // Don't need to fork for setup since it's non-revertible (if setup fails, transaction is thrown out). + context.state.fork(); - const result = await this.processPhase(context); - - if (result.reverted) { - // Drop the currently active forked state manager and rollback to end of setup. - // Fork again for teardown so that if teardown fails we can again rollback to end of setup. - context.state.discardForkedState(); - } else { - if (!context.hasPhase(TxExecutionPhase.TEARDOWN)) { - // Nothing to do after this (no teardown), so merge state in now instead of letting teardown handle it. - context.state.mergeForkedState(); - } - } + const result = await this.processPhase(TxExecutionPhase.APP_LOGIC, context); - return result; + if (result.reverted) { + // Drop the currently active forked state manager and rollback to end of setup. + context.state.discardForkedState(); + } else { + if (!context.hasPhase(TxExecutionPhase.TEARDOWN)) { + // Nothing to do after this (no teardown), so merge state updates now instead of letting teardown handle it. + context.state.mergeForkedState(); + } } - } - private async processTeardownPhase(context: PublicTxContext): Promise { - context.progressToNextPhase(); // to teardown - if (context.hasPhase()) { - if (!context.state.isForked()) { - // if state isn't forked (app logic was empty or reverted), fork now - // so we can rollback to the end of setup on teardown revert - context.state.fork(); - } + return result; + } - const result = await this.processPhase(context); + private async processTeardownPhase(context: PublicTxContext): Promise { + if (!context.state.isForked()) { + // If state isn't forked (app logic was empty or reverted), fork now + // so we can rollback to the end of setup if teardown reverts. + context.state.fork(); + } - if (result.reverted) { - // Drop the currently active forked state manager and rollback to end of setup. - context.state.discardForkedState(); - } else { - context.state.mergeForkedState(); - } + const result = await this.processPhase(TxExecutionPhase.TEARDOWN, context); - return result; + if (result.reverted) { + // Drop the currently active forked state manager and rollback to end of setup. + context.state.discardForkedState(); + } else { + // Merge state updates from teardown, + context.state.mergeForkedState(); } + + return result; } - private async processPhase(context: PublicTxContext): Promise { - const tx = context.tx; - const callRequests = context.getCallRequestsForCurrentPhase(); - const executionRequests = context.getExecutionRequestsForCurrentPhase(); + private async processPhase(phase: TxExecutionPhase, context: PublicTxContext): Promise { + const callRequests = context.getCallRequestsForPhase(phase); + const executionRequests = context.getExecutionRequestsForPhase(phase); const txStateManager = context.state.getActiveStateManager(); - this.log.debug( - `Beginning processing in phase ${TxExecutionPhase[context.getCurrentPhase()]} for tx ${tx.getTxHash()}`, - ); + this.log.debug(`Beginning processing in phase ${TxExecutionPhase[phase]} for tx ${context.getTxHash()}`); const returnValues: NestedProcessReturnValues[] = []; let reverted = false; @@ -194,66 +191,35 @@ export class PublicTxSimulator { const callRequest = callRequests[i]; const executionRequest = executionRequests[i]; - // add new contracts to the contracts db so that their functions may be found and called - // TODO(#4073): This is catching only private deployments, when we add public ones, we'll - // have to capture contracts emitted in that phase as well. - // TODO(@spalladino): Should we allow emitting contracts in the fee preparation phase? - // TODO(#6464): Should we allow emitting contracts in the private setup phase? - // if so, this should only add contracts that were deployed during private app logic. - // FIXME: we shouldn't need to directly modify worldStateDb here! - await this.worldStateDB.addNewContracts(tx); - - // each enqueued call starts with an incremented side effect counter - // FIXME: should be able to stop forking here and just trace the enqueued call (for hinting) - // and proceed with the same state manager for the entire phase - const enqueuedCallStateManager = txStateManager.fork(/*incrementSideEffectCounter=*/ true); const enqueuedCallResult = await this.enqueuedCallSimulator.simulate( callRequest, executionRequest, context.constants, - /*availableGas=*/ context.getGasLeftForCurrentPhase(), - /*transactionFee=*/ context.getTransactionFeeAtCurrentPhase(), - enqueuedCallStateManager, + /*availableGas=*/ context.getGasLeftForPhase(phase), + /*transactionFee=*/ context.getTransactionFee(phase), + txStateManager, ); + if (context.avmProvingRequest === undefined) { + // Propagate the very first avmProvingRequest of the tx for now. + // Eventually this will be the proof for the entire public portion of the transaction. + context.avmProvingRequest = enqueuedCallResult.avmProvingRequest; + } txStateManager.traceEnqueuedCall(callRequest, executionRequest.args, enqueuedCallResult.reverted!); - context.consumeGas(enqueuedCallResult.gasUsed); + context.consumeGas(phase, enqueuedCallResult.gasUsed); returnValues.push(enqueuedCallResult.returnValues); - // Propagate only one avmProvingRequest of a function call for now, so that we know it's still provable. - // Eventually this will be the proof for the entire public portion of the transaction. - context.avmProvingRequest = enqueuedCallResult.avmProvingRequest; + if (enqueuedCallResult.reverted) { reverted = true; const culprit = `${executionRequest.callContext.contractAddress}:${executionRequest.callContext.functionSelector}`; revertReason = enqueuedCallResult.revertReason; - context.revert(enqueuedCallResult.revertReason, culprit); // throws if in setup (non-revertible) phase - - // TODO(#6464): Should we allow emitting contracts in the private setup phase? - // if so, this is removing contracts deployed in private setup - // You can't submit contracts in public, so this is only relevant for private-created side effects - // FIXME: we shouldn't need to directly modify worldStateDb here! - await this.worldStateDB.removeNewContracts(tx); - // FIXME: we shouldn't be modifying the transaction here! - tx.filterRevertedLogs(context.latestPublicKernelOutput); - // Enqueeud call reverted. Discard state updates and accumulated side effects, but keep hints traced for the circuit. - txStateManager.rejectForkedState(enqueuedCallStateManager); - } else { - // FIXME: we shouldn't be modifying the transaction here! - tx.unencryptedLogs.addFunctionLogs([enqueuedCallResult.newUnencryptedLogs]); - // Enqueued call succeeded! Merge in any state updates made in the forked state manager. - txStateManager.mergeForkedState(enqueuedCallStateManager); + context.revert(phase, enqueuedCallResult.revertReason, culprit); // throws if in setup (non-revertible) phase } - - context.latestPublicKernelOutput = await runMergeKernelCircuit( - context.latestPublicKernelOutput, - enqueuedCallResult.kernelOutput, - this.publicKernelSimulator, - ); } return { - phase: context.getCurrentPhase(), + phase, durationMs: phaseTimer.ms(), returnValues, reverted, diff --git a/yarn-project/simulator/src/public/side_effect_trace.ts b/yarn-project/simulator/src/public/side_effect_trace.ts index efe7a3afa6b..5682e7cb6cc 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.ts @@ -106,8 +106,8 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { this.avmCircuitHints = AvmExecutionHints.empty(); } - public fork(incrementSideEffectCounter: boolean = false) { - return new PublicSideEffectTrace(incrementSideEffectCounter ? this.sideEffectCounter + 1 : this.sideEffectCounter); + public fork() { + return new PublicSideEffectTrace(this.sideEffectCounter); } public getCounter() { @@ -440,11 +440,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { throw new Error('Not implemented'); } - public mergeSuccessfulForkedTrace(_nestedTrace: this) { - throw new Error('Not implemented'); - } - - public mergeRevertedForkedTrace(_nestedTrace: this) { + public merge(_nestedTrace: this, _reverted: boolean = false) { throw new Error('Not implemented'); } diff --git a/yarn-project/simulator/src/public/side_effect_trace_interface.ts b/yarn-project/simulator/src/public/side_effect_trace_interface.ts index 02179157071..f3e427aecaa 100644 --- a/yarn-project/simulator/src/public/side_effect_trace_interface.ts +++ b/yarn-project/simulator/src/public/side_effect_trace_interface.ts @@ -17,7 +17,8 @@ import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.j import { type EnqueuedPublicCallExecutionResultWithSideEffects, type PublicFunctionCallResult } from './execution.js'; export interface PublicSideEffectTraceInterface { - fork(incrementSideEffectCounter?: boolean): PublicSideEffectTraceInterface; + fork(): PublicSideEffectTraceInterface; + merge(nestedTrace: PublicSideEffectTraceInterface, reverted?: boolean): void; getCounter(): number; // all "trace*" functions can throw SideEffectLimitReachedError tracePublicStorageRead( @@ -101,8 +102,6 @@ export interface PublicSideEffectTraceInterface { /** Did the call revert? */ reverted: boolean, ): void; - mergeSuccessfulForkedTrace(nestedTrace: PublicSideEffectTraceInterface): void; - mergeRevertedForkedTrace(nestedTrace: PublicSideEffectTraceInterface): void; toPublicEnqueuedCallExecutionResult( /** How much gas was left after this public execution. */ endGasLeft: Gas, diff --git a/yarn-project/simulator/src/public/utils.ts b/yarn-project/simulator/src/public/utils.ts index 8d3f6093f75..5b1713d70aa 100644 --- a/yarn-project/simulator/src/public/utils.ts +++ b/yarn-project/simulator/src/public/utils.ts @@ -1,36 +1,31 @@ import { type PublicExecutionRequest, type Tx, TxExecutionPhase } from '@aztec/circuit-types'; import { - AvmAccumulatedData, - AvmCircuitPublicInputs, - type CombinedAccumulatedData, - CombinedConstantData, - EnqueuedCallData, + type AvmCircuitPublicInputs, type Fr, type Gas, + type GasSettings, type GlobalVariables, - type KernelCircuitPublicInputs, - NESTED_RECURSIVE_PROOF_LENGTH, - type PrivateKernelTailCircuitPublicInputs, + MAX_L2_TO_L1_MSGS_PER_TX, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PrivateToAvmAccumulatedData, PrivateToAvmAccumulatedDataArrayLengths, type PrivateToPublicAccumulatedData, PublicAccumulatedData, - type PublicCallRequest, - PublicKernelCircuitPrivateInputs, - PublicKernelCircuitPublicInputs, - PublicKernelData, - PublicValidationRequests, - RevertCode, + PublicCallRequest, + PublicDataWrite, + type RevertCode, + type StateReference, TreeSnapshots, - type VMCircuitPublicInputs, - VerificationKeyData, countAccumulatedItems, - makeEmptyProof, - makeEmptyRecursiveProof, + mergeAccumulatedData, } from '@aztec/circuits.js'; -import { getVKSiblingPath } from '@aztec/noir-protocol-circuits-types'; +import { computeNoteHashNonce, computeUniqueNoteHash, siloNoteHash } from '@aztec/circuits.js/hash'; +import { padArrayEnd } from '@aztec/foundation/collection'; +import { assertLength } from '@aztec/foundation/serialize'; -import { type PublicKernelCircuitSimulator } from './public_kernel_circuit_simulator.js'; +import { type PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js'; export function getExecutionRequestsByPhase(tx: Tx, phase: TxExecutionPhase): PublicExecutionRequest[] { switch (phase) { @@ -62,51 +57,62 @@ export function getCallRequestsByPhase(tx: Tx, phase: TxExecutionPhase): PublicC } } -// Temporary hack to create PublicKernelCircuitPublicInputs from PrivateKernelTailCircuitPublicInputs. -export function getPublicKernelCircuitPublicInputs( - data: PrivateKernelTailCircuitPublicInputs, - globalVariables: GlobalVariables, -) { - const constants = CombinedConstantData.combine(data.constants, globalVariables); - - const validationRequest = PublicValidationRequests.empty(); - validationRequest.forRollup = data.rollupValidationRequests; - - const convertAccumulatedData = (from: PrivateToPublicAccumulatedData) => { - const to = PublicAccumulatedData.empty(); - to.noteHashes.forEach((_, i) => (to.noteHashes[i].noteHash.value = from.noteHashes[i])); - to.nullifiers.forEach((_, i) => (to.nullifiers[i].value = from.nullifiers[i])); - to.l2ToL1Msgs.forEach((_, i) => (to.l2ToL1Msgs[i] = from.l2ToL1Msgs[i])); - to.noteEncryptedLogsHashes.forEach((_, i) => (to.noteEncryptedLogsHashes[i] = from.noteEncryptedLogsHashes[i])); - to.encryptedLogsHashes.forEach((_, i) => (to.encryptedLogsHashes[i] = from.encryptedLogsHashes[i])); - to.publicCallStack.forEach((_, i) => (to.publicCallStack[i] = from.publicCallRequests[i])); - return to; - }; - - return new PublicKernelCircuitPublicInputs( - constants, - validationRequest, - convertAccumulatedData(data.forPublic!.nonRevertibleAccumulatedData), - convertAccumulatedData(data.forPublic!.revertibleAccumulatedData), - 0, - data.forPublic!.publicTeardownCallRequest, - data.feePayer, - RevertCode.OK, +export function convertPrivateToPublicAccumulatedData( + fromPrivate: PrivateToPublicAccumulatedData, +): PublicAccumulatedData { + const to = PublicAccumulatedData.empty(); + to.noteHashes.forEach((_, i) => (to.noteHashes[i].noteHash.value = fromPrivate.noteHashes[i])); + to.nullifiers.forEach((_, i) => (to.nullifiers[i].value = fromPrivate.nullifiers[i])); + to.l2ToL1Msgs.forEach((_, i) => (to.l2ToL1Msgs[i] = fromPrivate.l2ToL1Msgs[i])); + to.noteEncryptedLogsHashes.forEach( + (_, i) => (to.noteEncryptedLogsHashes[i] = fromPrivate.noteEncryptedLogsHashes[i]), ); + to.encryptedLogsHashes.forEach((_, i) => (to.encryptedLogsHashes[i] = fromPrivate.encryptedLogsHashes[i])); + to.publicCallStack.forEach((_, i) => (to.publicCallStack[i] = fromPrivate.publicCallRequests[i])); + return to; } -// Temporary hack to create the AvmCircuitPublicInputs from public tail's public inputs. export function generateAvmCircuitPublicInputs( - tx: Tx, - tailOutput: KernelCircuitPublicInputs, - gasUsedForFee: Gas, + trace: PublicEnqueuedCallSideEffectTrace, + globalVariables: GlobalVariables, + startStateReference: StateReference, + startGasUsed: Gas, + gasSettings: GasSettings, + setupCallRequests: PublicCallRequest[], + appLogicCallRequests: PublicCallRequest[], + teardownCallRequests: PublicCallRequest[], + nonRevertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, + revertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, + endStateReference: StateReference, + endGasUsed: Gas, transactionFee: Fr, -) { + revertCode: RevertCode, +): AvmCircuitPublicInputs { const startTreeSnapshots = new TreeSnapshots( - tailOutput.constants.historicalHeader.state.l1ToL2MessageTree, - tailOutput.startState.noteHashTree, - tailOutput.startState.nullifierTree, - tailOutput.startState.publicDataTree, + startStateReference.l1ToL2MessageTree, + startStateReference.partial.noteHashTree, + startStateReference.partial.nullifierTree, + startStateReference.partial.publicDataTree, + ); + const endTreeSnapshots = new TreeSnapshots( + endStateReference.l1ToL2MessageTree, + endStateReference.partial.noteHashTree, + endStateReference.partial.nullifierTree, + endStateReference.partial.publicDataTree, + ); + + const avmCircuitPublicInputs = trace.toAvmCircuitPublicInputs( + globalVariables, + startTreeSnapshots, + startGasUsed, + gasSettings, + setupCallRequests, + appLogicCallRequests, + teardownCallRequests.length ? teardownCallRequests[0] : PublicCallRequest.empty(), + endTreeSnapshots, + endGasUsed, + transactionFee, + !revertCode.isOK(), ); const getArrayLengths = (from: PrivateToPublicAccumulatedData) => @@ -115,68 +121,93 @@ export function generateAvmCircuitPublicInputs( countAccumulatedItems(from.nullifiers), countAccumulatedItems(from.l2ToL1Msgs), ); - const convertAccumulatedData = (from: PrivateToPublicAccumulatedData) => new PrivateToAvmAccumulatedData(from.noteHashes, from.nullifiers, from.l2ToL1Msgs); - - const convertAvmAccumulatedData = (from: CombinedAccumulatedData) => - new AvmAccumulatedData( - from.noteHashes, - from.nullifiers, - from.l2ToL1Msgs, - from.unencryptedLogsHashes, - from.publicDataWrites, - ); - - // This is wrong. But this is not used or checked in the rollup at the moment. - // Should fetch the updated roots from db. - const endTreeSnapshots = startTreeSnapshots; - - const avmCircuitpublicInputs = new AvmCircuitPublicInputs( - tailOutput.constants.globalVariables, - startTreeSnapshots, - tx.data.gasUsed, - tx.data.constants.txContext.gasSettings, - tx.data.forPublic!.nonRevertibleAccumulatedData.publicCallRequests, - tx.data.forPublic!.revertibleAccumulatedData.publicCallRequests, - tx.data.forPublic!.publicTeardownCallRequest, - getArrayLengths(tx.data.forPublic!.nonRevertibleAccumulatedData), - getArrayLengths(tx.data.forPublic!.revertibleAccumulatedData), - convertAccumulatedData(tx.data.forPublic!.nonRevertibleAccumulatedData), - convertAccumulatedData(tx.data.forPublic!.revertibleAccumulatedData), - endTreeSnapshots, - gasUsedForFee, - convertAvmAccumulatedData(tailOutput.end), - transactionFee, - !tailOutput.revertCode.equals(RevertCode.OK), + // Temporary overrides as these entries aren't yet populated in trace + avmCircuitPublicInputs.previousNonRevertibleAccumulatedDataArrayLengths = getArrayLengths( + nonRevertibleAccumulatedDataFromPrivate, + ); + avmCircuitPublicInputs.previousRevertibleAccumulatedDataArrayLengths = getArrayLengths( + revertibleAccumulatedDataFromPrivate, + ); + avmCircuitPublicInputs.previousNonRevertibleAccumulatedData = convertAccumulatedData( + nonRevertibleAccumulatedDataFromPrivate, + ); + avmCircuitPublicInputs.previousRevertibleAccumulatedData = convertAccumulatedData( + revertibleAccumulatedDataFromPrivate, ); - //console.log(`[FROM TAIL] AVM: ${inspect(avmCircuitpublicInputs, { depth: 5 })}`); - return avmCircuitpublicInputs; -} - -function getPreviousKernelData(previousOutput: PublicKernelCircuitPublicInputs): PublicKernelData { - // The proof is not used in simulation. - const proof = makeEmptyRecursiveProof(NESTED_RECURSIVE_PROOF_LENGTH); - const vk = VerificationKeyData.makeFakeHonk(); - const vkIndex = 0; - const siblingPath = getVKSiblingPath(vkIndex); + // merge all revertible & non-revertible side effects into output accumulated data + const noteHashesFromPrivate = revertCode.isOK() + ? mergeAccumulatedData( + avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.noteHashes, + avmCircuitPublicInputs.previousRevertibleAccumulatedData.noteHashes, + ) + : avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.noteHashes; + avmCircuitPublicInputs.accumulatedData.noteHashes = assertLength( + mergeAccumulatedData(noteHashesFromPrivate, avmCircuitPublicInputs.accumulatedData.noteHashes), + MAX_NOTE_HASHES_PER_TX, + ); - return new PublicKernelData(previousOutput, proof, vk, vkIndex, siblingPath); -} + const txHash = avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.nullifiers[0]; + + const scopedNoteHashesFromPublic = trace.getSideEffects().noteHashes; + for (let i = 0; i < scopedNoteHashesFromPublic.length; i++) { + const scopedNoteHash = scopedNoteHashesFromPublic[i]; + const noteHash = scopedNoteHash.value; + if (!noteHash.isZero()) { + const noteHashIndexInTx = i + countAccumulatedItems(noteHashesFromPrivate); + const nonce = computeNoteHashNonce(txHash, noteHashIndexInTx); + const uniqueNoteHash = computeUniqueNoteHash(nonce, noteHash); + const siloedNoteHash = siloNoteHash(scopedNoteHash.contractAddress, uniqueNoteHash); + avmCircuitPublicInputs.accumulatedData.noteHashes[noteHashIndexInTx] = siloedNoteHash; + } + } -export async function runMergeKernelCircuit( - previousOutput: PublicKernelCircuitPublicInputs, - enqueuedCallData: VMCircuitPublicInputs, - publicKernelSimulator: PublicKernelCircuitSimulator, -): Promise { - const previousKernel = getPreviousKernelData(previousOutput); + const nullifiersFromPrivate = revertCode.isOK() + ? mergeAccumulatedData( + avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.nullifiers, + avmCircuitPublicInputs.previousRevertibleAccumulatedData.nullifiers, + ) + : avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.nullifiers; + avmCircuitPublicInputs.accumulatedData.nullifiers = assertLength( + mergeAccumulatedData(nullifiersFromPrivate, avmCircuitPublicInputs.accumulatedData.nullifiers), + MAX_NULLIFIERS_PER_TX, + ); + const msgsFromPrivate = revertCode.isOK() + ? mergeAccumulatedData( + avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.l2ToL1Msgs, + avmCircuitPublicInputs.previousRevertibleAccumulatedData.l2ToL1Msgs, + ) + : avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.l2ToL1Msgs; + avmCircuitPublicInputs.accumulatedData.l2ToL1Msgs = assertLength( + mergeAccumulatedData(msgsFromPrivate, avmCircuitPublicInputs.accumulatedData.l2ToL1Msgs), + MAX_L2_TO_L1_MSGS_PER_TX, + ); - // The proof is not used in simulation. - const vmProof = makeEmptyProof(); - const callData = new EnqueuedCallData(enqueuedCallData, vmProof); + const dedupedPublicDataWrites: Array = []; + const leafSlotOccurences: Map = new Map(); + for (const publicDataWrite of avmCircuitPublicInputs.accumulatedData.publicDataWrites) { + const slot = publicDataWrite.leafSlot.toBigInt(); + const prevOccurrences = leafSlotOccurences.get(slot) || 0; + leafSlotOccurences.set(slot, prevOccurrences + 1); + } - const inputs = new PublicKernelCircuitPrivateInputs(previousKernel, callData); + for (const publicDataWrite of avmCircuitPublicInputs.accumulatedData.publicDataWrites) { + const slot = publicDataWrite.leafSlot.toBigInt(); + const prevOccurrences = leafSlotOccurences.get(slot) || 0; + if (prevOccurrences === 1) { + dedupedPublicDataWrites.push(publicDataWrite); + } else { + leafSlotOccurences.set(slot, prevOccurrences - 1); + } + } - return await publicKernelSimulator.publicKernelCircuitMerge(inputs); + avmCircuitPublicInputs.accumulatedData.publicDataWrites = padArrayEnd( + dedupedPublicDataWrites, + PublicDataWrite.empty(), + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ); + //console.log(`AvmCircuitPublicInputs:\n${inspect(avmCircuitPublicInputs)}`); + return avmCircuitPublicInputs; }