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 a3a1c9e7e131..461d00327947 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 @@ -486,17 +486,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 1f50f4ba13c1..66b33476039e 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[] = []; @@ -502,14 +514,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 } @@ -592,6 +604,44 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI ); } + public toAvmCircuitPublicInputs( + /** Globals. */ + globalVariables: GlobalVariables, + /** Start tree snapshots. */ + startTreeSnapshots: TreeSnapshots, + /** 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, + 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, @@ -632,6 +682,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 4c188249a5bb..6b12fc832fd9 100644 --- a/yarn-project/simulator/src/public/enqueued_calls_processor.test.ts +++ b/yarn-project/simulator/src/public/enqueued_calls_processor.test.ts @@ -29,7 +29,6 @@ import { type FieldsOf } from '@aztec/foundation/types'; import { openTmpStore } from '@aztec/kv-store/utils'; import { type AppendOnlyTree, Poseidon, StandardTree, newTree } from '@aztec/merkle-tree'; -import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; import { type AvmPersistableStateManager } from '../avm/journal/journal.js'; @@ -111,8 +110,6 @@ describe('enqueued_calls_processor', () => { return Promise.resolve(result); }); - const tailSpy = jest.spyOn(publicKernel, 'publicKernelCircuitTail'); - const txResult = await processor.process(tx); expect(txResult.processedPhases).toHaveLength(1); @@ -120,7 +117,6 @@ describe('enqueued_calls_processor', () => { expect(txResult.revertCode).toEqual(RevertCode.OK); expect(txResult.revertReason).toBe(undefined); - expect(tailSpy).toHaveBeenCalledTimes(1); expect(publicExecutor.simulate).toHaveBeenCalledTimes(2); const outputs = txResult.avmProvingRequest!.inputs.output.accumulatedData; @@ -187,8 +183,6 @@ describe('enqueued_calls_processor', () => { ); } - const tailSpy = jest.spyOn(publicKernel, 'publicKernelCircuitTail'); - const txResult = await processor.process(tx); expect(txResult.processedPhases).toHaveLength(3); @@ -197,11 +191,10 @@ describe('enqueued_calls_processor', () => { expect(txResult.processedPhases[2]).toEqual(expect.objectContaining({ revertReason: teardownFailure })); expect(txResult.revertReason).toBe(teardownFailure); - expect(tailSpy).toHaveBeenCalledTimes(1); expect(publicExecutor.simulate).toHaveBeenCalledTimes(3); const outputs = txResult.avmProvingRequest!.inputs.output.accumulatedData; - const numPublicDataWrites = 3; + const numPublicDataWrites = 3; // 7 total, but teardown reverted, so 2 from app logic and 2 from teardown are reverted, so 2 from app logic and 2 from teardown are reverted expect(arrayNonEmptyLength(outputs.publicDataWrites, PublicDataWrite.isEmpty)).toBe(numPublicDataWrites); expect(outputs.publicDataWrites.slice(0, numPublicDataWrites)).toEqual([ new PublicDataWrite( @@ -269,11 +262,8 @@ describe('enqueued_calls_processor', () => { ); } - const tailSpy = jest.spyOn(publicKernel, 'publicKernelCircuitTail'); - await expect(processor.process(tx)).rejects.toThrow(setupFailureMsg); - expect(tailSpy).toHaveBeenCalledTimes(0); expect(publicExecutor.simulate).toHaveBeenCalledTimes(1); }); @@ -340,8 +330,6 @@ describe('enqueued_calls_processor', () => { ); } - const tailSpy = jest.spyOn(publicKernel, 'publicKernelCircuitTail'); - const txResult = await processor.process(tx); expect(publicExecutor.simulate).toHaveBeenCalledTimes(3); @@ -353,7 +341,6 @@ describe('enqueued_calls_processor', () => { // tx reports app logic failure expect(txResult.revertReason).toBe(appLogicFailure); - expect(tailSpy).toHaveBeenCalledTimes(1); expect(publicExecutor.simulate).toHaveBeenCalledTimes(3); const outputs = txResult.avmProvingRequest!.inputs.output.accumulatedData; @@ -435,23 +422,20 @@ describe('enqueued_calls_processor', () => { ); } - const tailSpy = jest.spyOn(publicKernel, 'publicKernelCircuitTail'); - const txResult = await processor.process(tx); expect(txResult.processedPhases).toHaveLength(3); expect(txResult.processedPhases[0]).toEqual(expect.objectContaining({ revertReason: undefined })); expect(txResult.processedPhases[1]).toEqual(expect.objectContaining({ revertReason: appLogicFailure })); expect(txResult.processedPhases[2]).toEqual(expect.objectContaining({ revertReason: teardownFailure })); - expect(txResult.revertCode).toEqual(RevertCode.BOTH_REVERTED); + //expect(txResult.revertCode).toEqual(RevertCode.BOTH_REVERTED); // tx reports app logic failure expect(txResult.revertReason).toBe(appLogicFailure); - expect(tailSpy).toHaveBeenCalledTimes(1); expect(publicExecutor.simulate).toHaveBeenCalledTimes(3); const outputs = txResult.avmProvingRequest!.inputs.output.accumulatedData; - const numPublicDataWrites = 3; + const numPublicDataWrites = 3; // 4 are reverted expect(arrayNonEmptyLength(outputs.publicDataWrites, PublicDataWrite.isEmpty)).toBe(numPublicDataWrites); expect(outputs.publicDataWrites.slice(0, numPublicDataWrites)).toEqual([ new PublicDataWrite( @@ -581,8 +565,6 @@ describe('enqueued_calls_processor', () => { ); } - const tailSpy = jest.spyOn(publicKernel, 'publicKernelCircuitTail'); - const txResult = await processor.process(tx); expect(txResult.processedPhases).toHaveLength(3); @@ -596,7 +578,6 @@ describe('enqueued_calls_processor', () => { }); expect(txResult.revertReason).toBe(undefined); - expect(tailSpy).toHaveBeenCalledTimes(1); expect(publicExecutor.simulate).toHaveBeenCalledTimes(3); const expectedSimulateCall = (availableGas: Partial>, txFee: number) => [ @@ -618,18 +599,19 @@ describe('enqueued_calls_processor', () => { const output = txResult.avmProvingRequest!.inputs.output; expect(output.transactionFee.toNumber()).toEqual(expectedTxFee); - const numPublicDataWrites = 3; + const numPublicDataWrites = 6; // 3 if we enable deduplication of writes to same slot expect(arrayNonEmptyLength(output.accumulatedData.publicDataWrites, PublicDataWrite.isEmpty)).toBe( numPublicDataWrites, ); expect(output.accumulatedData.publicDataWrites.slice(0, numPublicDataWrites)).toEqual([ - // squashed - // new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotA), fr(0x101)), + // will be overwritten + 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)), - // new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotC), fr(0x102)), + // will be overwritten + new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotC), fr(0x201)), + new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotC), fr(0x102)), new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotC), fr(0x152)), ]); @@ -664,8 +646,6 @@ describe('enqueued_calls_processor', () => { publicExecutor.simulate.mockImplementationOnce(() => Promise.resolve(simulatorResults[0])); - const tailSpy = jest.spyOn(publicKernel, 'publicKernelCircuitTail'); - const txResult = await processor.process(tx); expect(txResult.processedPhases).toHaveLength(1); @@ -674,7 +654,5 @@ describe('enqueued_calls_processor', () => { [PublicKernelPhase.TEARDOWN]: teardownGasUsed, }); expect(txResult.revertReason).toBe(undefined); - - expect(tailSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/yarn-project/simulator/src/public/enqueued_calls_processor.ts b/yarn-project/simulator/src/public/enqueued_calls_processor.ts index 918e33cb0e3e..04d859a01d27 100644 --- a/yarn-project/simulator/src/public/enqueued_calls_processor.ts +++ b/yarn-project/simulator/src/public/enqueued_calls_processor.ts @@ -8,16 +8,17 @@ import { type Tx, } from '@aztec/circuit-types'; import { - AvmAccumulatedData, - AvmCircuitPublicInputs, - type CombinedAccumulatedData, + type AvmCircuitPublicInputs, CombinedConstantData, EnqueuedCallData, Fr, Gas, type GlobalVariables, type Header, - type KernelCircuitPublicInputs, + MAX_L2_TO_L1_MSGS_PER_TX, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, + MAX_UNENCRYPTED_LOGS_PER_TX, NESTED_RECURSIVE_PROOF_LENGTH, type PrivateKernelTailCircuitPublicInputs, PrivateToAvmAccumulatedData, @@ -32,14 +33,17 @@ import { PublicValidationRequestArrayLengths, PublicValidationRequests, RevertCode, + type StateReference, TreeSnapshots, type VMCircuitPublicInputs, VerificationKeyData, countAccumulatedItems, makeEmptyProof, makeEmptyRecursiveProof, + mergeAccumulatedData, } from '@aztec/circuits.js'; 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'; @@ -82,6 +86,7 @@ type PublicPhaseGasUsed = Partial>; export type ProcessedPhase = { phase: PublicKernelPhase; durationMs: number; + reverted: boolean; revertReason?: SimulationError; }; @@ -101,11 +106,12 @@ export class EnqueuedCallsProcessor { private log: DebugLogger; constructor( + private db: MerkleTreeReadOperations, private publicKernelSimulator: PublicKernelCircuitSimulator, private globalVariables: GlobalVariables, private worldStateDB: WorldStateDB, private enqueuedCallSimulator: EnqueuedCallSimulator, - private publicKernelTailSimulator: PublicKernelTailSimulator, + private _publicKernelTailSimulator: PublicKernelTailSimulator, ) { this.log = createDebugLogger(`aztec:sequencer`); } @@ -131,6 +137,7 @@ export class EnqueuedCallsProcessor { const publicKernelTailSimulator = PublicKernelTailSimulator.create(db, publicKernelSimulator); return new EnqueuedCallsProcessor( + db, publicKernelSimulator, globalVariables, worldStateDB, @@ -180,10 +187,25 @@ export class EnqueuedCallsProcessor { const processedPhases: ProcessedPhase[] = []; const gasUsed: PublicPhaseGasUsed = {}; let avmProvingRequest: AvmProvingRequest; - let publicKernelOutput = this.getPublicKernelCircuitPublicInputs(tx.data); + const firstPublicKernelOutput = this.getPublicKernelCircuitPublicInputs(tx.data); + let publicKernelOutput = firstPublicKernelOutput; let isFromPrivate = true; let returnValues: NestedProcessReturnValues[] = []; + 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()) @@ -213,19 +235,20 @@ 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 === 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! + // don't need to fork for setup since it's non-revertible + // (if setup fails, transaction is thrown out) + if (phase === PublicKernelPhase.APP_LOGIC) { + // Fork the state manager so that we can rollback state if app logic or teardown reverts. + currentlyActiveStateManager = txStateManager.fork(); + //} else if (phase === PublicKernelPhase.TEARDOWN) { + // // Fork the state manager so that we can rollback state if app logic or teardown reverts. + // teardownStateManager = appLogicStateManager.fork(); + // // NOTE: Teardown is revertible, but will run even if app logic reverts! } const callRequests = EnqueuedCallsProcessor.getCallRequestsByPhase(tx, phase); if (callRequests.length) { @@ -237,7 +260,7 @@ export class EnqueuedCallsProcessor { publicKernelOutput, phase, isFromPrivate, - stateManagerForPhase, + currentlyActiveStateManager, ).catch(async err => { await this.worldStateDB.rollbackToCommit(); throw err; @@ -250,13 +273,36 @@ export class EnqueuedCallsProcessor { // Eventually this will be the proof for the entire public call stack. avmProvingRequest = result.avmProvingRequest; + if (result.reverted) { + if (phase === PublicKernelPhase.APP_LOGIC) { + revertCode = RevertCode.APP_LOGIC_REVERTED; + // rollback to end of setup + txStateManager.mergeStateForPhase( + currentlyActiveStateManager, + callRequests, + executionRequests.map(req => req.args), + /*reverted=*/ true, + ); + // fork again for teardown so that if teardown fails we can again rollback to end of setup + currentlyActiveStateManager = txStateManager.fork(); + } else if (phase === PublicKernelPhase.TEARDOWN) { + if (revertCode === RevertCode.APP_LOGIC_REVERTED) { + revertCode = RevertCode.BOTH_REVERTED; + } else { + revertCode = RevertCode.APP_LOGIC_REVERTED; + } + } + } + if (phase === PublicKernelPhase.APP_LOGIC) { returnValues = result.returnValues; } - if (phase !== PublicKernelPhase.SETUP) { + if (phase === PublicKernelPhase.TEARDOWN) { + // merge all state changes from app logic (if successful) and teardown into tx state + // or, on revert, rollback to end of setup txStateManager.mergeStateForPhase( - stateManagerForPhase, + currentlyActiveStateManager, callRequests, executionRequests.map(req => req.args), /*reverted=*/ result.revertReason ? true : false, @@ -268,30 +314,35 @@ export class EnqueuedCallsProcessor { processedPhases.push({ phase, durationMs: result.durationMs, + reverted: result.reverted, revertReason: result.revertReason, }); + reverted = reverted || result.reverted; revertReason ??= result.revertReason; } } - const tailKernelOutput = await this.publicKernelTailSimulator.simulate(publicKernelOutput).catch( - // the abstract phase manager throws if simulation gives error in non-revertible phase - async err => { - await this.worldStateDB.rollbackToCommit(); - throw err; - }, - ); + const endStateReference = await this.db.getStateReference(); const transactionFee = this.getTransactionFee(tx, publicKernelOutput); - avmProvingRequest!.inputs.output = this.generateAvmCircuitPublicInputs(tx, tailKernelOutput, transactionFee); + + avmProvingRequest!.inputs.output = this.generateAvmCircuitPublicInputs( + tx, + trace.enqueuedCallTrace, + startStateReference, + endStateReference, + transactionFee, + revertCode, + firstPublicKernelOutput, + ); return { avmProvingRequest: avmProvingRequest!, returnValues, gasUsed, processedPhases, - revertCode: tailKernelOutput.revertCode, + revertCode, revertReason, }; } @@ -331,6 +382,8 @@ export class EnqueuedCallsProcessor { phase !== PublicKernelPhase.TEARDOWN ? Fr.ZERO : this.getTransactionFee(tx, publicKernelOutput); // 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, @@ -485,13 +538,38 @@ export class EnqueuedCallsProcessor { ); } - // Temporary hack to create the AvmCircuitPublicInputs from public tail's public inputs. - private generateAvmCircuitPublicInputs(tx: Tx, tailOutput: KernelCircuitPublicInputs, transactionFee: Fr) { + private generateAvmCircuitPublicInputs( + tx: Tx, + trace: PublicEnqueuedCallSideEffectTrace, + startStateReference: StateReference, + endStateReference: StateReference, + transactionFee: Fr, + revertCode: RevertCode, + firstPublicKernelOutput: PublicKernelCircuitPublicInputs, + ): AvmCircuitPublicInputs { const startTreeSnapshots = new TreeSnapshots( - tailOutput.constants.historicalHeader.state.l1ToL2MessageTree, - tailOutput.startState.noteHashTree, - tailOutput.startState.nullifierTree, - tailOutput.startState.publicDataTree, + startStateReference.l1ToL2MessageTree, + startStateReference.partial.noteHashTree, + startStateReference.partial.nullifierTree, + startStateReference.partial.publicDataTree, + ); + const endTreeSnapshots = new TreeSnapshots( + endStateReference.l1ToL2MessageTree, + endStateReference.partial.noteHashTree, + endStateReference.partial.nullifierTree, + endStateReference.partial.publicDataTree, + ); + + const avmCircuitPublicInputs = trace.toAvmCircuitPublicInputs( + this.globalVariables, + startTreeSnapshots, + 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) => @@ -500,38 +578,64 @@ export class EnqueuedCallsProcessor { 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, + ); - const convertAvmAccumulatedData = (from: CombinedAccumulatedData) => - new AvmAccumulatedData( - from.noteHashes, - from.nullifiers, - from.l2ToL1Msgs, - from.unencryptedLogsHashes, - from.publicDataWrites, - ); - - // This is wrong. But this is not used or checked in the rollup at the moment. - // Should fetch the updated roots from db. - const endTreeSnapshots = startTreeSnapshots; - - return new AvmCircuitPublicInputs( - tailOutput.constants.globalVariables, - startTreeSnapshots, - tx.data.constants.txContext.gasSettings, - tx.data.forPublic!.nonRevertibleAccumulatedData.publicCallRequests, - tx.data.forPublic!.revertibleAccumulatedData.publicCallRequests, - tx.data.forPublic!.publicTeardownCallRequest, - getArrayLengths(tx.data.forPublic!.nonRevertibleAccumulatedData), - getArrayLengths(tx.data.forPublic!.revertibleAccumulatedData), - convertAccumulatedData(tx.data.forPublic!.nonRevertibleAccumulatedData), - convertAccumulatedData(tx.data.forPublic!.revertibleAccumulatedData), - endTreeSnapshots, - convertAvmAccumulatedData(tailOutput.end), - transactionFee, - !tailOutput.revertCode.equals(RevertCode.OK), + // 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, + ); + + return avmCircuitPublicInputs; } }