From df3bcc6315ba6ded3a352f7374888504ecc48eb9 Mon Sep 17 00:00:00 2001 From: David Banks <47112877+dbanks12@users.noreply.github.com> Date: Mon, 6 May 2024 11:45:02 -0400 Subject: [PATCH] chore(avm-simulator): track recursive public execution result in avm-simulator for integration with old kernel (#6106) --- .../simulator/src/avm/journal/journal.ts | 111 +++++++++++++++++- .../simulator/src/avm/journal/trace.ts | 3 - .../src/avm/opcodes/external_calls.ts | 2 + 3 files changed, 108 insertions(+), 8 deletions(-) diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 5c21dd175a3..48208c21d4e 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -1,9 +1,20 @@ import { UnencryptedL2Log } from '@aztec/circuit-types'; -import { AztecAddress, EthAddress, L2ToL1Message } from '@aztec/circuits.js'; +import { + AztecAddress, + ContractStorageRead, + ContractStorageUpdateRequest, + EthAddress, + L2ToL1Message, + NoteHash, + Nullifier, + ReadRequest, + SideEffect, +} from '@aztec/circuits.js'; import { EventSelector } from '@aztec/foundation/abi'; import { Fr } from '@aztec/foundation/fields'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; +import { type PublicExecutionResult } from '../../index.js'; import { type HostStorage } from './host_storage.js'; import { Nullifiers } from './nullifiers.js'; import { PublicStorage } from './public_storage.js'; @@ -39,6 +50,23 @@ export type JournalData = { currentStorageValue: Map>; }; +// TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit +type PartialPublicExecutionResult = { + nullifierReadRequests: ReadRequest[]; + nullifierNonExistentReadRequests: ReadRequest[]; + newNoteHashes: NoteHash[]; + newL2ToL1Messages: L2ToL1Message[]; + startSideEffectCounter: number; + newNullifiers: Nullifier[]; + contractStorageReads: ContractStorageRead[]; + contractStorageUpdateRequests: ContractStorageUpdateRequest[]; + unencryptedLogsHashes: SideEffect[]; + unencryptedLogs: UnencryptedL2Log[]; + unencryptedLogPreimagesLength: Fr; + allUnencryptedLogs: UnencryptedL2Log[]; + nestedExecutions: PublicExecutionResult[]; +}; + /** * A class to manage persistable AVM state for contract calls. * Maintains a cache of the current world state, @@ -67,11 +95,30 @@ export class AvmPersistableStateManager { public newL1Messages: L2ToL1Message[] = []; public newLogs: UnencryptedL2Log[] = []; + // TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit + public transitionalExecutionResult: PartialPublicExecutionResult; + constructor(hostStorage: HostStorage, parent?: AvmPersistableStateManager) { this.hostStorage = hostStorage; this.publicStorage = new PublicStorage(hostStorage.publicStateDb, parent?.publicStorage); this.nullifiers = new Nullifiers(hostStorage.commitmentsDb, parent?.nullifiers); this.trace = new WorldStateAccessTrace(parent?.trace); + + this.transitionalExecutionResult = { + nullifierReadRequests: [], + nullifierNonExistentReadRequests: [], + newNoteHashes: [], + newL2ToL1Messages: [], + startSideEffectCounter: this.trace.accessCounter, + newNullifiers: [], + contractStorageReads: [], + contractStorageUpdateRequests: [], + unencryptedLogsHashes: [], + unencryptedLogs: [], + unencryptedLogPreimagesLength: new Fr(0), + allUnencryptedLogs: [], + nestedExecutions: [], + }; } /** @@ -92,6 +139,12 @@ export class AvmPersistableStateManager { this.log.debug(`storage(${storageAddress})@${slot} <- ${value}`); // Cache storage writes for later reference/reads this.publicStorage.write(storageAddress, slot, value); + + // TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit + this.transitionalExecutionResult.contractStorageUpdateRequests.push( + new ContractStorageUpdateRequest(slot, value, this.trace.accessCounter, storageAddress), + ); + // Trace all storage writes (even reverted ones) this.trace.tracePublicStorageWrite(storageAddress, slot, value); } @@ -106,6 +159,12 @@ export class AvmPersistableStateManager { public async readStorage(storageAddress: Fr, slot: Fr): Promise { const [exists, value] = await this.publicStorage.read(storageAddress, slot); this.log.debug(`storage(${storageAddress})@${slot} ?? value: ${value}, exists: ${exists}.`); + + // TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit + this.transitionalExecutionResult.contractStorageReads.push( + new ContractStorageRead(slot, value, this.trace.accessCounter, storageAddress), + ); + // We want to keep track of all performed reads (even reverted ones) this.trace.tracePublicStorageRead(storageAddress, slot, value, exists); return Promise.resolve(value); @@ -133,6 +192,9 @@ export class AvmPersistableStateManager { * @param noteHash - the unsiloed note hash to write */ public writeNoteHash(storageAddress: Fr, noteHash: Fr) { + // TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit + this.transitionalExecutionResult.newNoteHashes.push(new NoteHash(noteHash, this.trace.accessCounter)); + this.log.debug(`noteHashes(${storageAddress}) += @${noteHash}.`); this.trace.traceNewNoteHash(storageAddress, noteHash); } @@ -148,6 +210,16 @@ export class AvmPersistableStateManager { this.log.debug( `nullifiers(${storageAddress})@${nullifier} ?? leafIndex: ${leafIndex}, pending: ${isPending}, exists: ${exists}.`, ); + + // TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit + if (exists) { + this.transitionalExecutionResult.nullifierReadRequests.push(new ReadRequest(nullifier, this.trace.accessCounter)); + } else { + this.transitionalExecutionResult.nullifierNonExistentReadRequests.push( + new ReadRequest(nullifier, this.trace.accessCounter), + ); + } + this.trace.traceNullifierCheck(storageAddress, nullifier, exists, isPending, leafIndex); return Promise.resolve(exists); } @@ -158,6 +230,9 @@ export class AvmPersistableStateManager { * @param nullifier - the unsiloed nullifier to write */ public async writeNullifier(storageAddress: Fr, nullifier: Fr) { + // TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit + this.transitionalExecutionResult.newNullifiers.push(new Nullifier(nullifier, this.trace.accessCounter, Fr.ZERO)); + this.log.debug(`nullifiers(${storageAddress}) += ${nullifier}.`); // Cache pending nullifiers for later access await this.nullifiers.append(storageAddress, nullifier); @@ -189,18 +264,39 @@ export class AvmPersistableStateManager { public writeL1Message(recipient: EthAddress | Fr, content: Fr) { this.log.debug(`L1Messages(${recipient}) += ${content}.`); const recipientAddress = recipient instanceof EthAddress ? recipient : EthAddress.fromField(recipient); - this.newL1Messages.push(new L2ToL1Message(recipientAddress, content)); + const message = new L2ToL1Message(recipientAddress, content); + this.newL1Messages.push(message); + + // TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit + this.transitionalExecutionResult.newL2ToL1Messages.push(message); } public writeLog(contractAddress: Fr, event: Fr, log: Fr[]) { this.log.debug(`UnencryptedL2Log(${contractAddress}) += event ${event} with ${log.length} fields.`); - const L2log = new UnencryptedL2Log( + const ulog = new UnencryptedL2Log( AztecAddress.fromField(contractAddress), EventSelector.fromField(event), Buffer.concat(log.map(f => f.toBuffer())), ); - this.newLogs.push(L2log); - this.trace.traceNewLog(Fr.fromBuffer(L2log.hash())); + const logHash = Fr.fromBuffer(ulog.hash()); + + // TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit + this.transitionalExecutionResult.unencryptedLogs.push(ulog); + this.transitionalExecutionResult.allUnencryptedLogs.push(ulog); + // this duplicates exactly what happens in the trace just for the purpose of transitional integration with the kernel + this.transitionalExecutionResult.unencryptedLogsHashes.push( + new SideEffect(logHash, new Fr(this.trace.accessCounter)), + ); + // Duplicates computation performed in public_context.nr::emit_unencrypted_log + // 44 = addr (32) + selector (4) + raw log len (4) + processed log len (4). + this.transitionalExecutionResult.unencryptedLogPreimagesLength = new Fr( + this.transitionalExecutionResult.unencryptedLogPreimagesLength.toNumber() + 44 + log.length * Fr.SIZE_IN_BYTES, + ); + // TODO(6206): likely need to track this here and not just in the transitional logic. + + // TODO(6205): why are logs pushed here but logs hashes are traced? + this.newLogs.push(ulog); + this.trace.traceNewLog(logHash); } /** @@ -216,6 +312,11 @@ export class AvmPersistableStateManager { // Accrued Substate this.newL1Messages = this.newL1Messages.concat(nestedJournal.newL1Messages); this.newLogs = this.newLogs.concat(nestedJournal.newLogs); + + // TRANSITIONAL: This should be removed once the kernel handles and entire enqueued call per circuit + this.transitionalExecutionResult.allUnencryptedLogs.concat( + nestedJournal.transitionalExecutionResult.allUnencryptedLogs, + ); } /** diff --git a/yarn-project/simulator/src/avm/journal/trace.ts b/yarn-project/simulator/src/avm/journal/trace.ts index 8cb115eae67..68ed4b442e9 100644 --- a/yarn-project/simulator/src/avm/journal/trace.ts +++ b/yarn-project/simulator/src/avm/journal/trace.ts @@ -151,9 +151,6 @@ export class WorldStateAccessTrace { /** * Merges another trace into this one * - * - Public state journals (r/w logs), with the accessing being appended in chronological order - * - Utxo objects are concatenated - * * @param incomingTrace - the incoming trace to merge into this instance */ public acceptAndMerge(incomingTrace: WorldStateAccessTrace) { diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.ts index 5985f9f78ad..9d2a8fa68d4 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.ts @@ -79,6 +79,8 @@ abstract class ExternalCall extends Instruction { ); const pxContext = createPublicExecutionContext(nestedContext, calldata); const pxResults = await executePublicFunction(pxContext, /*nested=*/ true); + // store the old PublicExecutionResult object to maintain a recursive data structure for the old kernel + context.persistableState.transitionalExecutionResult.nestedExecutions.push(pxResults); const nestedCallResults: AvmContractCallResults = convertPublicExecutionResult(pxResults); updateAvmContextFromPublicExecutionResult(nestedContext, pxResults); const nestedPersistableState = nestedContext.persistableState;