From a5ff83db4c2bcde4a51c21548819db36dfc312eb Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Sat, 9 Nov 2024 19:49:15 +0000 Subject: [PATCH] chore: stop calling public kernel tail --- .../simulator/src/avm/journal/journal.ts | 132 +++++---- .../src/avm/opcodes/external_calls.ts | 5 +- .../enqueued_call_side_effect_trace.test.ts | 17 +- .../public/enqueued_call_side_effect_trace.ts | 89 +++++- .../public/enqueued_calls_processor.test.ts | 1 + .../src/public/enqueued_calls_processor.ts | 256 ++++++++++++++++-- 6 files changed, 411 insertions(+), 89 deletions(-) diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 38cbbf58f694..896450e76845 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -1,7 +1,6 @@ import { type AztecAddress, type Gas, - type PublicCallRequest, SerializableContractInstance, computePublicBytecodeCommitment, } from '@aztec/circuits.js'; @@ -246,7 +245,7 @@ export class AvmPersistableStateManager { /** * Accept nested world state modifications */ - public acceptForkedState(forkedState: AvmPersistableStateManager) { + public mergeForkedState(forkedState: AvmPersistableStateManager) { this.publicStorage.acceptAndMerge(forkedState.publicStorage); this.nullifiers.acceptAndMerge(forkedState.nullifiers); } @@ -290,10 +289,8 @@ export class AvmPersistableStateManager { return undefined; } } - /** - * Accept the nested call's state and trace the nested call - */ - public async processNestedCall( + + public async traceNestedCall( forkedState: AvmPersistableStateManager, nestedEnvironment: AvmExecutionEnvironment, startGasLeft: Gas, @@ -301,9 +298,6 @@ export class AvmPersistableStateManager { bytecode: Buffer, avmCallResults: AvmContractCallResult, ) { - if (!avmCallResults.reverted) { - this.acceptForkedState(forkedState); - } const functionName = await getPublicFunctionDebugName( this.worldStateDB, nestedEnvironment.address, @@ -311,7 +305,7 @@ export class AvmPersistableStateManager { nestedEnvironment.calldata, ); - this.log.verbose(`[AVM] Calling nested function ${functionName}`); + this.log.verbose(`[AVM] Tracing nested external contract call ${functionName}`); this.trace.traceNestedCall( forkedState.trace, @@ -324,46 +318,80 @@ export class AvmPersistableStateManager { ); } - public async mergeStateForEnqueuedCall( - forkedState: AvmPersistableStateManager, - /** The call request from private that enqueued this call. */ - publicCallRequest: PublicCallRequest, - /** The call's calldata */ - calldata: Fr[], - /** Did the call revert? */ - reverted: boolean, - ) { - if (!reverted) { - this.acceptForkedState(forkedState); - } - const functionName = await getPublicFunctionDebugName( - this.worldStateDB, - publicCallRequest.contractAddress, - publicCallRequest.functionSelector, - calldata, - ); - - this.log.verbose(`[AVM] Encountered enqueued public call starting with function ${functionName}`); - - this.trace.traceEnqueuedCall(forkedState.trace, publicCallRequest, calldata, reverted); - } - - public mergeStateForPhase( - /** The forked state manager used by app logic */ - forkedState: AvmPersistableStateManager, - /** The call requests for each enqueued call in app logic. */ - publicCallRequests: PublicCallRequest[], - /** The calldatas for each enqueued call in app logic */ - calldatas: Fr[][], - /** Did the any enqueued call in app logic revert? */ - reverted: boolean, - ) { - if (!reverted) { - this.acceptForkedState(forkedState); - } - - this.log.verbose(`[AVM] Encountered app logic phase`); - - this.trace.traceExecutionPhase(forkedState.trace, publicCallRequests, calldatas, reverted); - } + ///** + // * Accept the nested call's state and trace the nested call + // */ + //public async processNestedCall( + // forkedState: AvmPersistableStateManager, + // nestedEnvironment: AvmExecutionEnvironment, + // startGasLeft: Gas, + // endGasLeft: Gas, + // bytecode: Buffer, + // avmCallResults: AvmContractCallResult, + //) { + // if (!avmCallResults.reverted) { + // this.mergeForkedState(forkedState); + // } + // const functionName = await getPublicFunctionDebugName( + // this.worldStateDB, + // nestedEnvironment.address, + // nestedEnvironment.functionSelector, + // nestedEnvironment.calldata, + // ); + + // this.log.verbose(`[AVM] Calling nested function ${functionName}`); + + // this.trace.traceNestedCall( + // forkedState.trace, + // nestedEnvironment, + // startGasLeft, + // endGasLeft, + // bytecode, + // avmCallResults, + // functionName, + // ); + //} + + //public async mergeStateForEnqueuedCall( + // forkedState: AvmPersistableStateManager, + // /** The call request from private that enqueued this call. */ + // publicCallRequest: PublicCallRequest, + // /** The call's calldata */ + // calldata: Fr[], + // /** Did the call revert? */ + // reverted: boolean, + //) { + // if (!reverted) { + // this.mergeForkedState(forkedState); + // } + // const functionName = await getPublicFunctionDebugName( + // this.worldStateDB, + // publicCallRequest.contractAddress, + // publicCallRequest.functionSelector, + // calldata, + // ); + + // this.log.verbose(`[AVM] Encountered enqueued public call starting with function ${functionName}`); + + // this.trace.traceEnqueuedCall(forkedState.trace, publicCallRequest, calldata, reverted); + //} + + //public mergeStateForPhase( + // /** The forked state manager used by app logic */ + // forkedState: AvmPersistableStateManager, + // /** The call requests for each enqueued call in app logic. */ + // publicCallRequests: PublicCallRequest[], + // /** The calldatas for each enqueued call in app logic */ + // calldatas: Fr[][], + // /** Did the any enqueued call in app logic revert? */ + // reverted: boolean, + //) { + // if (!reverted) { + // this.mergeForkedState(forkedState); + // } + + // this.log.verbose(`[AVM] Encountered app logic phase`); + + // this.trace.traceExecutionPhase(forkedState.trace, publicCallRequests, calldatas, reverted); + //} } diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.ts index 9b52a8ff160c..7bfbe5e8c714 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.ts @@ -98,7 +98,10 @@ abstract class ExternalCall extends Instruction { context.machineState.refundGas(gasLeftToGas(nestedContext.machineState)); // Accept the nested call's state and trace the nested call - await context.persistableState.processNestedCall( + if (success) { + context.persistableState.mergeForkedState(nestedContext.persistableState); + } + await context.persistableState.traceNestedCall( /*nestedState=*/ nestedContext.persistableState, /*nestedEnvironment=*/ nestedContext.environment, /*startGasLeft=*/ Gas.from(allocatedGas), 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 4b32d350a18b..4adf817882e0 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 @@ -491,17 +491,16 @@ describe('Enqueued-call Side Effect Trace', () => { // parent absorbs child's side effects const parentSideEffects = trace.getSideEffects(); const childSideEffects = nestedTrace.getSideEffects(); + // TODO(dbanks12): confirm that all hints were merged from child if (callResults.reverted) { - expect(parentSideEffects.publicDataReads).toEqual(childSideEffects.publicDataReads); - expect(parentSideEffects.publicDataWrites).toEqual(childSideEffects.publicDataWrites); - expect(parentSideEffects.noteHashReadRequests).toEqual(childSideEffects.noteHashReadRequests); + expect(parentSideEffects.publicDataReads).toEqual([]); + expect(parentSideEffects.publicDataWrites).toEqual([]); + expect(parentSideEffects.noteHashReadRequests).toEqual([]); expect(parentSideEffects.noteHashes).toEqual([]); - expect(parentSideEffects.nullifierReadRequests).toEqual(childSideEffects.nullifierReadRequests); - expect(parentSideEffects.nullifierNonExistentReadRequests).toEqual( - childSideEffects.nullifierNonExistentReadRequests, - ); - expect(parentSideEffects.nullifiers).toEqual(childSideEffects.nullifiers); - expect(parentSideEffects.l1ToL2MsgReadRequests).toEqual(childSideEffects.l1ToL2MsgReadRequests); + expect(parentSideEffects.nullifierReadRequests).toEqual([]); + expect(parentSideEffects.nullifierNonExistentReadRequests).toEqual([]); + expect(parentSideEffects.nullifiers).toEqual([]); + expect(parentSideEffects.l1ToL2MsgReadRequests).toEqual([]); expect(parentSideEffects.l2ToL1Msgs).toEqual([]); expect(parentSideEffects.unencryptedLogs).toEqual([]); expect(parentSideEffects.unencryptedLogsHashes).toEqual([]); 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 cef43fb3d132..e87fad00efaa 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,5 +1,7 @@ import { UnencryptedFunctionL2Logs, UnencryptedL2Log } from '@aztec/circuit-types'; import { + AvmAccumulatedData, + AvmCircuitPublicInputs, AvmContractBytecodeHints, AvmContractInstanceHint, AvmEnqueuedCallHint, @@ -11,6 +13,8 @@ import { type ContractClassIdPreimage, EthAddress, Gas, + type GasSettings, + type GlobalVariables, L2ToL1Message, LogHash, MAX_ENCRYPTED_LOGS_PER_TX, @@ -28,11 +32,14 @@ import { MAX_UNENCRYPTED_LOGS_PER_TX, NoteHash, Nullifier, + PrivateToAvmAccumulatedData, + PrivateToAvmAccumulatedDataArrayLengths, PublicAccumulatedData, PublicAccumulatedDataArrayLengths, PublicCallRequest, PublicDataRead, PublicDataUpdateRequest, + PublicDataWrite, PublicInnerCallRequest, PublicValidationRequestArrayLengths, PublicValidationRequests, @@ -44,6 +51,7 @@ import { ScopedReadRequest, SerializableContractInstance, TreeLeafReadRequest, + type TreeSnapshots, VMCircuitPublicInputs, } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/circuits.js/hash'; @@ -51,6 +59,7 @@ import { makeTuple } from '@aztec/foundation/array'; import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; +import { type Tuple } from '@aztec/foundation/serialize'; import { type AvmContractCallResult } from '../avm/avm_contract_call_result.js'; import { type AvmExecutionEnvironment } from '../avm/avm_execution_environment.js'; @@ -93,6 +102,9 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI /** The side effect counter increments with every call to the trace. */ private sideEffectCounter: number; + //private publicSetupCallRequests: PublicCallRequest[] = []; + //private publicAppLogicCallRequests: PublicCallRequest[] = []; + //private publicTeardownCallRequest: PublicCallRequest[] = []; private enqueuedCalls: PublicCallRequest[] = []; private publicDataReads: PublicDataRead[] = []; @@ -505,14 +517,14 @@ 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.publicDataReads.push(...nestedTrace.publicDataReads); - this.publicDataWrites.push(...nestedTrace.publicDataWrites); - this.noteHashReadRequests.push(...nestedTrace.noteHashReadRequests); + //this.publicDataReads.push(...nestedTrace.publicDataReads); + //this.publicDataWrites.push(...nestedTrace.publicDataWrites); + //this.noteHashReadRequests.push(...nestedTrace.noteHashReadRequests); // new noteHashes are tossed on revert - this.nullifierReadRequests.push(...nestedTrace.nullifierReadRequests); - this.nullifierNonExistentReadRequests.push(...nestedTrace.nullifierNonExistentReadRequests); - this.nullifiers.push(...nestedTrace.nullifiers); - this.l1ToL2MsgReadRequests.push(...nestedTrace.l1ToL2MsgReadRequests); + //this.nullifierReadRequests.push(...nestedTrace.nullifierReadRequests); + //this.nullifierNonExistentReadRequests.push(...nestedTrace.nullifierNonExistentReadRequests); + //this.nullifiers.push(...nestedTrace.nullifiers); + //this.l1ToL2MsgReadRequests.push(...nestedTrace.l1ToL2MsgReadRequests); // new l2-to-l1 messages are tossed on revert // new unencrypted logs are tossed on revert } @@ -595,6 +607,47 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI ); } + public toAvmCircuitPublicInputs( + /** Globals. */ + globalVariables: GlobalVariables, + /** Start tree snapshots. */ + startTreeSnapshots: TreeSnapshots, + /** Gas used at start of TX. */ + startGasUsed: Gas, + /** How much gas was available for this public execution. */ + gasLimits: GasSettings, + /** Call requests for setup phase. */ + publicSetupCallRequests: Tuple, + /** Call requests for app logic phase. */ + publicAppLogicCallRequests: Tuple, + /** Call request for teardown phase. */ + publicTeardownCallRequest: PublicCallRequest, + /** End tree snapshots. */ + endTreeSnapshots: TreeSnapshots, + /** Transaction fee. */ + transactionFee: Fr, + /** The call's results */ + reverted: boolean, + ): AvmCircuitPublicInputs { + return new AvmCircuitPublicInputs( + globalVariables, + startTreeSnapshots, + startGasUsed, + gasLimits, + publicSetupCallRequests, + publicAppLogicCallRequests, + publicTeardownCallRequest, + /*previousNonRevertibleAccumulatedDataArrayLengths=*/ PrivateToAvmAccumulatedDataArrayLengths.empty(), + /*previousRevertibleAccumulatedDataArrayLengths=*/ PrivateToAvmAccumulatedDataArrayLengths.empty(), + /*previousNonRevertibleAccumulatedDataArray=*/ PrivateToAvmAccumulatedData.empty(), + /*previousRevertibleAccumulatedDataArray=*/ PrivateToAvmAccumulatedData.empty(), + endTreeSnapshots, + /*accumulatedData=*/ this.getAvmAccumulatedData(), + transactionFee, + reverted, + ); + } + public toPublicFunctionCallResult( /** The execution environment of the nested call. */ _avmEnvironment: AvmExecutionEnvironment, @@ -635,6 +688,28 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI ); } + private getAvmAccumulatedData() { + return new AvmAccumulatedData( + padArrayEnd( + this.noteHashes.map(n => n.value), + Fr.zero(), + MAX_NOTE_HASHES_PER_TX, + ), + padArrayEnd( + this.nullifiers.map(n => n.value), + Fr.zero(), + MAX_NULLIFIERS_PER_TX, + ), + padArrayEnd(this.l2ToL1Messages, ScopedL2ToL1Message.empty(), MAX_L2_TO_L1_MSGS_PER_TX), + padArrayEnd(this.unencryptedLogsHashes, ScopedLogHash.empty(), MAX_UNENCRYPTED_LOGS_PER_TX), + padArrayEnd( + this.publicDataWrites.map(w => new PublicDataWrite(w.leafSlot, w.newValue)), + PublicDataWrite.empty(), + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ), + ); + } + private getAccumulatedData(gasUsed: Gas) { return new PublicAccumulatedData( padArrayEnd(this.noteHashes, ScopedNoteHash.empty(), MAX_NOTE_HASHES_PER_TX), 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 dfccb2e12167..89d117ea94d7 100644 --- a/yarn-project/simulator/src/public/enqueued_calls_processor.test.ts +++ b/yarn-project/simulator/src/public/enqueued_calls_processor.test.ts @@ -370,6 +370,7 @@ describe('enqueued_calls_processor', () => { // squashed // new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotA), fr(0x101)), new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotB), fr(0x151)), + new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotA), fr(0x103)), // squashed // new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotC), fr(0x201)), diff --git a/yarn-project/simulator/src/public/enqueued_calls_processor.ts b/yarn-project/simulator/src/public/enqueued_calls_processor.ts index 1aaeef23cac3..ea8dc7e1cc47 100644 --- a/yarn-project/simulator/src/public/enqueued_calls_processor.ts +++ b/yarn-project/simulator/src/public/enqueued_calls_processor.ts @@ -19,6 +19,11 @@ import { type GlobalVariables, type Header, type KernelCircuitPublicInputs, + MAX_L2_TO_L1_MSGS_PER_TX, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_UNENCRYPTED_LOGS_PER_TX, NESTED_RECURSIVE_PROOF_LENGTH, type PrivateKernelTailCircuitPublicInputs, PrivateToAvmAccumulatedData, @@ -27,20 +32,25 @@ import { PublicAccumulatedData, PublicAccumulatedDataArrayLengths, type PublicCallRequest, + PublicDataWrite, PublicKernelCircuitPrivateInputs, PublicKernelCircuitPublicInputs, PublicKernelData, PublicValidationRequestArrayLengths, PublicValidationRequests, RevertCode, + type StateReference, TreeSnapshots, type VMCircuitPublicInputs, VerificationKeyData, countAccumulatedItems, makeEmptyProof, makeEmptyRecursiveProof, + mergeAccumulatedData, } from '@aztec/circuits.js'; +import { padArrayEnd } from '@aztec/foundation/collection'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; +import { assertLength } from '@aztec/foundation/serialize'; import { Timer } from '@aztec/foundation/timer'; import { getVKSiblingPath } from '@aztec/noir-protocol-circuits-types'; @@ -84,6 +94,7 @@ export type ProcessedPhase = { phase: TxExecutionPhase; durationMs: number; returnValues: NestedProcessReturnValues[]; + reverted: boolean; revertReason?: SimulationError; }; @@ -101,6 +112,7 @@ export class EnqueuedCallsProcessor { private log: DebugLogger; constructor( + private db: MerkleTreeReadOperations, private publicKernelSimulator: PublicKernelCircuitSimulator, private globalVariables: GlobalVariables, private worldStateDB: WorldStateDB, @@ -131,6 +143,7 @@ export class EnqueuedCallsProcessor { const publicKernelTailSimulator = PublicKernelTailSimulator.create(db, publicKernelSimulator); return new EnqueuedCallsProcessor( + db, publicKernelSimulator, globalVariables, worldStateDB, @@ -181,8 +194,23 @@ export class EnqueuedCallsProcessor { [TxExecutionPhase.TEARDOWN]: Gas.empty(), }; let avmProvingRequest: AvmProvingRequest; - let publicKernelOutput = this.getPublicKernelCircuitPublicInputs(tx.data); + const firstPublicKernelOutput = this.getPublicKernelCircuitPublicInputs(tx.data); + let publicKernelOutput = firstPublicKernelOutput; + let revertCode: RevertCode = RevertCode.OK; + let reverted = false; let revertReason: SimulationError | undefined; + const startStateReference = await this.db.getStateReference(); + /* + * Don't need to fork at all during setup because it's non-revertible. + * Fork at start of app phase (if app logic exists). + * Don't fork for any app subsequent enqueued calls because if any one fails, all of app logic reverts. + * If app logic reverts, rollback to end of setup and fork again for teardown. + * If app logic succeeds, don't fork for teardown. + * If teardown reverts, rollback to end of setup. + * If teardown succeeds, accept/merge all state changes from app logic. + * + */ + // rename merge to rollback/merge const nonRevertibleNullifiersFromPrivate = publicKernelOutput.endNonRevertibleData.nullifiers .filter(n => !n.isEmpty()) @@ -212,19 +240,17 @@ export class EnqueuedCallsProcessor { trace, nonRevertibleNullifiersFromPrivate, ); + let currentlyActiveStateManager = txStateManager; // TODO(dbanks12): insert all non-revertible side effects from private here. for (let i = 0; i < phases.length; i++) { const phase = phases[i]; - let stateManagerForPhase: AvmPersistableStateManager; - if (phase === TxExecutionPhase.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! + // don't need to fork for setup since it's non-revertible + // (if setup fails, transaction is thrown out) + if (phase === TxExecutionPhase.APP_LOGIC) { + // Fork the state manager so that we can rollback state if app logic or teardown reverts. + currentlyActiveStateManager = txStateManager.fork(); + // TODO: shouldn't need to fork if there are no enqueued calls in app logic } const callRequests = EnqueuedCallsProcessor.getCallRequestsByPhase(tx, phase); if (callRequests.length) { @@ -241,7 +267,7 @@ export class EnqueuedCallsProcessor { publicKernelOutput, allocatedGas, transactionFee, - stateManagerForPhase, + currentlyActiveStateManager, ).catch(async err => { await this.worldStateDB.rollbackToCommit(); throw err; @@ -253,9 +279,38 @@ export class EnqueuedCallsProcessor { // Eventually this will be the proof for the entire public call stack. avmProvingRequest = result.avmProvingRequest; - if (phase !== TxExecutionPhase.SETUP) { - txStateManager.mergeStateForPhase( - stateManagerForPhase, + if (result.reverted) { + if (phase === TxExecutionPhase.APP_LOGIC) { + revertCode = RevertCode.APP_LOGIC_REVERTED; + + // Trace app logic phase + txStateManager.trace.traceExecutionPhase( + currentlyActiveStateManager.trace, + callRequests, + executionRequests.map(req => req.args), + /*reverted=*/ true, + ); + // Drop the currently active forked state manager and rollback to end of setup. + // Fork again for teardown so that if teardown fails we can again rollback to end of setup. + currentlyActiveStateManager = txStateManager.fork(); + } else if (phase === TxExecutionPhase.TEARDOWN) { + if (revertCode === RevertCode.APP_LOGIC_REVERTED) { + revertCode = RevertCode.BOTH_REVERTED; + } else { + revertCode = RevertCode.TEARDOWN_REVERTED; + } + } + } + + if (phase === TxExecutionPhase.TEARDOWN) { + // merge all state changes from app logic (if successful) and teardown into tx state + // or, on revert, rollback to end of setup + if (!result.reverted) { + txStateManager.mergeForkedState(currentlyActiveStateManager); + } + // trace teardown phase + txStateManager.trace.traceExecutionPhase( + currentlyActiveStateManager.trace, callRequests, executionRequests.map(req => req.args), /*reverted=*/ result.revertReason ? true : false, @@ -266,6 +321,7 @@ export class EnqueuedCallsProcessor { phase, durationMs: result.durationMs, returnValues: result.returnValues, + reverted: result.reverted, revertReason: result.revertReason, }); @@ -274,6 +330,7 @@ export class EnqueuedCallsProcessor { [phase]: result.gasUsed, }; + reverted = reverted || result.reverted; revertReason ??= result.revertReason; } } @@ -287,7 +344,20 @@ export class EnqueuedCallsProcessor { ); const transactionFee = this.getTransactionFee(tx, phaseGasUsed); - avmProvingRequest!.inputs.output = this.generateAvmCircuitPublicInputs(tx, tailKernelOutput, transactionFee); + //avmProvingRequest!.inputs.output = + this.generateAvmCircuitPublicInputsOld(tx, tailKernelOutput, transactionFee); + + const endStateReference = await this.db.getStateReference(); + + avmProvingRequest!.inputs.output = this.generateAvmCircuitPublicInputs( + tx, + trace.enqueuedCallTrace, + startStateReference, + endStateReference, + transactionFee, + revertCode, + firstPublicKernelOutput, + ); const gasUsed = { totalGas: this.getActualGasUsed(tx, phaseGasUsed), @@ -298,7 +368,7 @@ export class EnqueuedCallsProcessor { avmProvingRequest: avmProvingRequest!, gasUsed, processedPhases, - revertCode: tailKernelOutput.revertCode, + revertCode, revertReason, }; } @@ -340,6 +410,8 @@ export class EnqueuedCallsProcessor { await this.worldStateDB.addNewContracts(tx); // each enqueued call starts with an incremented side effect counter + // FIXME: should be able to stop forking here and just trace the enqueued call (for hinting) + // and proceed with the same state manager for the entire phase const enqueuedCallStateManager = txStateManager.fork(/*incrementSideEffectCounter=*/ true); const enqueuedCallResult = await this.enqueuedCallSimulator.simulate( callRequest, @@ -356,8 +428,11 @@ export class EnqueuedCallsProcessor { ); throw enqueuedCallResult.revertReason; } - await txStateManager.mergeStateForEnqueuedCall( - enqueuedCallStateManager, + if (!enqueuedCallResult.reverted) { + txStateManager.mergeForkedState(enqueuedCallStateManager); + } // else rollback! + txStateManager.trace.traceEnqueuedCall( + enqueuedCallStateManager.trace, callRequest, executionRequest.args, enqueuedCallResult.reverted!, @@ -487,8 +562,141 @@ export class EnqueuedCallsProcessor { ); } + private generateAvmCircuitPublicInputs( + tx: Tx, + trace: PublicEnqueuedCallSideEffectTrace, + startStateReference: StateReference, + endStateReference: StateReference, + transactionFee: Fr, + revertCode: RevertCode, + firstPublicKernelOutput: PublicKernelCircuitPublicInputs, + ): AvmCircuitPublicInputs { + const startTreeSnapshots = new TreeSnapshots( + startStateReference.l1ToL2MessageTree, + startStateReference.partial.noteHashTree, + startStateReference.partial.nullifierTree, + startStateReference.partial.publicDataTree, + ); + const endTreeSnapshots = new TreeSnapshots( + endStateReference.l1ToL2MessageTree, + endStateReference.partial.noteHashTree, + endStateReference.partial.nullifierTree, + endStateReference.partial.publicDataTree, + ); + + const avmCircuitPublicInputs = trace.toAvmCircuitPublicInputs( + this.globalVariables, + startTreeSnapshots, + tx.data.gasUsed, + tx.data.constants.txContext.gasSettings, + tx.data.forPublic!.nonRevertibleAccumulatedData.publicCallRequests, + tx.data.forPublic!.revertibleAccumulatedData.publicCallRequests, + tx.data.forPublic!.publicTeardownCallRequest, + endTreeSnapshots, + transactionFee, + !revertCode.isOK(), + ); + + const getArrayLengths = (from: PrivateToPublicAccumulatedData) => + new PrivateToAvmAccumulatedDataArrayLengths( + countAccumulatedItems(from.noteHashes), + countAccumulatedItems(from.nullifiers), + countAccumulatedItems(from.l2ToL1Msgs), + ); + const convertAccumulatedData = (from: PrivateToPublicAccumulatedData) => + new PrivateToAvmAccumulatedData(from.noteHashes, from.nullifiers, from.l2ToL1Msgs); + // Temporary overrides as these entries aren't yet populated in trace + avmCircuitPublicInputs.previousNonRevertibleAccumulatedDataArrayLengths = getArrayLengths( + tx.data.forPublic!.nonRevertibleAccumulatedData, + ); + avmCircuitPublicInputs.previousRevertibleAccumulatedDataArrayLengths = getArrayLengths( + tx.data.forPublic!.revertibleAccumulatedData, + ); + avmCircuitPublicInputs.previousNonRevertibleAccumulatedData = convertAccumulatedData( + tx.data.forPublic!.nonRevertibleAccumulatedData, + ); + avmCircuitPublicInputs.previousRevertibleAccumulatedData = convertAccumulatedData( + tx.data.forPublic!.revertibleAccumulatedData, + ); + + // merge all revertible & non-revertible side effects into output accumulated data + const noteHashesFromPrivate = revertCode.isOK() + ? mergeAccumulatedData( + avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.noteHashes, + avmCircuitPublicInputs.previousRevertibleAccumulatedData.noteHashes, + ) + : avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.noteHashes; + avmCircuitPublicInputs.accumulatedData.noteHashes = assertLength( + mergeAccumulatedData(noteHashesFromPrivate, avmCircuitPublicInputs.accumulatedData.noteHashes), + MAX_NOTE_HASHES_PER_TX, + ); + const nullifiersFromPrivate = revertCode.isOK() + ? mergeAccumulatedData( + avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.nullifiers, + avmCircuitPublicInputs.previousRevertibleAccumulatedData.nullifiers, + ) + : avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.nullifiers; + avmCircuitPublicInputs.accumulatedData.nullifiers = assertLength( + mergeAccumulatedData(nullifiersFromPrivate, avmCircuitPublicInputs.accumulatedData.nullifiers), + MAX_NULLIFIERS_PER_TX, + ); + const msgsFromPrivate = revertCode.isOK() + ? mergeAccumulatedData( + avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.l2ToL1Msgs, + avmCircuitPublicInputs.previousRevertibleAccumulatedData.l2ToL1Msgs, + ) + : avmCircuitPublicInputs.previousNonRevertibleAccumulatedData.l2ToL1Msgs; + avmCircuitPublicInputs.accumulatedData.l2ToL1Msgs = assertLength( + mergeAccumulatedData(msgsFromPrivate, avmCircuitPublicInputs.accumulatedData.l2ToL1Msgs), + MAX_L2_TO_L1_MSGS_PER_TX, + ); + const ulogsFromPrivate = revertCode.isOK() + ? mergeAccumulatedData( + firstPublicKernelOutput.endNonRevertibleData.unencryptedLogsHashes, + firstPublicKernelOutput.end.unencryptedLogsHashes, + ) + : firstPublicKernelOutput.endNonRevertibleData.unencryptedLogsHashes; + avmCircuitPublicInputs.accumulatedData.unencryptedLogsHashes = assertLength( + mergeAccumulatedData(ulogsFromPrivate, avmCircuitPublicInputs.accumulatedData.unencryptedLogsHashes), + MAX_UNENCRYPTED_LOGS_PER_TX, + ); + + const dedupedPublicDataWrites: Array = []; + const leafSlotOccurences: Map = new Map(); + for (const publicDataWrite of avmCircuitPublicInputs.accumulatedData.publicDataWrites) { + const slot = publicDataWrite.leafSlot.toBigInt(); + const prevOccurrences = leafSlotOccurences.get(slot) || 0; + leafSlotOccurences.set(slot, prevOccurrences + 1); + } + + for (const publicDataWrite of avmCircuitPublicInputs.accumulatedData.publicDataWrites) { + const slot = publicDataWrite.leafSlot.toBigInt(); + const prevOccurrences = leafSlotOccurences.get(slot) || 0; + if (prevOccurrences === 1) { + dedupedPublicDataWrites.push(publicDataWrite); + } else { + leafSlotOccurences.set(slot, prevOccurrences - 1); + } + } + + avmCircuitPublicInputs.accumulatedData.publicDataWrites = padArrayEnd( + dedupedPublicDataWrites, + PublicDataWrite.empty(), + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ); + + this.log.debug(`New AVM circuit nullifiers: ${JSON.stringify(avmCircuitPublicInputs.accumulatedData.nullifiers)}`); + this.log.debug(`New AVM circuit note hashes: ${JSON.stringify(avmCircuitPublicInputs.accumulatedData.noteHashes)}`); + this.log.debug( + `New AVM circuit public writes: ${JSON.stringify(avmCircuitPublicInputs.accumulatedData.publicDataWrites)}`, + ); + this.log.debug(`New AVM: ${inspect(avmCircuitPublicInputs, { depth: 5 })}`); + + return avmCircuitPublicInputs; + } + // Temporary hack to create the AvmCircuitPublicInputs from public tail's public inputs. - private generateAvmCircuitPublicInputs(tx: Tx, tailOutput: KernelCircuitPublicInputs, transactionFee: Fr) { + private generateAvmCircuitPublicInputsOld(tx: Tx, tailOutput: KernelCircuitPublicInputs, transactionFee: Fr) { const startTreeSnapshots = new TreeSnapshots( tailOutput.constants.historicalHeader.state.l1ToL2MessageTree, tailOutput.startState.noteHashTree, @@ -519,7 +727,7 @@ export class EnqueuedCallsProcessor { // Should fetch the updated roots from db. const endTreeSnapshots = startTreeSnapshots; - return new AvmCircuitPublicInputs( + const old = new AvmCircuitPublicInputs( tailOutput.constants.globalVariables, startTreeSnapshots, tx.data.gasUsed, @@ -536,5 +744,13 @@ export class EnqueuedCallsProcessor { transactionFee, !tailOutput.revertCode.equals(RevertCode.OK), ); + + this.log.debug(`Old AVM circuit nullifiers: ${JSON.stringify(old.accumulatedData.nullifiers)}`); + this.log.debug(`Old AVM circuit note hashes: ${JSON.stringify(old.accumulatedData.noteHashes)}`); + this.log.debug(`Old AVM circuit public writes: ${JSON.stringify(old.accumulatedData.publicDataWrites)}`); + + this.log.debug(`Old AVM: ${inspect(old, { depth: 5 })}`); + + return old; } }