From 9bf1802940591c91737da80a8694996ed22d5d27 Mon Sep 17 00:00:00 2001 From: David Banks <47112877+dbanks12@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:19:25 -0500 Subject: [PATCH] [WIP] chore: refactoring public executor interface (#9701) --- .../vm/avm/trace/execution_hints.hpp | 7 - .../propagate_accumulated_data.nr | 1 - .../noir-protocol-circuits/pubkern.sh | 62 ------ .../bb-prover/src/avm_proving.test.ts | 2 +- yarn-project/bb-prover/src/test/test_avm.ts | 4 +- .../circuits.js/src/structs/avm/avm.ts | 41 +--- .../src/avm_integration.test.ts | 2 +- .../prover-client/src/mocks/test_context.ts | 6 +- .../simulator/src/avm/journal/journal.ts | 46 ++--- .../simulator/src/avm/journal/nullifiers.ts | 8 +- yarn-project/simulator/src/mocks/fixtures.ts | 117 +---------- .../src/public/dual_side_effect_trace.ts | 37 +++- .../enqueued_call_side_effect_trace.test.ts | 31 ++- .../public/enqueued_call_side_effect_trace.ts | 183 ++++++++---------- .../src/public/enqueued_call_simulator.ts | 104 ++++++---- .../public/enqueued_calls_processor.test.ts | 144 ++++---------- .../src/public/enqueued_calls_processor.ts | 92 ++++----- .../simulator/src/public/execution.ts | 60 +++++- yarn-project/simulator/src/public/executor.ts | 68 ++++--- yarn-project/simulator/src/public/index.ts | 5 +- .../src/public/public_processor.test.ts | 4 +- .../src/public/side_effect_trace.test.ts | 2 +- .../simulator/src/public/side_effect_trace.ts | 31 ++- .../src/public/side_effect_trace_interface.ts | 22 ++- yarn-project/txe/src/oracle/txe_oracle.ts | 96 ++++----- 25 files changed, 511 insertions(+), 664 deletions(-) delete mode 100755 noir-projects/noir-protocol-circuits/pubkern.sh diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution_hints.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution_hints.hpp index 87e0044ca6bc..4e9fe566bb8c 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution_hints.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution_hints.hpp @@ -8,13 +8,6 @@ namespace bb::avm_trace { using FF = AvmFlavorSettings::FF; using AffinePoint = grumpkin::g1::affine_element; -struct EnqueuedCallHint { - FF contract_address; - std::vector calldata; - - MSGPACK_FIELDS(contract_address, calldata); -}; - struct ExternalCallHint { FF success; std::vector return_data; diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/vm_circuit_output_composer/propagate_accumulated_data.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/vm_circuit_output_composer/propagate_accumulated_data.nr index eee2bf610ad4..6010ea888b44 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/vm_circuit_output_composer/propagate_accumulated_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/vm_circuit_output_composer/propagate_accumulated_data.nr @@ -90,7 +90,6 @@ fn propagate_public_data_writes( let writes = public_call.contract_storage_update_requests; for i in 0..writes.len() { let write = writes[i]; - // WARNING: What does this check accomplish? if write.counter != 0 { data.public_data_update_requests.push( PublicDataUpdateRequest::from_contract_storage_update_request( diff --git a/noir-projects/noir-protocol-circuits/pubkern.sh b/noir-projects/noir-protocol-circuits/pubkern.sh deleted file mode 100755 index ba4ee9f0c16d..000000000000 --- a/noir-projects/noir-protocol-circuits/pubkern.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -set -eu - -cd "$(dirname "$0")" - -CMD=${1:-} - -if [ -n "$CMD" ]; then - if [ "$CMD" = "clean" ]; then - git clean -fdx - exit 0 - else - echo "Unknown command: $CMD" - exit 1 - fi -fi - -yarn -node ./scripts/generate_variants.js - -NARGO=${NARGO:-../../noir/noir-repo/target/release/nargo} -echo "Compiling protocol circuits with ${RAYON_NUM_THREADS:-1} threads" -RAYON_NUM_THREADS=${RAYON_NUM_THREADS:-1} $NARGO compile --silence-warnings --package public_kernel_merge_simulated - -BB_HASH=${BB_HASH:-$(cd ../../ && git ls-tree -r HEAD | grep 'barretenberg/cpp' | awk '{print $3}' | git hash-object --stdin)} -echo Using BB hash $BB_HASH -mkdir -p "./target/keys" - -#AVAILABLE_MEMORY=0 -# -#case "$(uname)" in -# Linux*) -# # Check available memory on Linux -# AVAILABLE_MEMORY=$(awk '/MemTotal/ { printf $2 }' /proc/meminfo) -# ;; -# *) -# echo "Parallel vk generation not supported on this operating system" -# ;; -#esac -## This value may be too low. -## If vk generation fail with an amount of free memory greater than this value then it should be increased. -#MIN_PARALLEL_VK_GENERATION_MEMORY=500000000 -#PARALLEL_VK=${PARALLEL_VK:-true} -# -#if [[ AVAILABLE_MEMORY -gt MIN_PARALLEL_VK_GENERATION_MEMORY ]] && [[ $PARALLEL_VK == "true" ]]; then -# echo "Generating vks in parallel..." -# for pathname in "./target"/*.json; do -# BB_HASH=$BB_HASH node ../scripts/generate_vk_json.js "$pathname" "./target/keys" & -# done -# -# for job in $(jobs -p); do -# wait $job || exit 1 -# done -# -#else -# echo "System does not have enough memory for parallel vk generation, falling back to sequential" - echo "generating vk" - -# for pathname in "./target"/*.json; do - BB_HASH=$BB_HASH node ../scripts/generate_vk_json.js "./target/public_kernel_merge_simulated.json" "./target/keys" -# done -#fi diff --git a/yarn-project/bb-prover/src/avm_proving.test.ts b/yarn-project/bb-prover/src/avm_proving.test.ts index 69b9d4f96e7d..8f8677f2d53a 100644 --- a/yarn-project/bb-prover/src/avm_proving.test.ts +++ b/yarn-project/bb-prover/src/avm_proving.test.ts @@ -136,7 +136,7 @@ const proveAndVerifyAvmTestContract = async ( ); } - const pxResult = trace.toPublicExecutionResult( + const pxResult = trace.toPublicFunctionCallResult( environment, startGas, /*endGasLeft=*/ Gas.from(context.machineState.gasLeft), diff --git a/yarn-project/bb-prover/src/test/test_avm.ts b/yarn-project/bb-prover/src/test/test_avm.ts index b571926d4f7d..d1b128f0c00d 100644 --- a/yarn-project/bb-prover/src/test/test_avm.ts +++ b/yarn-project/bb-prover/src/test/test_avm.ts @@ -28,10 +28,10 @@ import { } from '@aztec/circuits.js'; import { computeVarArgsHash } from '@aztec/circuits.js/hash'; import { padArrayEnd } from '@aztec/foundation/collection'; -import { type PublicExecutionResult } from '@aztec/simulator'; +import { type PublicFunctionCallResult } from '@aztec/simulator'; // TODO: pub somewhere more usable - copied from abstract phase manager -export function getPublicInputs(result: PublicExecutionResult): PublicCircuitPublicInputs { +export function getPublicInputs(result: PublicFunctionCallResult): PublicCircuitPublicInputs { return PublicCircuitPublicInputs.from({ callContext: result.executionRequest.callContext, proverAddress: AztecAddress.ZERO, diff --git a/yarn-project/circuits.js/src/structs/avm/avm.ts b/yarn-project/circuits.js/src/structs/avm/avm.ts index 8e7bda947cbc..8931f5c2bd5d 100644 --- a/yarn-project/circuits.js/src/structs/avm/avm.ts +++ b/yarn-project/circuits.js/src/structs/avm/avm.ts @@ -3,31 +3,12 @@ import { Fr } from '@aztec/foundation/fields'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; -//import { Decoder, Encoder, addExtension } from 'msgpackr'; import { type ContractClassIdPreimage } from '../../contract/contract_class_id.js'; import { PublicKeys } from '../../types/public_keys.js'; import { Gas } from '../gas.js'; import { PublicCircuitPublicInputs } from '../public_circuit_public_inputs.js'; import { Vector } from '../shared.js'; -//addExtension({ -// Class: Fr, -// write: fr => fr.toBuffer(), -//}); -// -//const encoder = new Encoder({ -// // always encode JS objects as MessagePack maps -// // this makes it compatible with other MessagePack decoders -// useRecords: false, -// int64AsType: 'bigint', -//}); -// -///** A long-lived msgpack decoder */ -//const decoder = new Decoder({ -// useRecords: false, -// int64AsType: 'bigint', -//}); - export class AvmEnqueuedCallHint { public readonly contractAddress: Fr; public readonly calldata: Vector; @@ -41,7 +22,6 @@ export class AvmEnqueuedCallHint { * @returns - The inputs serialized to a buffer. */ toBuffer() { - //return encoder.encode(this); return serializeToBuffer(...AvmEnqueuedCallHint.getFields(this)); } @@ -87,7 +67,6 @@ export class AvmEnqueuedCallHint { static fromBuffer(buff: Buffer | BufferReader) { const reader = BufferReader.asReader(buff); return new AvmEnqueuedCallHint(Fr.fromBuffer(reader), reader.readVector(Fr)); - //return decoder.decode(buff); } /** @@ -455,7 +434,6 @@ export class AvmContractBytecodeHints { } } -// TODO(dbanks12): rename AvmCircuitHints export class AvmExecutionHints { public readonly enqueuedCalls: Vector; public readonly storageValues: Vector; @@ -500,14 +478,6 @@ export class AvmExecutionHints { */ toBuffer() { return serializeToBuffer(...AvmExecutionHints.getFields(this)); - //this.storageValues.items, - //this.noteHashExists.items, - //this.nullifierExists.items, - //this.l1ToL2MessageExists.items, - //this.externalCalls.items, - //this.contractInstances.items, - //this.contractBytecodeHints.items, - //); } /** @@ -542,7 +512,7 @@ export class AvmExecutionHints { */ static from(fields: FieldsOf): AvmExecutionHints { return new AvmExecutionHints( - //fields.enqueuedCalls.items, + // omit enqueued call hints until they're implemented in C++ new Array(), fields.storageValues.items, fields.noteHashExists.items, @@ -561,6 +531,7 @@ export class AvmExecutionHints { */ static getFields(fields: FieldsOf) { return [ + // omit enqueued call hints until they're implemented in C++ //fields.enqueuedCalls, fields.storageValues, fields.noteHashExists, @@ -580,8 +551,7 @@ export class AvmExecutionHints { static fromBuffer(buff: Buffer | BufferReader): AvmExecutionHints { const reader = BufferReader.asReader(buff); return new AvmExecutionHints( - //encoder.decode(buff), - //reader.readVector(AvmEnqueuedCallHint), + // omit enqueued call hints until they're implemented in C++ new Array(), reader.readVector(AvmKeyValueHint), reader.readVector(AvmKeyValueHint), @@ -656,6 +626,10 @@ export class AvmCircuitInputs { ); } + static empty(): AvmCircuitInputs { + return new AvmCircuitInputs('', [], PublicCircuitPublicInputs.empty(), AvmExecutionHints.empty()); + } + /** * Creates a new instance from fields. * @param fields - Fields to create the instance from. @@ -686,7 +660,6 @@ export class AvmCircuitInputs { /*calldata=*/ reader.readVector(Fr), PublicCircuitPublicInputs.fromBuffer(reader), AvmExecutionHints.fromBuffer(reader), - //decoder.decode(reader.readBuffer()), ); } diff --git a/yarn-project/ivc-integration/src/avm_integration.test.ts b/yarn-project/ivc-integration/src/avm_integration.test.ts index d22d573ead52..e99bd685b08d 100644 --- a/yarn-project/ivc-integration/src/avm_integration.test.ts +++ b/yarn-project/ivc-integration/src/avm_integration.test.ts @@ -227,7 +227,7 @@ const proveAvmTestContract = async ( ); } - const pxResult = trace.toPublicExecutionResult( + const pxResult = trace.toPublicFunctionCallResult( environment, startGas, /*endGasLeft=*/ Gas.from(context.machineState.gasLeft), diff --git a/yarn-project/prover-client/src/mocks/test_context.ts b/yarn-project/prover-client/src/mocks/test_context.ts index 187fd24ff253..f04621c16f22 100644 --- a/yarn-project/prover-client/src/mocks/test_context.ts +++ b/yarn-project/prover-client/src/mocks/test_context.ts @@ -160,7 +160,7 @@ export class TestContext { execution: PublicExecutionRequest, _globalVariables: GlobalVariables, allocatedGas: Gas, - transactionFee?: Fr, + _transactionFee?: Fr, ) => { for (const tx of txs) { const allCalls = tx.publicTeardownFunctionCall.isEmpty() @@ -168,10 +168,8 @@ export class TestContext { : [...tx.enqueuedPublicFunctionCalls, tx.publicTeardownFunctionCall]; for (const request of allCalls) { if (execution.callContext.equals(request.callContext)) { - const result = PublicExecutionResultBuilder.fromPublicExecutionRequest({ request }).build({ - startGasLeft: allocatedGas, + const result = PublicExecutionResultBuilder.empty().build({ endGasLeft: allocatedGas, - transactionFee, }); return Promise.resolve(result); } diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 9df364435905..dfc3e0d59a4c 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -37,8 +37,7 @@ export class AvmPersistableStateManager { // TODO(5818): make private once no longer accessed in executor public readonly trace: PublicSideEffectTraceInterface, /** Public storage, including cached writes */ - // TODO(5818): make private once no longer accessed in executor - public readonly publicStorage: PublicStorage = new PublicStorage(worldStateDB), + private readonly publicStorage: PublicStorage = new PublicStorage(worldStateDB), /** Nullifier set, including cached/recently-emitted nullifiers */ private readonly nullifiers: NullifierManager = new NullifierManager(worldStateDB), ) {} @@ -72,20 +71,6 @@ export class AvmPersistableStateManager { ); } - public forkWithPendingSiloedNullifiers(incrementSideEffectCounter: boolean, pendingSiloedNullifiers: Fr[]) { - const prePhaseNullifiers = NullifierManager.newWithPendingSiloedNullifiers( - this.worldStateDB, - pendingSiloedNullifiers, - this.nullifiers, - ); - return new AvmPersistableStateManager( - this.worldStateDB, - this.trace.fork(incrementSideEffectCounter), - this.publicStorage.fork(), - prePhaseNullifiers, // TODO(dbanks12): need a fork()? - ); - } - /** * Write to public storage, journal/trace the write. * @@ -257,9 +242,9 @@ export class AvmPersistableStateManager { /** * Accept nested world state modifications */ - public acceptNestedCallState(nestedState: AvmPersistableStateManager) { - this.publicStorage.acceptAndMerge(nestedState.publicStorage); - this.nullifiers.acceptAndMerge(nestedState.nullifiers); + public acceptForkedState(forkedState: AvmPersistableStateManager) { + this.publicStorage.acceptAndMerge(forkedState.publicStorage); + this.nullifiers.acceptAndMerge(forkedState.nullifiers); } /** @@ -301,12 +286,11 @@ export class AvmPersistableStateManager { return undefined; } } - /** * Accept the nested call's state and trace the nested call */ public async processNestedCall( - nestedState: AvmPersistableStateManager, + forkedState: AvmPersistableStateManager, nestedEnvironment: AvmExecutionEnvironment, startGasLeft: Gas, endGasLeft: Gas, @@ -314,7 +298,7 @@ export class AvmPersistableStateManager { avmCallResults: AvmContractCallResult, ) { if (!avmCallResults.reverted) { - this.acceptNestedCallState(nestedState); + this.acceptForkedState(forkedState); } const functionName = await getPublicFunctionDebugName( this.worldStateDB, @@ -326,7 +310,7 @@ export class AvmPersistableStateManager { this.log.verbose(`[AVM] Calling nested function ${functionName}`); this.trace.traceNestedCall( - nestedState.trace, + forkedState.trace, nestedEnvironment, startGasLeft, endGasLeft, @@ -336,8 +320,8 @@ export class AvmPersistableStateManager { ); } - public async processEnqueuedCall( - nestedState: AvmPersistableStateManager, + public async mergeStateForEnqueuedCall( + forkedState: AvmPersistableStateManager, /** The call request from private that enqueued this call. */ publicCallRequest: PublicCallRequest, /** The call's calldata */ @@ -346,7 +330,7 @@ export class AvmPersistableStateManager { reverted: boolean, ) { if (!reverted) { - this.acceptNestedCallState(nestedState); + this.acceptForkedState(forkedState); } const functionName = await getPublicFunctionDebugName( this.worldStateDB, @@ -357,12 +341,12 @@ export class AvmPersistableStateManager { this.log.verbose(`[AVM] Encountered enqueued public call starting with function ${functionName}`); - this.trace.traceEnqueuedCall(nestedState.trace, publicCallRequest, calldata, reverted); + this.trace.traceEnqueuedCall(forkedState.trace, publicCallRequest, calldata, reverted); } - public processEntireAppLogicPhase( + public mergeStateForPhase( /** The forked state manager used by app logic */ - nestedState: AvmPersistableStateManager, + forkedState: AvmPersistableStateManager, /** The call requests for each enqueued call in app logic. */ publicCallRequests: PublicCallRequest[], /** The calldatas for each enqueued call in app logic */ @@ -371,11 +355,11 @@ export class AvmPersistableStateManager { reverted: boolean, ) { if (!reverted) { - this.acceptNestedCallState(nestedState); + this.acceptForkedState(forkedState); } this.log.verbose(`[AVM] Encountered app logic phase`); - this.trace.traceAppLogicPhase(nestedState.trace, publicCallRequests, calldatas, reverted); + this.trace.traceExecutionPhase(forkedState.trace, publicCallRequests, calldatas, reverted); } } diff --git a/yarn-project/simulator/src/avm/journal/nullifiers.ts b/yarn-project/simulator/src/avm/journal/nullifiers.ts index fae33ec43579..d1b3577fffc7 100644 --- a/yarn-project/simulator/src/avm/journal/nullifiers.ts +++ b/yarn-project/simulator/src/avm/journal/nullifiers.ts @@ -22,13 +22,9 @@ export class NullifierManager { /** * Create a new nullifiers manager with some preloaded pending siloed nullifiers */ - public static newWithPendingSiloedNullifiers( - hostNullifiers: CommitmentsDB, - pendingSiloedNullifiers: Fr[], - parent?: NullifierManager, - ) { + public static newWithPendingSiloedNullifiers(hostNullifiers: CommitmentsDB, pendingSiloedNullifiers: Fr[]) { const cache = new NullifierCache(pendingSiloedNullifiers); - return new NullifierManager(hostNullifiers, cache, parent); + return new NullifierManager(hostNullifiers, cache); } /** diff --git a/yarn-project/simulator/src/mocks/fixtures.ts b/yarn-project/simulator/src/mocks/fixtures.ts index b575c23ab41c..76f7fdc7d039 100644 --- a/yarn-project/simulator/src/mocks/fixtures.ts +++ b/yarn-project/simulator/src/mocks/fixtures.ts @@ -1,40 +1,20 @@ -import { - type FunctionCall, - PublicExecutionRequest, - SimulationError, - UnencryptedFunctionL2Logs, -} from '@aztec/circuit-types'; -import { - ARGS_LENGTH, - AvmExecutionHints, - type AztecAddress, - CallContext, - type ContractStorageRead, - type ContractStorageUpdateRequest, - Fr, - Gas, -} from '@aztec/circuits.js'; +import { SimulationError } from '@aztec/circuit-types'; +import { ARGS_LENGTH, Fr, Gas } from '@aztec/circuits.js'; import { makeAztecAddress, makeSelector } from '@aztec/circuits.js/testing'; import { FunctionType } from '@aztec/foundation/abi'; import { padArrayEnd } from '@aztec/foundation/collection'; -import { type PublicExecutionResult, resultToPublicCallRequest } from '../public/execution.js'; +import { type EnqueuedPublicCallExecutionResult } from '../public/execution.js'; export class PublicExecutionResultBuilder { - private _executionRequest: PublicExecutionRequest; - private _nestedExecutions: PublicExecutionResult[] = []; - private _contractStorageUpdateRequests: ContractStorageUpdateRequest[] = []; - private _contractStorageReads: ContractStorageRead[] = []; private _returnValues: Fr[] = []; private _reverted = false; private _revertReason: SimulationError | undefined = undefined; - constructor(executionRequest: PublicExecutionRequest) { - this._executionRequest = executionRequest; - } + constructor() {} static empty(basicRevert = false) { - const builder = new PublicExecutionResultBuilder(PublicExecutionRequest.empty()); + const builder = new PublicExecutionResultBuilder(); if (basicRevert) { builder.withReverted(new SimulationError('Simulation failed', [])); } @@ -42,57 +22,14 @@ export class PublicExecutionResultBuilder { } static fromPublicExecutionRequest({ - request, returnValues = [new Fr(1n)], - nestedExecutions = [], - contractStorageUpdateRequests = [], - contractStorageReads = [], revertReason = undefined, }: { - request: PublicExecutionRequest; returnValues?: Fr[]; - nestedExecutions?: PublicExecutionResult[]; - contractStorageUpdateRequests?: ContractStorageUpdateRequest[]; - contractStorageReads?: ContractStorageRead[]; revertReason?: SimulationError; }): PublicExecutionResultBuilder { - const builder = new PublicExecutionResultBuilder(request); - - builder.withNestedExecutions(...nestedExecutions); - builder.withContractStorageUpdateRequest(...contractStorageUpdateRequests); - builder.withContractStorageRead(...contractStorageReads); - builder.withReturnValues(...returnValues); - if (revertReason) { - builder.withReverted(revertReason); - } - - return builder; - } - - static fromFunctionCall({ - from, - tx, - returnValues = [new Fr(1n)], - nestedExecutions = [], - contractStorageUpdateRequests = [], - contractStorageReads = [], - revertReason, - }: { - from: AztecAddress; - tx: FunctionCall; - returnValues?: Fr[]; - nestedExecutions?: PublicExecutionResult[]; - contractStorageUpdateRequests?: ContractStorageUpdateRequest[]; - contractStorageReads?: ContractStorageRead[]; - revertReason?: SimulationError; - }) { - const builder = new PublicExecutionResultBuilder( - new PublicExecutionRequest(new CallContext(from, tx.to, tx.selector, false), tx.args), - ); + const builder = new PublicExecutionResultBuilder(); - builder.withNestedExecutions(...nestedExecutions); - builder.withContractStorageUpdateRequest(...contractStorageUpdateRequests); - builder.withContractStorageRead(...contractStorageReads); builder.withReturnValues(...returnValues); if (revertReason) { builder.withReverted(revertReason); @@ -101,21 +38,6 @@ export class PublicExecutionResultBuilder { return builder; } - withNestedExecutions(...nested: PublicExecutionResult[]): PublicExecutionResultBuilder { - this._nestedExecutions.push(...nested); - return this; - } - - withContractStorageUpdateRequest(...request: ContractStorageUpdateRequest[]): PublicExecutionResultBuilder { - this._contractStorageUpdateRequests.push(...request); - return this; - } - - withContractStorageRead(...reads: ContractStorageRead[]): PublicExecutionResultBuilder { - this._contractStorageReads.push(...reads); - return this; - } - withReturnValues(...values: Fr[]): PublicExecutionResultBuilder { this._returnValues.push(...values); return this; @@ -127,34 +49,13 @@ export class PublicExecutionResultBuilder { return this; } - build(overrides: Partial = {}): PublicExecutionResult { + build(overrides: Partial = {}): EnqueuedPublicCallExecutionResult { return { - executionRequest: this._executionRequest, - nestedExecutions: this._nestedExecutions, - publicCallRequests: this._nestedExecutions.map(resultToPublicCallRequest), - noteHashReadRequests: [], - nullifierReadRequests: [], - nullifierNonExistentReadRequests: [], - l1ToL2MsgReadRequests: [], - contractStorageUpdateRequests: this._contractStorageUpdateRequests, - returnValues: padArrayEnd(this._returnValues, Fr.ZERO, 4), // TODO(#5450) Need to use the proper return values here - noteHashes: [], - nullifiers: [], - l2ToL1Messages: [], - contractStorageReads: [], - unencryptedLogsHashes: [], - unencryptedLogs: UnencryptedFunctionL2Logs.empty(), - allUnencryptedLogs: UnencryptedFunctionL2Logs.empty(), - startSideEffectCounter: Fr.ZERO, + endGasLeft: Gas.test(), endSideEffectCounter: Fr.ZERO, + returnValues: padArrayEnd(this._returnValues, Fr.ZERO, 4), // TODO(#5450) Need to use the proper return values here reverted: this._reverted, revertReason: this._revertReason, - startGasLeft: Gas.test(), - endGasLeft: Gas.test(), - transactionFee: Fr.ZERO, - calldata: [], - avmCircuitHints: AvmExecutionHints.empty(), - functionName: 'unknown', ...overrides, }; } 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 7ae4f334af4a..4572b26f9f63 100644 --- a/yarn-project/simulator/src/public/dual_side_effect_trace.ts +++ b/yarn-project/simulator/src/public/dual_side_effect_trace.ts @@ -14,7 +14,7 @@ import { assert } from 'console'; import { type AvmContractCallResult } from '../avm/avm_contract_call_result.js'; import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; import { type PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js'; -import { type PublicExecutionResult } from './execution.js'; +import { type EnqueuedPublicCallExecutionResultWithSideEffects, type PublicFunctionCallResult } from './execution.js'; import { type PublicSideEffectTrace } from './side_effect_trace.js'; import { type PublicSideEffectTraceInterface } from './side_effect_trace_interface.js'; @@ -160,7 +160,7 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface { ); } - public traceAppLogicPhase( + public traceExecutionPhase( /** The trace of the enqueued call. */ appLogicTrace: this, /** The call request from private that enqueued this call. */ @@ -170,13 +170,29 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface { /** Did the any enqueued call in app logic revert? */ reverted: boolean, ) { - this.enqueuedCallTrace.traceAppLogicPhase(appLogicTrace.enqueuedCallTrace, publicCallRequests, calldatas, reverted); + this.enqueuedCallTrace.traceExecutionPhase( + appLogicTrace.enqueuedCallTrace, + publicCallRequests, + calldatas, + reverted, + ); } /** * Convert this trace to a PublicExecutionResult for use externally to the simulator. */ - public toPublicExecutionResult( + public toPublicEnqueuedCallExecutionResult( + /** How much gas was left after this public execution. */ + endGasLeft: Gas, + /** The call's results */ + avmCallResults: AvmContractCallResult, + ): EnqueuedPublicCallExecutionResultWithSideEffects { + return this.enqueuedCallTrace.toPublicEnqueuedCallExecutionResult(endGasLeft, avmCallResults); + } + /** + * Convert this trace to a PublicExecutionResult for use externally to the simulator. + */ + public toPublicFunctionCallResult( /** The execution environment of the nested call. */ avmEnvironment: AvmExecutionEnvironment, /** How much gas was available for this public execution. */ @@ -189,8 +205,8 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface { avmCallResults: AvmContractCallResult, /** Function name for logging */ functionName: string = 'unknown', - ): PublicExecutionResult { - return this.innerCallTrace.toPublicExecutionResult( + ): PublicFunctionCallResult { + return this.innerCallTrace.toPublicFunctionCallResult( avmEnvironment, startGasLeft, endGasLeft, @@ -203,20 +219,23 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface { public toVMCircuitPublicInputs( /** Constants */ constants: CombinedConstantData, - /** The execution environment of the nested call. */ - avmEnvironment: AvmExecutionEnvironment, + /** The call request that triggered public execution. */ + callRequest: PublicCallRequest, /** How much gas was available for this public execution. */ startGasLeft: Gas, /** How much gas was left after this public execution. */ endGasLeft: Gas, + /** Transaction fee. */ + transactionFee: Fr, /** The call's results */ avmCallResults: AvmContractCallResult, ): VMCircuitPublicInputs { return this.enqueuedCallTrace.toVMCircuitPublicInputs( constants, - avmEnvironment, + callRequest, startGasLeft, endGasLeft, + transactionFee, avmCallResults, ); } 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 248ca7258270..b076c12cc3c4 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 @@ -1,6 +1,7 @@ import { UnencryptedL2Log } from '@aztec/circuit-types'; import { AztecAddress, + CallContext, CombinedConstantData, EthAddress, Gas, @@ -20,6 +21,7 @@ import { Nullifier, PublicAccumulatedData, PublicAccumulatedDataArrayLengths, + PublicCallRequest, PublicDataRead, PublicDataUpdateRequest, PublicValidationRequestArrayLengths, @@ -28,16 +30,30 @@ import { SerializableContractInstance, TreeLeafReadRequest, } from '@aztec/circuits.js'; -import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/circuits.js/hash'; +import { computePublicDataTreeLeafSlot, computeVarArgsHash, siloNullifier } from '@aztec/circuits.js/hash'; import { Fr } from '@aztec/foundation/fields'; import { randomBytes, randomInt } from 'crypto'; import { AvmContractCallResult } from '../avm/avm_contract_call_result.js'; +import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; import { initExecutionEnvironment } from '../avm/fixtures/index.js'; import { PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js'; import { SideEffectLimitReachedError } from './side_effect_errors.js'; +/** + * Helper function to create a public execution request from an AVM execution environment + */ +function createPublicCallRequest(avmEnvironment: AvmExecutionEnvironment): PublicCallRequest { + const callContext = CallContext.from({ + msgSender: avmEnvironment.sender, + contractAddress: avmEnvironment.address, + functionSelector: avmEnvironment.functionSelector, + isStaticCall: avmEnvironment.isStaticCall, + }); + return new PublicCallRequest(callContext, computeVarArgsHash(avmEnvironment.calldata), /*counter=*/ 0); +} + describe('Enqueued-call Side Effect Trace', () => { const address = Fr.random(); const utxo = Fr.random(); @@ -80,7 +96,14 @@ describe('Enqueued-call Side Effect Trace', () => { }); const toVMCircuitPublicInputs = (trc: PublicEnqueuedCallSideEffectTrace) => { - return trc.toVMCircuitPublicInputs(constants, avmEnvironment, startGasLeft, endGasLeft, avmCallResults); + return trc.toVMCircuitPublicInputs( + constants, + createPublicCallRequest(avmEnvironment), + startGasLeft, + endGasLeft, + transactionFee, + avmCallResults, + ); }; it('Should trace storage reads', () => { @@ -465,8 +488,8 @@ describe('Enqueued-call Side Effect Trace', () => { const parentSideEffects = trace.getSideEffects(); const childSideEffects = nestedTrace.getSideEffects(); if (callResults.reverted) { - expect(parentSideEffects.contractStorageReads).toEqual(childSideEffects.contractStorageReads); - expect(parentSideEffects.contractStorageUpdateRequests).toEqual(childSideEffects.contractStorageUpdateRequests); + expect(parentSideEffects.publicDataReads).toEqual(childSideEffects.publicDataReads); + expect(parentSideEffects.publicDataWrites).toEqual(childSideEffects.publicDataWrites); expect(parentSideEffects.noteHashReadRequests).toEqual(childSideEffects.noteHashReadRequests); expect(parentSideEffects.noteHashes).toEqual([]); expect(parentSideEffects.nullifierReadRequests).toEqual(childSideEffects.nullifierReadRequests); 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 d6e29607a198..370640f521e5 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 @@ -1,4 +1,4 @@ -import { UnencryptedL2Log } from '@aztec/circuit-types'; +import { UnencryptedFunctionL2Logs, UnencryptedL2Log } from '@aztec/circuit-types'; import { AvmContractBytecodeHints, AvmContractInstanceHint, @@ -7,11 +7,8 @@ import { AvmExternalCallHint, AvmKeyValueHint, AztecAddress, - CallContext, type CombinedConstantData, type ContractClassIdPreimage, - ContractStorageRead, - ContractStorageUpdateRequest, EthAddress, Gas, L2ToL1Message, @@ -49,7 +46,7 @@ import { TreeLeafReadRequest, VMCircuitPublicInputs, } from '@aztec/circuits.js'; -import { computePublicDataTreeLeafSlot, computeVarArgsHash, siloNullifier } from '@aztec/circuits.js/hash'; +import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/circuits.js/hash'; import { makeTuple } from '@aztec/foundation/array'; import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; @@ -57,7 +54,8 @@ import { createDebugLogger } from '@aztec/foundation/log'; import { type AvmContractCallResult } from '../avm/avm_contract_call_result.js'; import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; -import { type PublicExecutionResult } from './execution.js'; +import { createSimulationError } from '../common/errors.js'; +import { type EnqueuedPublicCallExecutionResultWithSideEffects, type PublicFunctionCallResult } from './execution.js'; import { SideEffectLimitReachedError } from './side_effect_errors.js'; import { type PublicSideEffectTraceInterface } from './side_effect_trace_interface.js'; @@ -69,8 +67,8 @@ import { type PublicSideEffectTraceInterface } from './side_effect_trace_interfa export type SideEffects = { enqueuedCalls: PublicCallRequest[]; - contractStorageReads: ContractStorageRead[]; - contractStorageUpdateRequests: ContractStorageUpdateRequest[]; + publicDataReads: PublicDataRead[]; + publicDataWrites: PublicDataUpdateRequest[]; noteHashReadRequests: TreeLeafReadRequest[]; noteHashes: ScopedNoteHash[]; @@ -96,9 +94,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI private sideEffectCounter: number; private enqueuedCalls: PublicCallRequest[] = []; - // TODO(dbanks12):remove contractStorageReads/writes - private contractStorageReads: ContractStorageRead[] = []; - private contractStorageUpdateRequests: ContractStorageUpdateRequest[] = []; + private publicDataReads: PublicDataRead[] = []; private publicDataWrites: PublicDataUpdateRequest[] = []; @@ -110,7 +106,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI private nullifiers: Nullifier[] = []; private l1ToL2MsgReadRequests: TreeLeafReadRequest[] = []; - private l2ToL1Msgs: ScopedL2ToL1Message[] = []; + private l2ToL1Messages: ScopedL2ToL1Message[] = []; private unencryptedLogs: UnencryptedL2Log[] = []; private unencryptedLogsHashes: ScopedLogHash[] = []; @@ -140,16 +136,16 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI this.previousValidationRequestArrayLengths.nullifierNonExistentReadRequests + this.nullifierNonExistentReadRequests.length, this.previousValidationRequestArrayLengths.l1ToL2MsgReadRequests + this.l1ToL2MsgReadRequests.length, - this.previousValidationRequestArrayLengths.publicDataReads + this.contractStorageReads.length, + this.previousValidationRequestArrayLengths.publicDataReads + this.publicDataReads.length, ), new PublicAccumulatedDataArrayLengths( this.previousAccumulatedDataArrayLengths.noteHashes + this.noteHashes.length, this.previousAccumulatedDataArrayLengths.nullifiers + this.nullifiers.length, - this.previousAccumulatedDataArrayLengths.l2ToL1Msgs + this.l2ToL1Msgs.length, + this.previousAccumulatedDataArrayLengths.l2ToL1Msgs + this.l2ToL1Messages.length, this.previousAccumulatedDataArrayLengths.noteEncryptedLogsHashes, this.previousAccumulatedDataArrayLengths.encryptedLogsHashes, this.previousAccumulatedDataArrayLengths.unencryptedLogsHashes + this.unencryptedLogsHashes.length, - this.previousAccumulatedDataArrayLengths.publicDataUpdateRequests + this.contractStorageUpdateRequests.length, + this.previousAccumulatedDataArrayLengths.publicDataUpdateRequests + this.publicDataWrites.length, this.previousAccumulatedDataArrayLengths.publicCallStack, ), ); @@ -166,17 +162,14 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI public tracePublicStorageRead(contractAddress: Fr, slot: Fr, value: Fr, _exists: boolean, _cached: boolean) { // NOTE: exists and cached are unused for now but may be used for optimizations or kernel hints later if ( - this.contractStorageReads.length + this.previousValidationRequestArrayLengths.publicDataReads >= + this.publicDataReads.length + this.previousValidationRequestArrayLengths.publicDataReads >= MAX_PUBLIC_DATA_READS_PER_TX ) { - throw new SideEffectLimitReachedError('contract storage read', MAX_PUBLIC_DATA_READS_PER_TX); + throw new SideEffectLimitReachedError('public data (contract storage) read', MAX_PUBLIC_DATA_READS_PER_TX); } const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot); this.publicDataReads.push(new PublicDataRead(leafSlot, value, this.sideEffectCounter)); - this.contractStorageReads.push( - new ContractStorageRead(slot, value, this.sideEffectCounter, AztecAddress.fromField(contractAddress)), - ); this.avmCircuitHints.storageValues.items.push( new AvmKeyValueHint(/*key=*/ new Fr(this.sideEffectCounter), /*value=*/ value), ); @@ -186,17 +179,17 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI public tracePublicStorageWrite(contractAddress: Fr, slot: Fr, value: Fr) { if ( - this.contractStorageUpdateRequests.length + this.previousAccumulatedDataArrayLengths.publicDataUpdateRequests >= + this.publicDataWrites.length + this.previousAccumulatedDataArrayLengths.publicDataUpdateRequests >= MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX ) { - throw new SideEffectLimitReachedError('contract storage write', MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX); + throw new SideEffectLimitReachedError( + 'public data (contract storage) write', + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ); } const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot); this.publicDataWrites.push(new PublicDataUpdateRequest(leafSlot, value, this.sideEffectCounter)); - this.contractStorageUpdateRequests.push( - new ContractStorageUpdateRequest(slot, value, this.sideEffectCounter, contractAddress), - ); this.log.debug(`SSTORE cnt: ${this.sideEffectCounter} val: ${value} slot: ${slot}`); this.incrementSideEffectCounter(); } @@ -225,6 +218,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI } // TODO(dbanks12): make unique and silo instead of scoping + //const siloedNoteHash = siloNoteHash(contractAddress, noteHash); this.noteHashes.push(new NoteHash(noteHash, this.sideEffectCounter).scope(AztecAddress.fromField(contractAddress))); this.log.debug(`NEW_NOTE_HASH cnt: ${this.sideEffectCounter}`); this.incrementSideEffectCounter(); @@ -234,8 +228,8 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI // NOTE: isPending and leafIndex are unused for now but may be used for optimizations or kernel hints later this.enforceLimitOnNullifierChecks(); - // TODO(dbanks12): use siloed nullifier instead of scoped - // Requires change to VMCircuitPublicInputs + // TODO(dbanks12): use siloed nullifier instead of scoped once public kernel stops siloing + // and once VM public inputs are meant to contain siloed nullifiers. //const siloedNullifier = siloNullifier(contractAddress, nullifier); const readRequest = new ReadRequest(nullifier, this.sideEffectCounter).scope( AztecAddress.fromField(contractAddress), @@ -280,12 +274,12 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI } public traceNewL2ToL1Message(contractAddress: Fr, recipient: Fr, content: Fr) { - if (this.l2ToL1Msgs.length + this.previousAccumulatedDataArrayLengths.l2ToL1Msgs >= MAX_L2_TO_L1_MSGS_PER_TX) { + if (this.l2ToL1Messages.length + this.previousAccumulatedDataArrayLengths.l2ToL1Msgs >= MAX_L2_TO_L1_MSGS_PER_TX) { throw new SideEffectLimitReachedError('l2 to l1 message', MAX_L2_TO_L1_MSGS_PER_TX); } const recipientAddress = EthAddress.fromField(recipient); - this.l2ToL1Msgs.push( + this.l2ToL1Messages.push( new L2ToL1Message(recipientAddress, content, this.sideEffectCounter).scope( AztecAddress.fromField(contractAddress), ), @@ -404,9 +398,9 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI // Since this trace function happens _after_ a nested call, such threshold limits must take // place in another trace function that occurs _before_ a nested call. if (avmCallResults.reverted) { - this.absorbRevertedNestedTrace(nestedCallTrace); + this.mergeRevertedForkedTrace(nestedCallTrace); } else { - this.absorbSuccessfulNestedTrace(nestedCallTrace); + this.mergeSuccessfulForkedTrace(nestedCallTrace); } const gasUsed = new Gas(startGasLeft.daGas - endGasLeft.daGas, startGasLeft.l2Gas - endGasLeft.l2Gas); @@ -442,18 +436,10 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI // Since this trace function happens _after_ a nested call, such threshold limits must take // place in another trace function that occurs _before_ a nested call. if (reverted) { - this.absorbRevertedNestedTrace(enqueuedCallTrace); + this.mergeRevertedForkedTrace(enqueuedCallTrace); } else { - this.absorbSuccessfulNestedTrace(enqueuedCallTrace); + this.mergeSuccessfulForkedTrace(enqueuedCallTrace); } - this.log.debug( - `tracing enqueued call: start side effect ${enqueuedCallTrace.startSideEffectCounter}, end: ${enqueuedCallTrace.sideEffectCounter}`, - ); - //if (enqueuedCallTrace.startSideEffectCounter == enqueuedCallTrace.sideEffectCounter) { - // // TODO(dbanks12): better debug msg and/or move this and clean up - // this.log.debug(`No side effects traced in enqueued call, so its side effect counter was never incremented. Incrementing paren't side effect counter now....`); - // this.incrementSideEffectCounter(); - //} this.enqueuedCalls.push(publicCallRequest); @@ -466,9 +452,9 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI * Trace an enqueued call. * Accept some results from a finished call's trace into this one. */ - public traceAppLogicPhase( + public traceExecutionPhase( /** The trace of the enqueued call. */ - appLogicTrace: this, + phaseTrace: this, /** The call request from private that enqueued this call. */ publicCallRequests: PublicCallRequest[], /** The call's calldata */ @@ -476,21 +462,14 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI /** Did the any enqueued call in app logic revert? */ reverted: boolean, ) { - // TODO(4805): check if some threshold is reached for max enqueued or nested calls (to unique contracts?) - // TODO(dbanks12): should emit a nullifier read request. There should be two thresholds. - // one for max unique contract calls, and another based on max nullifier reads. - // Since this trace function happens _after_ a nested call, such threshold limits must take - // place in another trace function that occurs _before_ a nested call. + // We only merge in enqueued calls here at the top-level + // because enqueued calls cannot enqueue others. + this.enqueuedCalls.push(...phaseTrace.enqueuedCalls); if (reverted) { - this.absorbRevertedAppLogicPhaseTrace(appLogicTrace); + this.mergeRevertedForkedTrace(phaseTrace); } else { - this.absorbSuccessfulAppLogicPhaseTrace(appLogicTrace); + this.mergeSuccessfulForkedTrace(phaseTrace); } - //if (appLogicTrace.startSideEffectCounter == appLogicTrace.sideEffectCounter) { - // // TODO(dbanks12): better debug msg and/or move this and clean up - // this.log.debug(`No side effects traced in enqueued call, so its side effect counter was never incremented. Incrementing paren't side effect counter now....`); - // this.incrementSideEffectCounter(); - //} for (let i = 0; i < publicCallRequests.length; i++) { this.enqueuedCalls.push(publicCallRequests[i]); @@ -501,26 +480,9 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI } } - public absorbSuccessfulAppLogicPhaseTrace(nestedTrace: this) { - // TODO(dbanks12): properly absorb hints from nested traces - this.enqueuedCalls.push(...nestedTrace.enqueuedCalls); - this.absorbSuccessfulNestedTrace(nestedTrace); - } - - public absorbRevertedAppLogicPhaseTrace(nestedTrace: this) { - // NOTE: Even on revert of an app-logic enqueued call, we need hints to prove that we reverted for valid reasons! - this.enqueuedCalls.push(...nestedTrace.enqueuedCalls); - this.absorbRevertedNestedTrace(nestedTrace); - } - - public absorbSuccessfulNestedTrace(nestedTrace: this) { - // NOTE: don't absorb enqueued calls! A call cannot enqueue another one, - // so only the top level trace instance can trace enqueued calls. - + private mergeSuccessfulForkedTrace(nestedTrace: this) { // TODO(dbanks12): accept & merge nested trace's hints! this.sideEffectCounter = nestedTrace.sideEffectCounter; - this.contractStorageReads.push(...nestedTrace.contractStorageReads); - this.contractStorageUpdateRequests.push(...nestedTrace.contractStorageUpdateRequests); this.publicDataReads.push(...nestedTrace.publicDataReads); this.publicDataWrites.push(...nestedTrace.publicDataWrites); this.noteHashReadRequests.push(...nestedTrace.noteHashReadRequests); @@ -529,12 +491,12 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI this.nullifierNonExistentReadRequests.push(...nestedTrace.nullifierNonExistentReadRequests); this.nullifiers.push(...nestedTrace.nullifiers); this.l1ToL2MsgReadRequests.push(...nestedTrace.l1ToL2MsgReadRequests); - this.l2ToL1Msgs.push(...nestedTrace.l2ToL1Msgs); + this.l2ToL1Messages.push(...nestedTrace.l2ToL1Messages); this.unencryptedLogs.push(...nestedTrace.unencryptedLogs); this.unencryptedLogsHashes.push(...nestedTrace.unencryptedLogsHashes); } - public absorbRevertedNestedTrace(nestedTrace: this) { + private mergeRevertedForkedTrace(nestedTrace: this) { // All read requests, and any writes (storage & nullifiers) that // require complex validation in public kernel (with end lifetimes) // must be absorbed even on revert. @@ -542,8 +504,6 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI // TODO(dbanks12): accept & merge nested trace's hints! // TODO(dbanks12): What should happen to side effect counter on revert? this.sideEffectCounter = nestedTrace.sideEffectCounter; - this.contractStorageReads.push(...nestedTrace.contractStorageReads); - this.contractStorageUpdateRequests.push(...nestedTrace.contractStorageUpdateRequests); this.publicDataReads.push(...nestedTrace.publicDataReads); this.publicDataWrites.push(...nestedTrace.publicDataWrites); this.noteHashReadRequests.push(...nestedTrace.noteHashReadRequests); @@ -559,39 +519,67 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI public getSideEffects(): SideEffects { return { enqueuedCalls: this.enqueuedCalls, - contractStorageReads: this.contractStorageReads, - contractStorageUpdateRequests: this.contractStorageUpdateRequests, + publicDataReads: this.publicDataReads, + publicDataWrites: this.publicDataWrites, noteHashReadRequests: this.noteHashReadRequests, noteHashes: this.noteHashes, nullifierReadRequests: this.nullifierReadRequests, nullifierNonExistentReadRequests: this.nullifierNonExistentReadRequests, nullifiers: this.nullifiers, l1ToL2MsgReadRequests: this.l1ToL2MsgReadRequests, - l2ToL1Msgs: this.l2ToL1Msgs, + l2ToL1Msgs: this.l2ToL1Messages, unencryptedLogs: this.unencryptedLogs, unencryptedLogsHashes: this.unencryptedLogsHashes, }; } + /** + * Get the results of public execution. + */ + public toPublicEnqueuedCallExecutionResult( + /** How much gas was left after this public execution. */ + endGasLeft: Gas, + /** The call's results */ + avmCallResults: AvmContractCallResult, + ): EnqueuedPublicCallExecutionResultWithSideEffects { + return { + endGasLeft, + endSideEffectCounter: new Fr(this.sideEffectCounter), + returnValues: avmCallResults.output, + reverted: avmCallResults.reverted, + revertReason: avmCallResults.revertReason ? createSimulationError(avmCallResults.revertReason, avmCallResults.output) : undefined, + sideEffects: { + publicDataWrites: this.publicDataWrites, + noteHashes: this.noteHashes, + nullifiers: this.nullifiers, + l2ToL1Messages: this.l2ToL1Messages, + unencryptedLogsHashes: this.unencryptedLogsHashes, // Scoped? + unencryptedLogs: new UnencryptedFunctionL2Logs(this.unencryptedLogs), + }, + }; + } + + /** + * Construct AVM circuit public inputs based on traced contents. + */ public toVMCircuitPublicInputs( /** Constants. */ constants: CombinedConstantData, - /** The execution environment of the nested call. */ - avmEnvironment: AvmExecutionEnvironment, + /** The call request that triggered public execution. */ + callRequest: PublicCallRequest, /** How much gas was available for this public execution. */ startGasLeft: Gas, /** How much gas was left after this public execution. */ endGasLeft: Gas, + /** Transaction fee. */ + transactionFee: Fr, /** The call's results */ avmCallResults: AvmContractCallResult, ): VMCircuitPublicInputs { - const callRequest = createPublicCallRequest(avmEnvironment); - const tempPublicCallStack = makeTuple(MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, PublicInnerCallRequest.empty); - //tempPublicCallStack[0].item = compress(callRequest); return new VMCircuitPublicInputs( /*constants=*/ constants, /*callRequest=*/ callRequest, - /*publicCallStack=*/ tempPublicCallStack, + /*publicCallStack=*/ makeTuple(MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, PublicInnerCallRequest.empty), /*previousValidationRequestArrayLengths=*/ this.previousValidationRequestArrayLengths, /*validationRequests=*/ this.getValidationRequests(), /*previousAccumulatedDataArrayLengths=*/ this.previousAccumulatedDataArrayLengths, @@ -599,12 +587,12 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI /*startSideEffectCounter=*/ this.startSideEffectCounter, /*endSideEffectCounter=*/ this.sideEffectCounter, /*startGasLeft=*/ startGasLeft, - /*transactionFee=*/ avmEnvironment.transactionFee, + /*transactionFee=*/ transactionFee, /*reverted=*/ avmCallResults.reverted, ); } - public toPublicExecutionResult( + public toPublicFunctionCallResult( /** The execution environment of the nested call. */ _avmEnvironment: AvmExecutionEnvironment, /** How much gas was available for this public execution. */ @@ -617,7 +605,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI _avmCallResults: AvmContractCallResult, /** Function name for logging */ _functionName: string = 'unknown', - ): PublicExecutionResult { + ): PublicFunctionCallResult { throw new Error('Not implemented'); } @@ -631,7 +619,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI private getValidationRequests() { return new PublicValidationRequests( - RollupValidationRequests.empty(), // TODO(dbanks12): what should this be? + RollupValidationRequests.empty(), padArrayEnd(this.noteHashReadRequests, TreeLeafReadRequest.empty(), MAX_NOTE_HASH_READ_REQUESTS_PER_TX), padArrayEnd(this.nullifierReadRequests, ScopedReadRequest.empty(), MAX_NULLIFIER_READ_REQUESTS_PER_TX), padArrayEnd( @@ -648,7 +636,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI return new PublicAccumulatedData( padArrayEnd(this.noteHashes, ScopedNoteHash.empty(), MAX_NOTE_HASHES_PER_TX), padArrayEnd(this.nullifiers, Nullifier.empty(), MAX_NULLIFIERS_PER_TX), - padArrayEnd(this.l2ToL1Msgs, ScopedL2ToL1Message.empty(), MAX_L2_TO_L1_MSGS_PER_TX), + padArrayEnd(this.l2ToL1Messages, ScopedL2ToL1Message.empty(), MAX_L2_TO_L1_MSGS_PER_TX), /*noteEncryptedLogsHashes=*/ makeTuple(MAX_NOTE_ENCRYPTED_LOGS_PER_TX, LogHash.empty), /*encryptedLogsHashes=*/ makeTuple(MAX_ENCRYPTED_LOGS_PER_TX, ScopedLogHash.empty), padArrayEnd(this.unencryptedLogsHashes, ScopedLogHash.empty(), MAX_UNENCRYPTED_LOGS_PER_TX), @@ -686,16 +674,3 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI } } } - -/** - * Helper function to create a public execution request from an AVM execution environment - */ -function createPublicCallRequest(avmEnvironment: AvmExecutionEnvironment): PublicCallRequest { - const callContext = CallContext.from({ - msgSender: avmEnvironment.sender, - contractAddress: avmEnvironment.address, - functionSelector: avmEnvironment.functionSelector, - isStaticCall: avmEnvironment.isStaticCall, - }); - return new PublicCallRequest(callContext, computeVarArgsHash(avmEnvironment.calldata), /*counter=*/ 0); -} diff --git a/yarn-project/simulator/src/public/enqueued_call_simulator.ts b/yarn-project/simulator/src/public/enqueued_call_simulator.ts index e62455717a6e..2f4be543a406 100644 --- a/yarn-project/simulator/src/public/enqueued_call_simulator.ts +++ b/yarn-project/simulator/src/public/enqueued_call_simulator.ts @@ -39,7 +39,6 @@ import { PublicCircuitPublicInputs, PublicInnerCallRequest, type PublicKernelCircuitPublicInputs, - PublicValidationRequestArrayLengths, ReadRequest, RevertCode, TreeLeafReadRequest, @@ -53,10 +52,18 @@ import { type MerkleTreeReadOperations } from '@aztec/world-state'; import { AvmContractCallResult } from '../avm/avm_contract_call_result.js'; import { type AvmPersistableStateManager } from '../avm/journal/journal.js'; -import { type PublicExecutionResult } from './execution.js'; +import { getPublicFunctionDebugName } from '../common/debug_fn_name.js'; +import { type EnqueuedPublicCallExecutionResult, type PublicFunctionCallResult } from './execution.js'; import { type PublicExecutor, createAvmExecutionEnvironment } from './executor.js'; +import { type WorldStateDB } from './public_db_sources.js'; -function makeAvmProvingRequest(inputs: PublicCircuitPublicInputs, result: PublicExecutionResult): AvmProvingRequest { +function emptyAvmProvingRequest(): AvmProvingRequest { + return { + type: ProvingRequestType.PUBLIC_VM, + inputs: AvmCircuitInputs.empty(), + }; +} +function makeAvmProvingRequest(inputs: PublicCircuitPublicInputs, result: PublicFunctionCallResult): AvmProvingRequest { return { type: ProvingRequestType.PUBLIC_VM, inputs: new AvmCircuitInputs(result.functionName, result.calldata, inputs, result.avmCircuitHints), @@ -74,19 +81,21 @@ export type EnqueuedCallResult = { returnValues: NestedProcessReturnValues; /** Gas used during the execution this enqueued call */ gasUsed: Gas; + /** Did call revert? */ + reverted: boolean; /** Revert reason, if any */ revertReason?: SimulationError; - /** Did call revert? */ - reverted?: boolean; }; export class EnqueuedCallSimulator { private log: DebugLogger; constructor( private db: MerkleTreeReadOperations, + private worldStateDB: WorldStateDB, private publicExecutor: PublicExecutor, private globalVariables: GlobalVariables, private historicalHeader: Header, + private realAvmProvingRequests: boolean, ) { this.log = createDebugLogger(`aztec:sequencer`); } @@ -106,44 +115,73 @@ export class EnqueuedCallSimulator { /*daGas=*/ availableGas.daGas, /*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_ENQUEUED_CALL), ); - // TODO(dbanks12): remove or properly use to update state manager on fork? const _pendingNullifiers = this.getSiloedPendingNullifiers(previousPublicKernelOutput); - const prevAccumulatedData = - phase === PublicKernelPhase.SETUP - ? previousPublicKernelOutput.endNonRevertibleData - : previousPublicKernelOutput.end; - const previousValidationRequestArrayLengths = PublicValidationRequestArrayLengths.new( - previousPublicKernelOutput.validationRequests, + const result = (await this.publicExecutor.simulate( + stateManager, + executionRequest, + this.globalVariables, + allocatedGas, + transactionFee, + )) as EnqueuedPublicCallExecutionResult; + + /////////////////////////////////////////////////////////////////////////// + // ALL TEMPORARY + const fnName = await getPublicFunctionDebugName( + this.worldStateDB, + executionRequest.callContext.contractAddress, + executionRequest.callContext.functionSelector, + executionRequest.args, ); - const previousAccumulatedDataArrayLengths = PublicAccumulatedDataArrayLengths.new(prevAccumulatedData); + + const avmExecutionEnv = createAvmExecutionEnvironment(executionRequest, this.globalVariables, transactionFee); + const avmCallResult = new AvmContractCallResult(result.reverted, result.returnValues); + + // Generate an AVM proving request + let avmProvingRequest: AvmProvingRequest; + if (this.realAvmProvingRequests) { + const deprecatedFunctionCallResult = stateManager.trace.toPublicFunctionCallResult( + avmExecutionEnv, + /*startGasLeft=*/ allocatedGas, + /*endGasLeft=*/ Gas.from(result.endGasLeft), + Buffer.alloc(0), + avmCallResult, + fnName, + ); + const callData = await this.getPublicCallData(deprecatedFunctionCallResult); + avmProvingRequest = makeAvmProvingRequest(callData.publicInputs, deprecatedFunctionCallResult); + } else { + avmProvingRequest = emptyAvmProvingRequest(); + } // If this is the first enqueued call in public, constants will be empty // because private kernel does not expose them. const constants = previousPublicKernelOutput.constants.clone(); constants.globalVariables = this.globalVariables; - const result = await this.publicExecutor.simulate( - stateManager, - executionRequest, - this.globalVariables, - allocatedGas, - transactionFee, - ); + // TODO(dbanks12): Since AVM circuit will be at the level of all enqueued calls in a TX, + // this public inputs generation will move up to the enqueued calls processor. const vmCircuitPublicInputs = stateManager.trace.toVMCircuitPublicInputs( constants, - createAvmExecutionEnvironment(executionRequest, constants.globalVariables, transactionFee), - allocatedGas, - result.endGasLeft, - new AvmContractCallResult(result.reverted, []), + callRequest, + /*startGasLeft=*/ allocatedGas, + /*endGasLeft=*/ Gas.from(result.endGasLeft), + transactionFee, + avmCallResult, ); - vmCircuitPublicInputs.callRequest.counter = callRequest.counter; - // TODO: FIX. For now, override this because it is hardcoded to be only non-revertible lengths in EnqueuedCallsProcessor + // FIXME(dbanks12): For now, override this because there is a disconnect with how the TS/simulator + // tracks "previous lengths" versus the kernel. The kernel uses "non revertible lengths" in SETUP + // and "revertible lengths" otherwise. TS also uses "non revertible lengths" in SETUP, but then + // uses _total_/combined lengths otherwise. + const prevAccumulatedData = + phase === PublicKernelPhase.SETUP + ? previousPublicKernelOutput.endNonRevertibleData + : previousPublicKernelOutput.end; + const previousAccumulatedDataArrayLengths = PublicAccumulatedDataArrayLengths.new(prevAccumulatedData); vmCircuitPublicInputs.previousAccumulatedDataArrayLengths = previousAccumulatedDataArrayLengths; - vmCircuitPublicInputs.previousValidationRequestArrayLengths = previousValidationRequestArrayLengths; + // END TEMPORARY + /////////////////////////////////////////////////////////////////////////// - const callData = await this.getPublicCallData(result); - const avmProvingRequest = makeAvmProvingRequest(callData.publicInputs, result); const gasUsed = allocatedGas.sub(Gas.from(result.endGasLeft)); return { @@ -152,8 +190,8 @@ export class EnqueuedCallSimulator { newUnencryptedLogs: new UnencryptedFunctionL2Logs(stateManager!.trace.getUnencryptedLogs()), returnValues: new NestedProcessReturnValues(result.returnValues), gasUsed, - revertReason: result.revertReason, reverted: result.reverted, + revertReason: result.revertReason, }; } @@ -168,13 +206,13 @@ export class EnqueuedCallSimulator { * @param result - The execution result. * @returns A corresponding PublicCallData object. */ - private async getPublicCallData(result: PublicExecutionResult) { + private async getPublicCallData(result: PublicFunctionCallResult) { const bytecodeHash = await this.getBytecodeHash(result); const publicInputs = await this.getPublicCircuitPublicInputs(result); return new PublicCallData(publicInputs, makeEmptyProof(), bytecodeHash); } - private async getPublicCircuitPublicInputs(result: PublicExecutionResult) { + private async getPublicCircuitPublicInputs(result: PublicFunctionCallResult) { const publicDataTreeInfo = await this.db.getTreeInfo(MerkleTreeId.PUBLIC_DATA_TREE); this.historicalHeader.state.partial.publicDataTree.root = Fr.fromBuffer(publicDataTreeInfo.root); @@ -261,7 +299,7 @@ export class EnqueuedCallSimulator { }); } - private getBytecodeHash(_result: PublicExecutionResult) { + private getBytecodeHash(_result: PublicFunctionCallResult) { // TODO: Determine how to calculate bytecode hash. Circuits just check it isn't zero for now. // See https://github.com/AztecProtocol/aztec3-packages/issues/378 const bytecodeHash = new Fr(1n); diff --git a/yarn-project/simulator/src/public/enqueued_calls_processor.test.ts b/yarn-project/simulator/src/public/enqueued_calls_processor.test.ts index 64342ea6a7a8..d7e681b1dade 100644 --- a/yarn-project/simulator/src/public/enqueued_calls_processor.test.ts +++ b/yarn-project/simulator/src/public/enqueued_calls_processor.test.ts @@ -30,7 +30,7 @@ import { type AvmPersistableStateManager } from '../avm/journal/journal.js'; import { PublicExecutionResultBuilder } from '../mocks/fixtures.js'; import { WASMSimulator } from '../providers/acvm_wasm.js'; import { EnqueuedCallsProcessor } from './enqueued_calls_processor.js'; -import { type PublicExecutionResult } from './execution.js'; +import { type EnqueuedPublicCallExecutionResult } from './execution.js'; import { type PublicExecutor } from './executor.js'; import { type WorldStateDB } from './public_db_sources.js'; import { RealPublicKernelCircuitSimulator } from './public_kernel.js'; @@ -89,6 +89,7 @@ describe('enqueued_calls_processor', () => { GlobalVariables.from({ ...GlobalVariables.empty(), gasFees: GasFees.default() }), Header.empty(), worldStateDB, + /*realAvmProvingRequest=*/ false, ); }); @@ -99,8 +100,8 @@ describe('enqueued_calls_processor', () => { hasLogs: true, }); - publicExecutor.simulate.mockImplementation((_stateManager, request) => { - const result = PublicExecutionResultBuilder.fromPublicExecutionRequest({ request }).build(); + publicExecutor.simulate.mockImplementation(_stateManager => { + const result = PublicExecutionResultBuilder.empty().build(); return Promise.resolve(result); }); @@ -133,10 +134,8 @@ describe('enqueued_calls_processor', () => { const nonRevertibleRequests = tx.getNonRevertiblePublicExecutionRequests(); const revertibleRequests = tx.getRevertiblePublicExecutionRequests(); - const teardownRequest = tx.getPublicTeardownExecutionRequest()!; const teardownGas = tx.data.constants.txContext.gasSettings.getTeardownLimits(); - const teardownResultSettings = { startGasLeft: teardownGas, endGasLeft: teardownGas }; const nestedContractAddress = AztecAddress.fromBigInt(112233n); const contractSlotA = fr(0x100); @@ -144,23 +143,13 @@ describe('enqueued_calls_processor', () => { const contractSlotC = fr(0x200); const teardownFailure = new SimulationError('Simulation Failed in teardown', []); - const simulatorResults: PublicExecutionResult[] = [ + const simulatorResults: EnqueuedPublicCallExecutionResult[] = [ // Setup - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: nonRevertibleRequests[0], - }).build(), - + PublicExecutionResultBuilder.empty().build(), // App Logic - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: revertibleRequests[0], - }).build(), - + PublicExecutionResultBuilder.empty().build(), // Teardown - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: teardownRequest, - // revert at top-level of teardown enqueued call - revertReason: teardownFailure, - }).build(teardownResultSettings), + PublicExecutionResultBuilder.empty().withReverted(teardownFailure).build({ endGasLeft: teardownGas }), ]; const mockedSimulatorExecutions = [ // SETUP @@ -188,7 +177,7 @@ describe('enqueued_calls_processor', () => { for (const executeSimulator of mockedSimulatorExecutions) { publicExecutor.simulate.mockImplementationOnce( - (stateManager: AvmPersistableStateManager): Promise => { + (stateManager: AvmPersistableStateManager): Promise => { return executeSimulator(stateManager); }, ); @@ -240,8 +229,6 @@ describe('enqueued_calls_processor', () => { }); const nonRevertibleRequests = tx.getNonRevertiblePublicExecutionRequests(); - const revertibleRequests = tx.getRevertiblePublicExecutionRequests(); - const teardownRequest = tx.getPublicTeardownExecutionRequest()!; const nestedContractAddress = AztecAddress.fromBigInt(112233n); const contractSlotA = fr(0x100); @@ -250,22 +237,13 @@ describe('enqueued_calls_processor', () => { const setupFailureMsg = 'Simulation Failed in setup'; const setupFailure = new SimulationError(setupFailureMsg, []); - const simulatorResults: PublicExecutionResult[] = [ + const simulatorResults: EnqueuedPublicCallExecutionResult[] = [ // Setup - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: nonRevertibleRequests[0], - revertReason: setupFailure, - }).build(), - + PublicExecutionResultBuilder.empty().withReverted(setupFailure).build(), // App Logic - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: revertibleRequests[0], - }).build(), - + PublicExecutionResultBuilder.empty().build(), // Teardown - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: teardownRequest, - }).build(), + PublicExecutionResultBuilder.empty().build(), ]; const mockedSimulatorExecutions = [ // SETUP @@ -290,7 +268,7 @@ describe('enqueued_calls_processor', () => { for (const executeSimulator of mockedSimulatorExecutions) { publicExecutor.simulate.mockImplementationOnce( - (stateManager: AvmPersistableStateManager): Promise => { + (stateManager: AvmPersistableStateManager): Promise => { return executeSimulator(stateManager); }, ); @@ -312,11 +290,8 @@ describe('enqueued_calls_processor', () => { }); const nonRevertibleRequests = tx.getNonRevertiblePublicExecutionRequests(); - const revertibleRequests = tx.getRevertiblePublicExecutionRequests(); - const teardownRequest = tx.getPublicTeardownExecutionRequest()!; const teardownGas = tx.data.constants.txContext.gasSettings.getTeardownLimits(); - const teardownResultSettings = { startGasLeft: teardownGas, endGasLeft: teardownGas }; const nestedContractAddress = AztecAddress.fromBigInt(112233n); const contractSlotA = fr(0x100); @@ -327,22 +302,13 @@ describe('enqueued_calls_processor', () => { const contractSlotF = fr(0x350); const appLogicFailure = new SimulationError('Simulation Failed in app logic', []); - const simulatorResults: PublicExecutionResult[] = [ + const simulatorResults: EnqueuedPublicCallExecutionResult[] = [ // Setup - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: nonRevertibleRequests[0], - }).build(), - + PublicExecutionResultBuilder.empty().build(), // App Logic - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: revertibleRequests[0], - revertReason: appLogicFailure, - }).build(), - + PublicExecutionResultBuilder.empty().withReverted(appLogicFailure).build(), // Teardown - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: teardownRequest, - }).build(teardownResultSettings), + PublicExecutionResultBuilder.empty().build({ endGasLeft: teardownGas }), ]; const mockedSimulatorExecutions = [ @@ -372,7 +338,7 @@ describe('enqueued_calls_processor', () => { for (const executeSimulator of mockedSimulatorExecutions) { publicExecutor.simulate.mockImplementationOnce( - (stateManager: AvmPersistableStateManager): Promise => { + (stateManager: AvmPersistableStateManager): Promise => { return executeSimulator(stateManager); }, ); @@ -437,10 +403,8 @@ describe('enqueued_calls_processor', () => { const nonRevertibleRequests = tx.getNonRevertiblePublicExecutionRequests(); const revertibleRequests = tx.getRevertiblePublicExecutionRequests(); - const teardownRequest = tx.getPublicTeardownExecutionRequest()!; const teardownGas = tx.data.constants.txContext.gasSettings.getTeardownLimits(); - const teardownResultSettings = { startGasLeft: teardownGas, endGasLeft: teardownGas }; const nestedContractAddress = AztecAddress.fromBigInt(112233n); const contractSlotA = fr(0x100); @@ -449,25 +413,13 @@ describe('enqueued_calls_processor', () => { const appLogicFailure = new SimulationError('Simulation Failed in app logic', []); const teardownFailure = new SimulationError('Simulation Failed in teardown', []); - const simulatorResults: PublicExecutionResult[] = [ + const simulatorResults: EnqueuedPublicCallExecutionResult[] = [ // Setup - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: nonRevertibleRequests[0], - }).build(), - + PublicExecutionResultBuilder.empty().build(), // App Logic - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: revertibleRequests[0], - // revert at top-level of enqueued call - revertReason: appLogicFailure, - }).build(), - + PublicExecutionResultBuilder.empty().withReverted(appLogicFailure).build(), // Teardown - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: teardownRequest, - // revert at top-level of enqueued call - revertReason: teardownFailure, - }).build(teardownResultSettings), + PublicExecutionResultBuilder.empty().withReverted(teardownFailure).build({ endGasLeft: teardownGas }), ]; const mockedSimulatorExecutions = [ @@ -496,7 +448,7 @@ describe('enqueued_calls_processor', () => { for (const executeSimulator of mockedSimulatorExecutions) { publicExecutor.simulate.mockImplementationOnce( - (stateManager: AvmPersistableStateManager): Promise => { + (stateManager: AvmPersistableStateManager): Promise => { return executeSimulator(stateManager); }, ); @@ -549,9 +501,7 @@ describe('enqueued_calls_processor', () => { hasPublicTeardownCallRequest: true, }); - const nonRevertibleRequests = tx.getNonRevertiblePublicExecutionRequests(); const revertibleRequests = tx.getRevertiblePublicExecutionRequests(); - const teardownRequest = tx.getPublicTeardownExecutionRequest()!; // Keep gas numbers MAX_L2_GAS_PER_ENQUEUED_CALL or the logic below has to get weird const gasLimits = Gas.from({ l2Gas: 1e6, daGas: 1e6 }); @@ -599,30 +549,24 @@ describe('enqueued_calls_processor', () => { tx.data.constants.txContext.gasSettings.inclusionFee.toNumber() + expectedTotalGasUsed.l2Gas * 1 + expectedTotalGasUsed.daGas * 1; - const transactionFee = new Fr(expectedTxFee); - const simulatorResults: PublicExecutionResult[] = [ + const simulatorResults: EnqueuedPublicCallExecutionResult[] = [ // Setup - PublicExecutionResultBuilder.fromPublicExecutionRequest({ request: nonRevertibleRequests[0] }).build({ - startGasLeft: initialGas, + PublicExecutionResultBuilder.empty().build({ + // starts with initialGas, ends with endGasLeft: afterSetupGas, }), // App Logic - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: revertibleRequests[0], - }).build({ - startGasLeft: afterSetupGas, + PublicExecutionResultBuilder.empty().build({ + // starts with afterSetupGas, ends with endGasLeft: afterAppGas, }), // Teardown - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: teardownRequest, - }).build({ - startGasLeft: teardownGas, + PublicExecutionResultBuilder.empty().build({ + // starts with tearDownGas, ends with endGasLeft: afterTeardownGas, - transactionFee, }), ]; @@ -635,9 +579,9 @@ describe('enqueued_calls_processor', () => { // APP LOGIC async (stateManager: AvmPersistableStateManager) => { // mock storage writes on the state manager - stateManager.writeStorage(revertibleRequests[0].callContext.contractAddress, contractSlotA, fr(0x101)); - stateManager.writeStorage(revertibleRequests[0].callContext.contractAddress, contractSlotB, fr(0x151)); - await stateManager.readStorage(revertibleRequests[0].callContext.contractAddress, contractSlotA); + stateManager.writeStorage(contractAddress, contractSlotA, fr(0x101)); + stateManager.writeStorage(contractAddress, contractSlotB, fr(0x151)); + await stateManager.readStorage(contractAddress, contractSlotA); return Promise.resolve(simulatorResults[1]); }, async (stateManager: AvmPersistableStateManager) => { @@ -654,7 +598,7 @@ describe('enqueued_calls_processor', () => { for (const executeSimulator of mockedSimulatorExecutions) { publicExecutor.simulate.mockImplementationOnce( - (stateManager: AvmPersistableStateManager): Promise => { + (stateManager: AvmPersistableStateManager): Promise => { return executeSimulator(stateManager); }, ); @@ -675,11 +619,9 @@ describe('enqueued_calls_processor', () => { const expectedSimulateCall = (availableGas: Partial>, txFee: number) => [ expect.anything(), // AvmPersistableStateManager - expect.anything(), // PublicExecution + expect.anything(), // PublicExecutionRequest expect.anything(), // GlobalVariables Gas.from(availableGas), - expect.anything(), // TxContext - expect.anything(), // pendingNullifiers new Fr(txFee), ]; @@ -726,8 +668,6 @@ describe('enqueued_calls_processor', () => { hasPublicTeardownCallRequest: true, }); - const teardownRequest = tx.getPublicTeardownExecutionRequest()!; - const gasLimits = Gas.from({ l2Gas: 1e9, daGas: 1e9 }); const teardownGas = Gas.from({ l2Gas: 1e7, daGas: 1e7 }); tx.data.constants.txContext.gasSettings = GasSettings.from({ @@ -747,19 +687,13 @@ describe('enqueued_calls_processor', () => { .withGasUsed(Gas.empty()) .build(); - const txOverhead = 1e4; - const expectedTxFee = txOverhead + teardownGas.l2Gas * 1 + teardownGas.daGas * 1; - const transactionFee = new Fr(expectedTxFee); const teardownGasUsed = Gas.from({ l2Gas: 1e6, daGas: 1e6 }); - const simulatorResults: PublicExecutionResult[] = [ + const simulatorResults: EnqueuedPublicCallExecutionResult[] = [ // Teardown - PublicExecutionResultBuilder.fromPublicExecutionRequest({ - request: teardownRequest, - }).build({ - startGasLeft: teardownGas, + PublicExecutionResultBuilder.empty().build({ + // starts with tearDownGas, ends with endGasLeft: teardownGas.sub(teardownGasUsed), - transactionFee, }), ]; diff --git a/yarn-project/simulator/src/public/enqueued_calls_processor.ts b/yarn-project/simulator/src/public/enqueued_calls_processor.ts index d4596cd5d1ec..3f13b35e1109 100644 --- a/yarn-project/simulator/src/public/enqueued_calls_processor.ts +++ b/yarn-project/simulator/src/public/enqueued_calls_processor.ts @@ -59,6 +59,8 @@ type PublicPhaseResult = { gasUsed: Gas; /** Time spent for the execution this phase */ durationMs: number; + /** Reverted */ + reverted: boolean; /** Revert reason, if any */ revertReason?: SimulationError; }; @@ -102,8 +104,16 @@ export class EnqueuedCallsProcessor { globalVariables: GlobalVariables, historicalHeader: Header, worldStateDB: WorldStateDB, + realAvmProvingRequests: boolean = true, ) { - const enqueuedCallSimulator = new EnqueuedCallSimulator(db, publicExecutor, globalVariables, historicalHeader); + const enqueuedCallSimulator = new EnqueuedCallSimulator( + db, + worldStateDB, + publicExecutor, + globalVariables, + historicalHeader, + realAvmProvingRequests, + ); const publicKernelTailSimulator = PublicKernelTailSimulator.create(db, publicKernelSimulator); @@ -162,59 +172,48 @@ export class EnqueuedCallsProcessor { let returnValues: NestedProcessReturnValues[] = []; let revertReason: SimulationError | undefined; - // TODO(dbanks12): need to emit TX nullifier if there was no private execution? - // Dow we also need to check its existence? - const pendingNullifiers = [ - ...publicKernelOutput.end.nullifiers, - ...publicKernelOutput.endNonRevertibleData.nullifiers, - ] + const nonRevertibleNullifiersFromPrivate = publicKernelOutput.endNonRevertibleData.nullifiers .filter(n => !n.isEmpty()) .map(n => n.value); - // TODO(dbanks12): cleanup - const prevAccumulatedData = - //phase === PublicKernelPhase.SETUP ? - publicKernelOutput.endNonRevertibleData; - //: publicKernelOutput.end; + const _revertibleNullifiersFromPrivate = publicKernelOutput.end.nullifiers + .filter(n => !n.isEmpty()) + .map(n => n.value); + + // During SETUP, non revertible side effects from private are our "previous data" + const prevAccumulatedData = publicKernelOutput.endNonRevertibleData; const previousValidationRequestArrayLengths = PublicValidationRequestArrayLengths.new( publicKernelOutput.validationRequests, ); const previousAccumulatedDataArrayLengths = PublicAccumulatedDataArrayLengths.new(prevAccumulatedData); - // TODO(dbanks12): if an enqueued call has startSideEffectCounter == endSideEffectCounter, - // then we need to make sure to increment on the next fork - const innerCallTrace = new PublicSideEffectTrace(1); + const innerCallTrace = new PublicSideEffectTrace(); const enqueuedCallTrace = new PublicEnqueuedCallSideEffectTrace( - /*startSideEffectCounter=*/ 1, + /*startSideEffectCounter=*/ 0, previousValidationRequestArrayLengths, previousAccumulatedDataArrayLengths, ); const trace = new DualSideEffectTrace(innerCallTrace, enqueuedCallTrace); - // TODO(dbanks12): do we need to pass pending note hashes, messages, logs here too? - // - // Right now we are creating one top level state manager, - // and then forking it per enqueued call. - // - // We want to store a checkpoint at the start of app logic that - // is rolled back to if any enqaueued call in app logic reverts. + + // Transaction level state manager that will be forked for revertible phases. const txStateManager = AvmPersistableStateManager.newWithPendingSiloedNullifiers( this.worldStateDB, trace, - pendingNullifiers, + nonRevertibleNullifiersFromPrivate, ); + // TODO(dbanks12): insert all non-revertible side effects from private here. + for (let i = 0; i < phases.length; i++) { - // TODO(dbanks12): cleanup const phase = phases[i]; - // If in app logic, fork the state so that if ANY app-logic enqueued call reverts, - // we can rollback to the start of app logic. - // This effectively snapshots the state at the end of setup (start of app-logic) - // so that we can rollback to it before teardown if app-logic reverts. - // NOTE: should actually be able to fork for entire revertible section (app + teardown), but - // then we need to call `processEntireAppLogicPhase` with all callRequests & executionRequests - // across both of those phases. - //const stateManagerForPhase = phase === PublicKernelPhase.SETUP ? txStateManager : txStateManager.fork(); - // Teardown is revertible, but will run even if app logic reverts! - this.log.debug(`start of phase, end counter from previousKernel: ${publicKernelOutput.endSideEffectCounter}`); - const stateManagerForPhase = phase !== PublicKernelPhase.SETUP ? txStateManager.fork() : txStateManager; + let stateManagerForPhase: AvmPersistableStateManager; + if (phase === PublicKernelPhase.SETUP) { + // don't need to fork for setup since it's non-revertible + // (if setup fails, transaction is thrown out) + stateManagerForPhase = txStateManager; + } else { + // Fork the state manager so that we can rollback state if a revertible phase reverts. + stateManagerForPhase = txStateManager.fork(); + // NOTE: Teardown is revertible, but will run even if app logic reverts! + } const callRequests = EnqueuedCallsProcessor.getCallRequestsByPhase(tx, phase); if (callRequests.length) { const executionRequests = EnqueuedCallsProcessor.getExecutionRequestsByPhase(tx, phase); @@ -243,8 +242,7 @@ export class EnqueuedCallsProcessor { } if (phase !== PublicKernelPhase.SETUP) { - // TODO(dbanks12): do we need to do this if we already did this for each enqueued call fork? - txStateManager.processEntireAppLogicPhase( + txStateManager.mergeStateForPhase( stateManagerForPhase, callRequests, executionRequests.map(req => req.args), @@ -262,10 +260,6 @@ export class EnqueuedCallsProcessor { revertReason ??= result.revertReason; } - let j = 0; - for (const req of enqueuedCallTrace.getSideEffects().contractStorageUpdateRequests) { - this.log.debug(`Storage update request ${j++}: ${JSON.stringify(req)}`); - } } const tailKernelOutput = await this.publicKernelTailSimulator.simulate(publicKernelOutput).catch( @@ -302,6 +296,7 @@ export class EnqueuedCallsProcessor { let avmProvingRequest: AvmProvingRequest; let publicKernelOutput = previousPublicKernelOutput; let gasUsed = Gas.empty(); + let reverted: boolean = false; let revertReason: SimulationError | undefined; for (let i = callRequests.length - 1; i >= 0 && !revertReason; i--) { const callRequest = callRequests[i]; @@ -336,7 +331,7 @@ export class EnqueuedCallsProcessor { ); throw enqueuedCallResult.revertReason; } - await txStateManager.processEnqueuedCall( + await txStateManager.mergeStateForEnqueuedCall( enqueuedCallStateManager, callRequest, executionRequest.args, @@ -346,6 +341,7 @@ export class EnqueuedCallsProcessor { avmProvingRequest = enqueuedCallResult.avmProvingRequest; returnValues.push(enqueuedCallResult.returnValues); gasUsed = gasUsed.add(enqueuedCallResult.gasUsed); + reverted = enqueuedCallResult.reverted; revertReason ??= enqueuedCallResult.revertReason; // Instead of operating on worldStateDB here, do we do AvmPersistableStateManager.revert() or return()? @@ -357,12 +353,9 @@ export class EnqueuedCallsProcessor { // Are we reverting here back to end of non-revertible insertions? // What are we reverting back to? await this.worldStateDB.removeNewContracts(tx); - //await this.worldStateDB.rollbackToCheckpoint(); - // TODO(dbanks12): shouldn't be necessary since side effect trace should handle this tx.filterRevertedLogs(publicKernelOutput); } else { // TODO(#6470): we should be adding contracts deployed in those logs to the publicContractsDB - // TODO(dbanks12): shouldn't be necessary since side effect trace should handle this tx.unencryptedLogs.addFunctionLogs([enqueuedCallResult.newUnencryptedLogs]); } @@ -375,16 +368,13 @@ export class EnqueuedCallsProcessor { isFromPrivate = false; } - //if (phase === PublicKernelPhase.SETUP) { - // await this.worldStateDB.checkpoint(); - //} - return { avmProvingRequest: avmProvingRequest!, publicKernelOutput, durationMs: phaseTimer.ms(), gasUsed, returnValues: returnValues, + reverted: reverted, revertReason, }; } @@ -437,8 +427,6 @@ export class EnqueuedCallsProcessor { const inputs = new PublicKernelCircuitPrivateInputs(previousKernel, callData); - this.log.debug(`previousKernel end counter: ${previousKernel.publicInputs.endSideEffectCounter}`); - this.log.debug(`enqueued call start counter: ${enqueuedCallData.startSideEffectCounter}`); return await this.publicKernelSimulator.publicKernelCircuitMerge(inputs); } diff --git a/yarn-project/simulator/src/public/execution.ts b/yarn-project/simulator/src/public/execution.ts index 2efac0c35dc1..e6fe8c174575 100644 --- a/yarn-project/simulator/src/public/execution.ts +++ b/yarn-project/simulator/src/public/execution.ts @@ -14,17 +14,73 @@ import { type NoteHash, type Nullifier, PublicCallStackItemCompressed, + type PublicDataUpdateRequest, PublicInnerCallRequest, type ReadRequest, RevertCode, + type ScopedL2ToL1Message, + type ScopedLogHash, + type ScopedNoteHash, type TreeLeafReadRequest, } from '@aztec/circuits.js'; import { computeVarArgsHash } from '@aztec/circuits.js/hash'; +export interface PublicSideEffects { + /** The contract storage update requests performed. */ + publicDataWrites: PublicDataUpdateRequest[]; + /** The new note hashes to be inserted into the note hashes tree. */ + noteHashes: ScopedNoteHash[]; + /** The new nullifiers to be inserted into the nullifier tree. */ + nullifiers: Nullifier[]; + /** The new l2 to l1 messages generated to be inserted into the messages tree. */ + l2ToL1Messages: ScopedL2ToL1Message[]; + /** + * The hashed logs with side effect counter. + * Note: required as we don't track the counter anywhere else. + */ + unencryptedLogsHashes: ScopedLogHash[]; + /** + * Unencrypted logs emitted during execution. + * Note: These are preimages to `unencryptedLogsHashes`. + */ + unencryptedLogs: UnencryptedFunctionL2Logs; +} + +export interface EnqueuedPublicCallExecutionResult { + /** How much gas was left after this public execution. */ + endGasLeft: Gas; + /** The side effect counter after execution */ + endSideEffectCounter: Fr; + + /** The return values of the function. */ + returnValues: Fr[]; + /** Whether the execution reverted. */ + reverted: boolean; + /** The revert reason if the execution reverted. */ + revertReason?: SimulationError; +} + +export interface EnqueuedPublicCallExecutionResultWithSideEffects { + /** How much gas was left after this public execution. */ + endGasLeft: Gas; + /** The side effect counter after execution */ + endSideEffectCounter: Fr; + + /** The return values of the function. */ + returnValues: Fr[]; + /** Whether the execution reverted. */ + reverted: boolean; + /** The revert reason if the execution reverted. */ + revertReason?: SimulationError; + + /** The public side effects of the function. */ + sideEffects: PublicSideEffects; +} + /** * The public function execution result. */ -export interface PublicExecutionResult { +export interface PublicFunctionCallResult { /** The execution request that triggered this result. */ executionRequest: PublicExecutionRequest; @@ -96,7 +152,7 @@ export interface PublicExecutionResult { functionName: string; } -export function resultToPublicCallRequest(result: PublicExecutionResult) { +export function resultToPublicCallRequest(result: PublicFunctionCallResult) { const request = result.executionRequest; const item = new PublicCallStackItemCompressed( request.callContext.contractAddress, diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index 60c05724c2e5..3a90b863bdb6 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -13,7 +13,10 @@ import { AvmPersistableStateManager } from '../avm/journal/index.js'; import { getPublicFunctionDebugName } from '../common/debug_fn_name.js'; import { DualSideEffectTrace } from './dual_side_effect_trace.js'; import { PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js'; -import { type PublicExecutionResult } from './execution.js'; +import { + type EnqueuedPublicCallExecutionResult, + type EnqueuedPublicCallExecutionResultWithSideEffects, +} from './execution.js'; import { ExecutorMetrics } from './executor_metrics.js'; import { type WorldStateDB } from './public_db_sources.js'; import { PublicSideEffectTrace } from './side_effect_trace.js'; @@ -31,12 +34,12 @@ export class PublicExecutor { static readonly log = createDebugLogger('aztec:simulator:public_executor'); /** - * Executes a public execution request. + * Simulate a public execution request. * @param executionRequest - The execution to run. * @param globalVariables - The global variables to use. * @param allocatedGas - The gas available at the start of this enqueued call. * @param transactionFee - Fee offered for this TX. - * @returns The result of execution, including the results of all nested calls. + * @returns The result of execution. */ public async simulate( stateManager: AvmPersistableStateManager, @@ -44,10 +47,9 @@ export class PublicExecutor { globalVariables: GlobalVariables, allocatedGas: Gas, transactionFee: Fr = Fr.ZERO, - ): Promise { + ): Promise { const address = executionRequest.callContext.contractAddress; const selector = executionRequest.callContext.functionSelector; - // TODO(dbanks12): move function name debugging elsewhere? or add it to state manager? const fnName = await getPublicFunctionDebugName(this.worldStateDB, address, selector, executionRequest.args); PublicExecutor.log.verbose( @@ -60,12 +62,12 @@ export class PublicExecutor { const avmMachineState = new AvmMachineState(allocatedGas); const avmContext = new AvmContext(stateManager, avmExecutionEnv, avmMachineState); const simulator = new AvmSimulator(avmContext); - const avmResult = await simulator.execute(); + const avmCallResult = await simulator.execute(); const bytecode = simulator.getBytecode()!; PublicExecutor.log.verbose( - `[AVM] ${fnName} returned, reverted: ${avmResult.reverted}${ - avmResult.reverted ? ', reason: ' + avmResult.revertReason : '' + `[AVM] ${fnName} returned, reverted: ${avmCallResult.reverted}${ + avmCallResult.reverted ? ', reason: ' + avmCallResult.revertReason : '' }.`, { eventName: 'avm-simulation', @@ -75,23 +77,19 @@ export class PublicExecutor { } satisfies AvmSimulationStats, ); - const publicExecutionResult = stateManager.trace.toPublicExecutionResult( - avmExecutionEnv, - /*startGasLeft=*/ allocatedGas, + const publicExecutionResult = stateManager.trace.toPublicEnqueuedCallExecutionResult( /*endGasLeft=*/ Gas.from(avmContext.machineState.gasLeft), - bytecode, - avmResult, - fnName, + avmCallResult, ); - if (avmResult.reverted) { + if (avmCallResult.reverted) { this.metrics.recordFunctionSimulationFailure(); } else { this.metrics.recordFunctionSimulation(bytecode.length, timer.ms()); } PublicExecutor.log.verbose( - `[AVM] ${fnName} simulation complete. Reverted=${avmResult.reverted}. Consumed ${ + `[AVM] ${fnName} simulation complete. Reverted=${avmCallResult.reverted}. Consumed ${ allocatedGas.l2Gas - avmContext.machineState.gasLeft.l2Gas } L2 gas, ending with ${avmContext.machineState.gasLeft.l2Gas} L2 gas left.`, ); @@ -99,25 +97,33 @@ export class PublicExecutor { return publicExecutionResult; } - // WARNING: do not call from enqueued call simulator! - public async simulateSimple( - executionRequest: PublicExecutionRequest, // TODO(dbanks12): CallRequest instead? + /** + * Simulate an enqueued call on its own, and include its side effects in its results. + * @param executionRequest - The execution to run. + * @param globalVariables - The global variables to use. + * @param allocatedGas - The gas available at the start of this enqueued call. + * @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. + */ + public async simulateIsolatedEnqueuedCall( + executionRequest: PublicExecutionRequest, globalVariables: GlobalVariables, allocatedGas: Gas, - transactionFee: Fr = Fr.ZERO, - ): Promise { - const innerCallTrace = new PublicSideEffectTrace(); - const enqueuedCallTrace = new PublicEnqueuedCallSideEffectTrace(); + transactionFee: Fr = Fr.ONE, + startSideEffectCounter: number = 0, + ): Promise { + const innerCallTrace = new PublicSideEffectTrace(startSideEffectCounter); + const enqueuedCallTrace = new PublicEnqueuedCallSideEffectTrace(startSideEffectCounter); const trace = new DualSideEffectTrace(innerCallTrace, enqueuedCallTrace); const stateManager = new AvmPersistableStateManager(this.worldStateDB, trace); - return await this.simulate(stateManager, executionRequest, globalVariables, allocatedGas, transactionFee); - } - - public getDefaultStateManager(): AvmPersistableStateManager { - const innerCallTrace = new PublicSideEffectTrace(); - const enqueuedCallTrace = new PublicEnqueuedCallSideEffectTrace(); - const trace = new DualSideEffectTrace(innerCallTrace, enqueuedCallTrace); - return new AvmPersistableStateManager(this.worldStateDB, trace); + return (await this.simulate( + stateManager, + executionRequest, + globalVariables, + allocatedGas, + transactionFee, + )) as EnqueuedPublicCallExecutionResultWithSideEffects; } } diff --git a/yarn-project/simulator/src/public/index.ts b/yarn-project/simulator/src/public/index.ts index b57fbdbf6635..1145e23e7b0b 100644 --- a/yarn-project/simulator/src/public/index.ts +++ b/yarn-project/simulator/src/public/index.ts @@ -1,7 +1,10 @@ export * from './db_interfaces.js'; export { EnqueuedCallSimulator } from './enqueued_call_simulator.js'; export * from './enqueued_calls_processor.js'; -export { type PublicExecutionResult } from './execution.js'; +export { + type EnqueuedPublicCallExecutionResult as PublicExecutionResult, + type PublicFunctionCallResult, +} from './execution.js'; export { PublicExecutor } from './executor.js'; export * from './fee_payment.js'; export { HintsBuilder } from './hints_builder.js'; diff --git a/yarn-project/simulator/src/public/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor.test.ts index fb43d02546ba..2159e77bc183 100644 --- a/yarn-project/simulator/src/public/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor.test.ts @@ -160,8 +160,8 @@ describe('public_processor', () => { db.getPreviousValueIndex.mockResolvedValue({ index: 0n, alreadyPresent: true }); db.getLeafPreimage.mockResolvedValue(new PublicDataTreeLeafPreimage(new Fr(0), new Fr(0), new Fr(0), 0n)); - publicExecutor.simulate.mockImplementation((_stateManager, request) => { - const result = PublicExecutionResultBuilder.fromPublicExecutionRequest({ request }).build(); + publicExecutor.simulate.mockImplementation((_stateManager, _request) => { + const result = PublicExecutionResultBuilder.empty().build(); return Promise.resolve(result); }); diff --git a/yarn-project/simulator/src/public/side_effect_trace.test.ts b/yarn-project/simulator/src/public/side_effect_trace.test.ts index 5eae41f49b44..7a65890a6f6d 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.test.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.test.ts @@ -64,7 +64,7 @@ describe('Side Effect Trace', () => { }); const toPxResult = (trc: PublicSideEffectTrace) => { - return trc.toPublicExecutionResult(avmEnvironment, startGasLeft, endGasLeft, bytecode, avmCallResults); + return trc.toPublicFunctionCallResult(avmEnvironment, startGasLeft, endGasLeft, bytecode, avmCallResults); }; it('Should trace storage reads', () => { diff --git a/yarn-project/simulator/src/public/side_effect_trace.ts b/yarn-project/simulator/src/public/side_effect_trace.ts index aaffa92958a1..e1b370ab81be 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.ts @@ -41,7 +41,11 @@ import { createDebugLogger } from '@aztec/foundation/log'; import { type AvmContractCallResult } from '../avm/avm_contract_call_result.js'; import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; import { createSimulationError } from '../common/errors.js'; -import { type PublicExecutionResult, resultToPublicCallRequest } from './execution.js'; +import { + type EnqueuedPublicCallExecutionResultWithSideEffects, + type PublicFunctionCallResult, + resultToPublicCallRequest, +} from './execution.js'; import { SideEffectLimitReachedError } from './side_effect_errors.js'; import { type PublicSideEffectTraceInterface } from './side_effect_trace_interface.js'; @@ -72,7 +76,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { private publicCallRequests: PublicInnerCallRequest[] = []; - private nestedExecutions: PublicExecutionResult[] = []; + private nestedExecutions: PublicFunctionCallResult[] = []; private avmCircuitHints: AvmExecutionHints; @@ -301,7 +305,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { // one for max unique contract calls, and another based on max nullifier reads. // Since this trace function happens _after_ a nested call, such threshold limits must take // place in another trace function that occurs _before_ a nested call. - const result = nestedCallTrace.toPublicExecutionResult( + const result = nestedCallTrace.toPublicFunctionCallResult( nestedEnvironment, startGasLeft, endGasLeft, @@ -347,7 +351,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { throw new Error('Not implemented'); } - public traceAppLogicPhase( + public traceExecutionPhase( /** The trace of the enqueued call. */ _appLogicTrace: this, /** The call request from private that enqueued this call. */ @@ -363,7 +367,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { /** * Convert this trace to a PublicExecutionResult for use externally to the simulator. */ - public toPublicExecutionResult( + public toPublicFunctionCallResult( /** The execution environment of the nested call. */ avmEnvironment: AvmExecutionEnvironment, /** How much gas was available for this public execution. */ @@ -376,7 +380,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { avmCallResults: AvmContractCallResult, /** Function name for logging */ functionName: string = 'unknown', - ): PublicExecutionResult { + ): PublicFunctionCallResult { return { executionRequest: createPublicExecutionRequest(avmEnvironment), @@ -417,15 +421,26 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { }; } + public toPublicEnqueuedCallExecutionResult( + /** How much gas was left after this public execution. */ + _endGasLeft: Gas, + /** The call's results */ + _avmCallResults: AvmContractCallResult, + ): EnqueuedPublicCallExecutionResultWithSideEffects { + throw new Error('Not implemented'); + } + public toVMCircuitPublicInputs( /** Constants. */ _constants: CombinedConstantData, - /** The execution environment of the nested call. */ - _avmEnvironment: AvmExecutionEnvironment, + /** The call request that triggered public execution. */ + _callRequest: PublicCallRequest, /** How much gas was available for this public execution. */ _startGasLeft: Gas, /** How much gas was left after this public execution. */ _endGasLeft: Gas, + /** Transaction fee. */ + _transactionFee: Fr, /** The call's results */ _avmCallResults: AvmContractCallResult, ): VMCircuitPublicInputs { 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 98d95f9c40c9..666024058451 100644 --- a/yarn-project/simulator/src/public/side_effect_trace_interface.ts +++ b/yarn-project/simulator/src/public/side_effect_trace_interface.ts @@ -11,7 +11,7 @@ import { type Fr } from '@aztec/foundation/fields'; import { type AvmContractCallResult } from '../avm/avm_contract_call_result.js'; import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; -import { type PublicExecutionResult } from './execution.js'; +import { type EnqueuedPublicCallExecutionResultWithSideEffects, type PublicFunctionCallResult } from './execution.js'; export interface PublicSideEffectTraceInterface { fork(incrementSideEffectCounter?: boolean): PublicSideEffectTraceInterface; @@ -40,10 +40,8 @@ export interface PublicSideEffectTraceInterface { /** The execution environment of the nested call. */ nestedEnvironment: AvmExecutionEnvironment, /** How much gas was available for this public execution. */ - // TODO(dbanks12): consider moving to AvmExecutionEnvironment startGasLeft: Gas, /** How much gas was left after this public execution. */ - // TODO(dbanks12): consider moving to AvmContractCallResults endGasLeft: Gas, /** Bytecode used for this execution. */ bytecode: Buffer, @@ -62,7 +60,7 @@ export interface PublicSideEffectTraceInterface { /** Did the call revert? */ reverted: boolean, ): void; - traceAppLogicPhase( + traceExecutionPhase( /** The trace of the enqueued call. */ appLogicTrace: this, /** The call request from private that enqueued this call. */ @@ -72,7 +70,13 @@ export interface PublicSideEffectTraceInterface { /** Did the any enqueued call in app logic revert? */ reverted: boolean, ): void; - toPublicExecutionResult( + toPublicEnqueuedCallExecutionResult( + /** How much gas was left after this public execution. */ + endGasLeft: Gas, + /** The call's results */ + avmCallResults: AvmContractCallResult, + ): EnqueuedPublicCallExecutionResultWithSideEffects; + toPublicFunctionCallResult( /** The execution environment of the nested call. */ avmEnvironment: AvmExecutionEnvironment, /** How much gas was available for this public execution. */ @@ -85,16 +89,18 @@ export interface PublicSideEffectTraceInterface { avmCallResults: AvmContractCallResult, /** Function name for logging */ functionName: string, - ): PublicExecutionResult; + ): PublicFunctionCallResult; toVMCircuitPublicInputs( /** Constants. */ constants: CombinedConstantData, - /** The execution environment of the nested call. */ - avmEnvironment: AvmExecutionEnvironment, + /** The call request that triggered public execution. */ + callRequest: PublicCallRequest, /** How much gas was available for this public execution. */ startGasLeft: Gas, /** How much gas was left after this public execution. */ endGasLeft: Gas, + /** Transaction fee. */ + transactionFee: Fr, /** The call's results */ avmCallResults: AvmContractCallResult, ): VMCircuitPublicInputs; diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index b74f3b2f825b..151a85b6539a 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -30,6 +30,7 @@ import { PrivateContextInputs, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, + type PublicDataUpdateRequest, TaggingSecret, computeContractClassId, computeTaggingSecret, @@ -53,17 +54,13 @@ import { Timer } from '@aztec/foundation/timer'; import { type KeyStore } from '@aztec/key-store'; import { ContractDataOracle, enrichPublicSimulationError } from '@aztec/pxe'; import { - AvmPersistableStateManager, - DualSideEffectTrace, ExecutionError, type ExecutionNoteCache, type MessageLoadOracleInputs, type NoteData, Oracle, type PackedValuesCache, - PublicEnqueuedCallSideEffectTrace, PublicExecutor, - PublicSideEffectTrace, type TypedOracle, acvm, createSimulationError, @@ -77,6 +74,7 @@ import { import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; +import { type EnqueuedPublicCallExecutionResultWithSideEffects } from '../../../simulator/src/public/execution.js'; import { type TXEDatabase } from '../util/txe_database.js'; import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_source.js'; import { TXEWorldStateDB } from '../util/txe_world_state_db.js'; @@ -222,11 +220,36 @@ export class TXE implements TypedOracle { return this.txeDatabase.addAuthWitness(authWitness.requestHash, authWitness.witness); } - async addNullifiers(contractAddress: AztecAddress, nullifiers: Fr[]) { + async addPublicDataWrites(writes: PublicDataUpdateRequest[]) { const db = await this.trees.getLatest(); - const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier).toBuffer()); - await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, siloedNullifiers, NULLIFIER_SUBTREE_HEIGHT); + // only insert the last write to a given slot + const uniqueWritesSet = new Map(); + for (const write of writes) { + uniqueWritesSet.set(write.leafSlot.toBigInt(), write); + } + const uniqueWrites = Array.from(uniqueWritesSet.values()); + + await db.batchInsert( + MerkleTreeId.PUBLIC_DATA_TREE, + uniqueWrites.map(w => new PublicDataTreeLeaf(w.leafSlot, w.newValue).toBuffer()), + 0, + ); + } + + async addSiloedNullifiers(siloedNullifiers: Fr[]) { + const db = await this.trees.getLatest(); + + await db.batchInsert( + MerkleTreeId.NULLIFIER_TREE, + siloedNullifiers.map(n => n.toBuffer()), + NULLIFIER_SUBTREE_HEIGHT, + ); + } + + async addNullifiers(contractAddress: AztecAddress, nullifiers: Fr[]) { + const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier)); + await this.addSiloedNullifiers(siloedNullifiers); } async addNoteHashes(contractAddress: AztecAddress, noteHashes: Fr[]) { @@ -629,7 +652,7 @@ export class TXE implements TypedOracle { return `${artifact.name}:${f.name}`; } - private async executePublicFunction(stateManager: AvmPersistableStateManager, args: Fr[], callContext: CallContext) { + private async executePublicFunction(args: Fr[], callContext: CallContext) { const execution = new PublicExecutionRequest(callContext, args); const db = await this.trees.getLatest(); @@ -650,21 +673,16 @@ export class TXE implements TypedOracle { combinedConstantData.txContext.chainId = this.chainId; combinedConstantData.txContext.version = this.version; - // TODO(dbanks12): this is ugly! need a better external interface - // for the txe to use. Really I just need to see the side effects - // without needing to access the inner trace. Either via return values, - // or expose via state manager. And creation of the state manager should - // be more straightforward const simulator = new PublicExecutor( new TXEWorldStateDB(db, new TXEPublicContractDataSource(this)), new NoopTelemetryClient(), ); - const executionResult = await simulator.simulate( - stateManager, + const executionResult = await simulator.simulateIsolatedEnqueuedCall( execution, combinedConstantData.globalVariables, Gas.test(), - /* transactionFee */ Fr.ONE, + /*transactionFee=*/ Fr.ONE, + /*startSideEffectCounter=*/ this.sideEffectCounter, ); return Promise.resolve(executionResult); } @@ -694,14 +712,7 @@ export class TXE implements TypedOracle { const args = [this.functionSelector.toField(), ...this.packedValuesCache.unpack(argsHash)]; const newArgsHash = this.packedValuesCache.pack(args); - const innerCallTrace = new PublicSideEffectTrace(); - const enqueuedCallTrace = new PublicEnqueuedCallSideEffectTrace(); - const trace = new DualSideEffectTrace(innerCallTrace, enqueuedCallTrace); - const stateManager = new AvmPersistableStateManager( - new TXEWorldStateDB(await this.trees.getLatest(), new TXEPublicContractDataSource(this)), - trace, - ); - const executionResult = await this.executePublicFunction(stateManager, args, callContext); + const executionResult = await this.executePublicFunction(args, callContext); // Poor man's revert handling if (executionResult.reverted) { @@ -719,16 +730,13 @@ export class TXE implements TypedOracle { } // Apply side effects - this.sideEffectCounter = executionResult.endSideEffectCounter.toNumber() + 1; - const sideEffects = enqueuedCallTrace.getSideEffects(); + this.sideEffectCounter = executionResult.endSideEffectCounter.toNumber(); + await this.addPublicDataWrites(executionResult.sideEffects.publicDataWrites); await this.addNoteHashes( targetContractAddress, - sideEffects.noteHashes.map(noteHash => noteHash.value), - ); - await this.addNullifiers( - targetContractAddress, - sideEffects.nullifiers.map(nullifier => nullifier.value), + executionResult.sideEffects.noteHashes.map(noteHash => noteHash.value), ); + await this.addSiloedNullifiers(executionResult.sideEffects.nullifiers.map(nullifier => nullifier.value)); this.setContractAddress(currentContractAddress); this.setMsgSender(currentMessageSender); @@ -815,7 +823,11 @@ export class TXE implements TypedOracle { // AVM oracles - async avmOpcodeCall(targetContractAddress: AztecAddress, args: Fr[], isStaticCall: boolean) { + async avmOpcodeCall( + targetContractAddress: AztecAddress, + args: Fr[], + isStaticCall: boolean, + ): Promise { // Store and modify env const currentContractAddress = AztecAddress.fromField(this.contractAddress); const currentMessageSender = AztecAddress.fromField(this.msgSender); @@ -829,29 +841,19 @@ export class TXE implements TypedOracle { isStaticCall, ); - const innerCallTrace = new PublicSideEffectTrace(); - const enqueuedCallTrace = new PublicEnqueuedCallSideEffectTrace(); - const trace = new DualSideEffectTrace(innerCallTrace, enqueuedCallTrace); - const stateManager = new AvmPersistableStateManager( - new TXEWorldStateDB(await this.trees.getLatest(), new TXEPublicContractDataSource(this)), - trace, - ); - const executionResult = await this.executePublicFunction(stateManager, args, callContext); + const executionResult = await this.executePublicFunction(args, callContext); // Save return/revert data for later. this.nestedCallReturndata = executionResult.returnValues; // Apply side effects if (!executionResult.reverted) { - const sideEffects = enqueuedCallTrace.getSideEffects(); - this.sideEffectCounter = executionResult.endSideEffectCounter.toNumber() + 1; + this.sideEffectCounter = executionResult.endSideEffectCounter.toNumber(); + await this.addPublicDataWrites(executionResult.sideEffects.publicDataWrites); await this.addNoteHashes( targetContractAddress, - sideEffects.noteHashes.map(noteHash => noteHash.value), - ); - await this.addNullifiers( - targetContractAddress, - sideEffects.nullifiers.map(nullifier => nullifier.value), + executionResult.sideEffects.noteHashes.map(noteHash => noteHash.value), ); + await this.addSiloedNullifiers(executionResult.sideEffects.nullifiers.map(nullifier => nullifier.value)); } this.setContractAddress(currentContractAddress);