From 0ba3950c754653169b70087639871b848dcf24ae Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Thu, 14 Nov 2024 17:21:23 +0000 Subject: [PATCH] refactor: stop calling public kernels --- .../simulator/src/public/public_tx_context.ts | 126 +++++++++------ .../src/public/public_tx_processor.ts | 12 +- yarn-project/simulator/src/public/utils.ts | 152 +++++++++++++++++- 3 files changed, 235 insertions(+), 55 deletions(-) diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index a3be696a019..f4143d13174 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -18,6 +18,8 @@ import { PublicValidationRequestArrayLengths, RevertCode, type StateReference, + AvmCircuitPublicInputs, + PrivateKernelTailCircuitPublicInputs, } from '@aztec/circuits.js'; import { assert } from 'console'; @@ -27,40 +29,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 currentPhase: TxExecutionPhase = TxExecutionPhase.SETUP; @@ -76,7 +45,7 @@ export class PublicTxContext { public avmProvingRequest: AvmProvingRequest | undefined; // tmp hack constructor( - public readonly state: PhaseStateManager, + public readonly state: PhaseStateForker, public readonly tx: Tx, // tmp hack public readonly globalVariables: GlobalVariables, public readonly constants: CombinedConstantData, // tmp hack @@ -89,7 +58,9 @@ export class PublicTxContext { private readonly setupExecutionRequests: PublicExecutionRequest[], private readonly appLogicExecutionRequests: PublicExecutionRequest[], private readonly teardownExecutionRequests: PublicExecutionRequest[], + private firstPublicKernelOutput: PublicKernelCircuitPublicInputs, public latestPublicKernelOutput: PublicKernelCircuitPublicInputs, + private enqueuedCallTrace: PublicEnqueuedCallSideEffectTrace, ) { this.gasUsed = startGasUsed; } @@ -101,19 +72,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); @@ -134,7 +105,7 @@ export class PublicTxContext { ); return new PublicTxContext( - new PhaseStateManager(txStateManager), + new PhaseStateForker(txStateManager), tx, globalVariables, CombinedConstantData.combine(tx.data.constants, globalVariables), @@ -147,7 +118,9 @@ export class PublicTxContext { getExecutionRequestsByPhase(tx, TxExecutionPhase.SETUP), getExecutionRequestsByPhase(tx, TxExecutionPhase.APP_LOGIC), getExecutionRequestsByPhase(tx, TxExecutionPhase.TEARDOWN), - latestPublicKernelOutput, + firstPublicKernelOutput, + firstPublicKernelOutput, + enqueuedCallTrace ); } @@ -176,11 +149,6 @@ export class PublicTxContext { } revert(revertReason: SimulationError | undefined = undefined, culprit = '') { - if (revertReason && !this.revertReason) { - // don't override revertReason - // (if app logic and teardown both revert, we want app logic's reason) - this.revertReason = revertReason; - } if (this.currentPhase === TxExecutionPhase.SETUP) { throw new Error( `Setup phase reverted! The transaction will be thrown out. ${culprit} failed with reason: ${revertReason}`, @@ -194,6 +162,11 @@ export class PublicTxContext { this.revertCode = RevertCode.TEARDOWN_REVERTED; } } + if (revertReason && !this.revertReason) { + // don't override revertReason + // (if app logic and teardown both revert, we want app logic's reason) + this.revertReason = revertReason; + } } getCallRequestsForCurrentPhase(): PublicCallRequest[] { @@ -266,4 +239,63 @@ export class PublicTxContext { //this.log.debug(`Computed tx fee`, { txFee, gasUsed: inspect(phaseGasUsed), gasFees: inspect(gasFees) }); return this.gasUsed.computeFee(this.globalVariables.gasFees); } + + private async generateAvmCircuitPublicInputs(endStateReference: StateReference): Promise { + assert(this.currentPhase === TxExecutionPhase.TEARDOWN, 'Can only get AvmCircuitPublicInputs after teardown (tx done)'); + return generateAvmCircuitPublicInputs( + this.tx, + this.enqueuedCallTrace, + this.globalVariables, + this.startStateReference, + endStateReference, + this.getActualGasUsed(), + this.getTransactionFee(), + 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!; + } +} + +/** + * A thin wrapper for the state manager for forking and merging state per phase. + * This lets us keep track of + */ +class PhaseStateForker { + 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_processor.ts b/yarn-project/simulator/src/public/public_tx_processor.ts index b9c2c412989..f01ebf569c7 100644 --- a/yarn-project/simulator/src/public/public_tx_processor.ts +++ b/yarn-project/simulator/src/public/public_tx_processor.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,14 @@ export class PublicTxProcessor { 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.getTransactionFee(), ); const gasUsed = { @@ -111,7 +109,7 @@ export class PublicTxProcessor { teardownGas: context.teardownGasUsed, }; return { - avmProvingRequest: context.avmProvingRequest!, + avmProvingRequest: await context.generateProvingRequest(endStateReference), gasUsed, revertCode: context.revertCode, revertReason: context.revertReason, diff --git a/yarn-project/simulator/src/public/utils.ts b/yarn-project/simulator/src/public/utils.ts index 8d3f6093f75..cfd586ae457 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, @@ -180,3 +192,141 @@ 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) { + console.log(`Public data write: ${JSON.stringify(publicDataWrite)}`); + 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, + ); + + console.log(`New AVM circuit nullifiers: ${JSON.stringify(avmCircuitPublicInputs.accumulatedData.nullifiers)}`); + console.log(`New AVM circuit note hashes: ${JSON.stringify(avmCircuitPublicInputs.accumulatedData.noteHashes)}`); + console.log( + `New AVM circuit public writes: ${JSON.stringify(avmCircuitPublicInputs.accumulatedData.publicDataWrites)}`, + ); + console.log(`New AVM: ${inspect(avmCircuitPublicInputs, { depth: 5 })}`); + + return avmCircuitPublicInputs; +} +