diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index ac5e88104742..e7b856597ed5 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -428,18 +428,21 @@ export class AvmPersistableStateManager { } /** - * Accept nested world state modifications + * Accept forked world state modifications & traced side effects / hints */ public mergeForkedState(forkedState: AvmPersistableStateManager) { this.publicStorage.acceptAndMerge(forkedState.publicStorage); this.nullifiers.acceptAndMerge(forkedState.nullifiers); - this.trace.mergeSuccessfulForkedTrace(forkedState.trace); + this.trace.mergeForkedTrace(forkedState.trace, /*reverted=*/ false); } + /** + * Reject forked world state modifications & traced side effects, keep traced hints + */ public rejectForkedState(forkedState: AvmPersistableStateManager) { this.publicStorage.acceptAndMerge(forkedState.publicStorage); this.nullifiers.acceptAndMerge(forkedState.nullifiers); - this.trace.mergeRevertedForkedTrace(forkedState.trace); + this.trace.mergeForkedTrace(forkedState.trace, /*reverted=*/ true); } /** 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 15fa4b76456a..f2d3f313f64a 100644 --- a/yarn-project/simulator/src/public/dual_side_effect_trace.ts +++ b/yarn-project/simulator/src/public/dual_side_effect_trace.ts @@ -232,12 +232,8 @@ export class DualSideEffectTrace implements PublicSideEffectTraceInterface { this.enqueuedCallTrace.traceEnqueuedCall(publicCallRequest, calldata, reverted); } - public mergeSuccessfulForkedTrace(nestedTrace: this) { - this.enqueuedCallTrace.mergeSuccessfulForkedTrace(nestedTrace.enqueuedCallTrace); - } - - public mergeRevertedForkedTrace(nestedTrace: this) { - this.enqueuedCallTrace.mergeRevertedForkedTrace(nestedTrace.enqueuedCallTrace); + public mergeForkedTrace(nestedTrace: this, reverted: boolean = false) { + this.enqueuedCallTrace.mergeForkedTrace(nestedTrace.enqueuedCallTrace, reverted); } /** 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 dc1ccb6bc45d..b339ac8c96d2 100644 --- a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts +++ b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts @@ -508,11 +508,7 @@ describe('Enqueued-call Side Effect Trace', () => { nestedTrace.traceGetContractInstance(address, /*exists=*/ false, contractInstance); testCounter++; - if (reverted) { - trace.mergeRevertedForkedTrace(nestedTrace); - } else { - trace.mergeSuccessfulForkedTrace(nestedTrace); - } + trace.mergeForkedTrace(nestedTrace, reverted); // parent trace adopts nested call's counter expect(trace.getCounter()).toBe(testCounter); diff --git a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts index b5f87c521b9a..6b06e838799f 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 @@ -523,36 +523,24 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI this.avmCircuitHints.enqueuedCalls.items.push(new AvmEnqueuedCallHint(publicCallRequest.contractAddress, calldata)); } - public mergeSuccessfulForkedTrace(nestedTrace: this) { + public mergeForkedTrace(nestedTrace: this, reverted: boolean = false) { // TODO(dbanks12): accept & merge nested trace's hints! this.sideEffectCounter = nestedTrace.sideEffectCounter; - this.enqueuedCalls.push(...nestedTrace.enqueuedCalls); - this.publicDataReads.push(...nestedTrace.publicDataReads); - this.publicDataWrites.push(...nestedTrace.publicDataWrites); - this.noteHashReadRequests.push(...nestedTrace.noteHashReadRequests); - this.noteHashes.push(...nestedTrace.noteHashes); - this.nullifierReadRequests.push(...nestedTrace.nullifierReadRequests); - this.nullifierNonExistentReadRequests.push(...nestedTrace.nullifierNonExistentReadRequests); - this.log.debug(`Merging nullifiers: ${nestedTrace.nullifiers.length}`); - this.log.debug(`Into parent nullifiers: ${this.nullifiers.length}`); - this.nullifiers.push(...nestedTrace.nullifiers); - this.log.debug(`After merge: ${JSON.stringify(this.nullifiers)}`); - this.l1ToL2MsgReadRequests.push(...nestedTrace.l1ToL2MsgReadRequests); - this.l2ToL1Messages.push(...nestedTrace.l2ToL1Messages); - this.unencryptedLogs.push(...nestedTrace.unencryptedLogs); - this.unencryptedLogsHashes.push(...nestedTrace.unencryptedLogsHashes); - } - - /** - * Discard accumulated side effects, but keep hints. - */ - public mergeRevertedForkedTrace(nestedTrace: this) { - // TODO(dbanks12): accept & merge nested trace's hints! - this.sideEffectCounter = nestedTrace.sideEffectCounter; - - this.enqueuedCalls.push(...nestedTrace.enqueuedCalls); + if (!reverted) { + this.publicDataReads.push(...nestedTrace.publicDataReads); + this.publicDataWrites.push(...nestedTrace.publicDataWrites); + this.noteHashReadRequests.push(...nestedTrace.noteHashReadRequests); + this.noteHashes.push(...nestedTrace.noteHashes); + this.nullifierReadRequests.push(...nestedTrace.nullifierReadRequests); + this.nullifierNonExistentReadRequests.push(...nestedTrace.nullifierNonExistentReadRequests); + this.nullifiers.push(...nestedTrace.nullifiers); + this.l1ToL2MsgReadRequests.push(...nestedTrace.l1ToL2MsgReadRequests); + this.l2ToL1Messages.push(...nestedTrace.l2ToL1Messages); + this.unencryptedLogs.push(...nestedTrace.unencryptedLogs); + this.unencryptedLogsHashes.push(...nestedTrace.unencryptedLogsHashes); + } } public getSideEffects(): SideEffects { @@ -617,7 +605,6 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI /** The call's results */ avmCallResults: AvmContractCallResult, ): VMCircuitPublicInputs { - this.log.debug(`Creating public inputs with call result: ${avmCallResults.reverted}`); return new VMCircuitPublicInputs( /*constants=*/ constants, /*callRequest=*/ callRequest, diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index 8e2a898ffbbf..8f96471d5bb5 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -7,6 +7,7 @@ import { TxExecutionPhase, } from '@aztec/circuit-types'; import { + type AvmCircuitPublicInputs, CombinedConstantData, Fr, Gas, @@ -29,40 +30,7 @@ import { DualSideEffectTrace } from './dual_side_effect_trace.js'; import { PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js'; import { type WorldStateDB } from './public_db_sources.js'; import { PublicSideEffectTrace } from './side_effect_trace.js'; -import { getCallRequestsByPhase, getExecutionRequestsByPhase, getPublicKernelCircuitPublicInputs } from './utils.js'; - -class PhaseStateManager { - private currentlyActiveStateManager: AvmPersistableStateManager | undefined; - - constructor(private readonly txStateManager: AvmPersistableStateManager) {} - - fork() { - assert(!this.currentlyActiveStateManager, 'Cannot fork when already forked'); - this.currentlyActiveStateManager = this.txStateManager.fork(); - } - - getActiveStateManager() { - return this.currentlyActiveStateManager || this.txStateManager; - } - - isForked() { - return !!this.currentlyActiveStateManager; - } - - mergeForkedState() { - assert(this.currentlyActiveStateManager, 'No forked state to merge'); - this.txStateManager.mergeForkedState(this.currentlyActiveStateManager!); - // Drop the forked state manager now that it is merged - this.currentlyActiveStateManager = undefined; - } - - discardForkedState() { - assert(this.currentlyActiveStateManager, 'No forked state to discard'); - this.txStateManager.rejectForkedState(this.currentlyActiveStateManager!); - // Drop the forked state manager. We don't want it! - this.currentlyActiveStateManager = undefined; - } -} +import { generateAvmCircuitPublicInputs, getCallRequestsByPhase, getExecutionRequestsByPhase, getPublicKernelCircuitPublicInputs } from './utils.js'; export class PublicTxContext { private log: DebugLogger; @@ -93,6 +61,7 @@ export class PublicTxContext { private readonly setupExecutionRequests: PublicExecutionRequest[], private readonly appLogicExecutionRequests: PublicExecutionRequest[], private readonly teardownExecutionRequests: PublicExecutionRequest[], + private firstPublicKernelOutput: PublicKernelCircuitPublicInputs, public latestPublicKernelOutput: PublicKernelCircuitPublicInputs, public trace: PublicEnqueuedCallSideEffectTrace, ) { @@ -107,19 +76,19 @@ export class PublicTxContext { globalVariables: GlobalVariables, ) { const privateKernelOutput = tx.data; - const latestPublicKernelOutput = getPublicKernelCircuitPublicInputs(privateKernelOutput, globalVariables); + const firstPublicKernelOutput = getPublicKernelCircuitPublicInputs(privateKernelOutput, globalVariables); - const nonRevertibleNullifiersFromPrivate = latestPublicKernelOutput.endNonRevertibleData.nullifiers + const nonRevertibleNullifiersFromPrivate = firstPublicKernelOutput.endNonRevertibleData.nullifiers .filter(n => !n.isEmpty()) .map(n => n.value); - const _revertibleNullifiersFromPrivate = latestPublicKernelOutput.end.nullifiers + const _revertibleNullifiersFromPrivate = firstPublicKernelOutput.end.nullifiers .filter(n => !n.isEmpty()) .map(n => n.value); // During SETUP, non revertible side effects from private are our "previous data" - const prevAccumulatedData = latestPublicKernelOutput.endNonRevertibleData; + const prevAccumulatedData = firstPublicKernelOutput.endNonRevertibleData; const previousValidationRequestArrayLengths = PublicValidationRequestArrayLengths.new( - latestPublicKernelOutput.validationRequests, + firstPublicKernelOutput.validationRequests, ); const previousAccumulatedDataArrayLengths = PublicAccumulatedDataArrayLengths.new(prevAccumulatedData); @@ -153,7 +122,8 @@ export class PublicTxContext { getExecutionRequestsByPhase(tx, TxExecutionPhase.SETUP), getExecutionRequestsByPhase(tx, TxExecutionPhase.APP_LOGIC), getExecutionRequestsByPhase(tx, TxExecutionPhase.TEARDOWN), - latestPublicKernelOutput, + firstPublicKernelOutput, + firstPublicKernelOutput, enqueuedCallTrace, ); } @@ -251,7 +221,7 @@ export class PublicTxContext { /** * Compute the gas used using the actual gas used during teardown instead * of the teardown gas limit. - * Note that this.startGasUsed comes from private and private includes + * Note that this.gasUsed comes from private and private includes * teardown gas limit in its output gasUsed. */ getActualGasUsed(): Gas { @@ -265,7 +235,7 @@ export class PublicTxContext { return this.gasUsed; } - getTransactionFeeAtCurrentPhase(): Fr { + getTransactionFee(): Fr { if (this.currentPhase === TxExecutionPhase.TEARDOWN) { return this.getTransactionFeeUnsafe(); } else { @@ -273,7 +243,7 @@ export class PublicTxContext { } } - getTransactionFee(): Fr { + getFinalTransactionFee(): Fr { assert(this.currentPhase === TxExecutionPhase.TEARDOWN, 'Transaction fee is only known during/after teardown'); return this.getTransactionFeeUnsafe(); } @@ -287,4 +257,60 @@ export class PublicTxContext { }); return txFee; } + + private async generateAvmCircuitPublicInputs(endStateReference: StateReference): Promise { + assert(this.currentPhase === TxExecutionPhase.TEARDOWN, 'Can only get AvmCircuitPublicInputs after teardown (tx done)'); + return generateAvmCircuitPublicInputs( + this.tx, + this.trace, + this.globalVariables, + this.startStateReference, + endStateReference, + this.gasUsed, + this.getFinalTransactionFee(), + this.revertCode, + this.firstPublicKernelOutput, + ); + } + + async generateProvingRequest(endStateReference: StateReference): Promise { + // TODO(dbanks12): Once we actually have tx-level proving, this will generate the entire + // proving request for the first time + this.avmProvingRequest!.inputs.output = await this.generateAvmCircuitPublicInputs(endStateReference); + return this.avmProvingRequest!; + } } + +class PhaseStateManager { + private currentlyActiveStateManager: AvmPersistableStateManager | undefined; + + constructor(private readonly txStateManager: AvmPersistableStateManager) {} + + fork() { + assert(!this.currentlyActiveStateManager, 'Cannot fork when already forked'); + this.currentlyActiveStateManager = this.txStateManager.fork(); + } + + getActiveStateManager() { + return this.currentlyActiveStateManager || this.txStateManager; + } + + isForked() { + return !!this.currentlyActiveStateManager; + } + + mergeForkedState() { + assert(this.currentlyActiveStateManager, 'No forked state to merge'); + this.txStateManager.mergeForkedState(this.currentlyActiveStateManager!); + // Drop the forked state manager now that it is merged + this.currentlyActiveStateManager = undefined; + } + + discardForkedState() { + assert(this.currentlyActiveStateManager, 'No forked state to discard'); + this.txStateManager.rejectForkedState(this.currentlyActiveStateManager!); + // Drop the forked state manager. We don't want it! + this.currentlyActiveStateManager = undefined; + } +} + diff --git a/yarn-project/simulator/src/public/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator.ts index e694b45dc9c8..b2f15d8bd8f7 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.ts @@ -17,7 +17,7 @@ import { type WorldStateDB } from './public_db_sources.js'; import { type PublicKernelCircuitSimulator } from './public_kernel_circuit_simulator.js'; import { PublicKernelTailSimulator } from './public_kernel_tail_simulator.js'; import { PublicTxContext } from './public_tx_context.js'; -import { generateAvmCircuitPublicInputs, runMergeKernelCircuit } from './utils.js'; +import { generateAvmCircuitPublicInputs, generateAvmCircuitPublicInputsDeprecated, runMergeKernelCircuit } from './utils.js'; export type ProcessedPhase = { phase: TxExecutionPhase; @@ -94,16 +94,15 @@ export class PublicTxSimulator { result => result !== undefined, ) as ProcessedPhase[]; - const _endStateReference = await this.db.getStateReference(); - const transactionFee = context.getTransactionFee(); + const endStateReference = await this.db.getStateReference(); const tailKernelOutput = await this.publicKernelTailSimulator.simulate(context.latestPublicKernelOutput); - context.avmProvingRequest!.inputs.output = generateAvmCircuitPublicInputs( + generateAvmCircuitPublicInputsDeprecated( tx, tailKernelOutput, context.getGasUsedForFee(), - transactionFee, + context.getFinalTransactionFee(), ); const gasUsed = { @@ -111,7 +110,7 @@ export class PublicTxSimulator { teardownGas: context.teardownGasUsed, }; return { - avmProvingRequest: context.avmProvingRequest!, + avmProvingRequest: await context.generateProvingRequest(endStateReference), gasUsed, revertCode: context.revertCode, revertReason: context.revertReason, @@ -212,7 +211,7 @@ export class PublicTxSimulator { executionRequest, context.constants, /*availableGas=*/ context.getGasLeftForCurrentPhase(), - /*transactionFee=*/ context.getTransactionFeeAtCurrentPhase(), + /*transactionFee=*/ context.getTransactionFee(), enqueuedCallStateManager, ); diff --git a/yarn-project/simulator/src/public/side_effect_trace.ts b/yarn-project/simulator/src/public/side_effect_trace.ts index efe7a3afa6b9..31d6b66b3419 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.ts @@ -440,11 +440,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { throw new Error('Not implemented'); } - public mergeSuccessfulForkedTrace(_nestedTrace: this) { - throw new Error('Not implemented'); - } - - public mergeRevertedForkedTrace(_nestedTrace: this) { + public mergeForkedTrace(_nestedTrace: this, _reverted: boolean = false) { throw new Error('Not implemented'); } diff --git a/yarn-project/simulator/src/public/side_effect_trace_interface.ts b/yarn-project/simulator/src/public/side_effect_trace_interface.ts index 021791570710..db3ddcc68294 100644 --- a/yarn-project/simulator/src/public/side_effect_trace_interface.ts +++ b/yarn-project/simulator/src/public/side_effect_trace_interface.ts @@ -18,6 +18,7 @@ import { type EnqueuedPublicCallExecutionResultWithSideEffects, type PublicFunct export interface PublicSideEffectTraceInterface { fork(incrementSideEffectCounter?: boolean): PublicSideEffectTraceInterface; + mergeForkedTrace(nestedTrace: PublicSideEffectTraceInterface, reverted?: boolean): void; getCounter(): number; // all "trace*" functions can throw SideEffectLimitReachedError tracePublicStorageRead( @@ -101,8 +102,6 @@ export interface PublicSideEffectTraceInterface { /** Did the call revert? */ reverted: boolean, ): void; - mergeSuccessfulForkedTrace(nestedTrace: PublicSideEffectTraceInterface): void; - mergeRevertedForkedTrace(nestedTrace: PublicSideEffectTraceInterface): void; toPublicEnqueuedCallExecutionResult( /** How much gas was left after this public execution. */ endGasLeft: Gas, diff --git a/yarn-project/simulator/src/public/utils.ts b/yarn-project/simulator/src/public/utils.ts index 8d3f6093f758..05b9064a434d 100644 --- a/yarn-project/simulator/src/public/utils.ts +++ b/yarn-project/simulator/src/public/utils.ts @@ -27,10 +27,22 @@ import { countAccumulatedItems, makeEmptyProof, makeEmptyRecursiveProof, + StateReference, + mergeAccumulatedData, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, + MAX_L2_TO_L1_MSGS_PER_TX, + MAX_UNENCRYPTED_LOGS_PER_TX, + PublicDataWrite, + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, } from '@aztec/circuits.js'; import { getVKSiblingPath } from '@aztec/noir-protocol-circuits-types'; import { type PublicKernelCircuitSimulator } from './public_kernel_circuit_simulator.js'; +import { PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js'; +import { assertLength } from '@aztec/foundation/serialize'; +import { padArrayEnd } from '@aztec/foundation/collection'; +import { inspect } from 'util'; export function getExecutionRequestsByPhase(tx: Tx, phase: TxExecutionPhase): PublicExecutionRequest[] { switch (phase) { @@ -96,7 +108,7 @@ export function getPublicKernelCircuitPublicInputs( } // Temporary hack to create the AvmCircuitPublicInputs from public tail's public inputs. -export function generateAvmCircuitPublicInputs( +export function generateAvmCircuitPublicInputsDeprecated( tx: Tx, tailOutput: KernelCircuitPublicInputs, gasUsedForFee: Gas, @@ -150,7 +162,6 @@ export function generateAvmCircuitPublicInputs( transactionFee, !tailOutput.revertCode.equals(RevertCode.OK), ); - //console.log(`[FROM TAIL] AVM: ${inspect(avmCircuitpublicInputs, { depth: 5 })}`); return avmCircuitpublicInputs; } @@ -180,3 +191,131 @@ export async function runMergeKernelCircuit( return await publicKernelSimulator.publicKernelCircuitMerge(inputs); } + +export function generateAvmCircuitPublicInputs( + tx: Tx, + trace: PublicEnqueuedCallSideEffectTrace, + globalVariables: GlobalVariables, + startStateReference: StateReference, + endStateReference: StateReference, + endGasUsed: Gas, + 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( + 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, + endGasUsed, + 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, + ); + return avmCircuitPublicInputs; +}