Skip to content

Commit

Permalink
feat: avm side effect trace accounts for array lengths from previous …
Browse files Browse the repository at this point in the history
…kernel
  • Loading branch information
dbanks12 committed Oct 2, 2024
1 parent 9d69cc7 commit daf6dc5
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 19 deletions.
22 changes: 16 additions & 6 deletions yarn-project/simulator/src/public/enqueued_call_simulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ export class EnqueuedCallSimulator {
): Promise<EnqueuedCallResult> {
const pendingNullifiers = this.getSiloedPendingNullifiers(previousPublicKernelOutput);
const startSideEffectCounter = previousPublicKernelOutput.endSideEffectCounter + 1;

const prevAccumulatedData =
phase === PublicKernelPhase.SETUP
? previousPublicKernelOutput.endNonRevertibleData
: previousPublicKernelOutput.end;
const previousValidationRequestArrayLengths = PublicValidationRequestArrayLengths.new(
previousPublicKernelOutput.validationRequests,
);
const previousAccumulatedDataArrayLengths = PublicAccumulatedDataArrayLengths.new(prevAccumulatedData);

const result = await this.publicExecutor.simulate(
executionRequest,
this.globalVariables,
Expand All @@ -131,25 +141,25 @@ export class EnqueuedCallSimulator {
pendingNullifiers,
transactionFee,
startSideEffectCounter,
previousValidationRequestArrayLengths,
previousAccumulatedDataArrayLengths,
);

const callStack = makeTuple(MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, PublicInnerCallRequest.empty);
callStack[0].item.contractAddress = callRequest.contractAddress;
callStack[0].item.callContext = callRequest.callContext;
callStack[0].item.argsHash = callRequest.argsHash;
const prevAccumulatedData =
phase === PublicKernelPhase.SETUP
? previousPublicKernelOutput.endNonRevertibleData
: previousPublicKernelOutput.end;

const accumulatedData = PublicAccumulatedData.empty();
accumulatedData.publicCallStack[0] = callRequest;

const startVMCircuitOutput = new VMCircuitPublicInputs(
previousPublicKernelOutput.constants,
callRequest,
callStack,
PublicValidationRequestArrayLengths.new(previousPublicKernelOutput.validationRequests),
previousValidationRequestArrayLengths,
PublicValidationRequests.empty(),
PublicAccumulatedDataArrayLengths.new(prevAccumulatedData),
previousAccumulatedDataArrayLengths,
accumulatedData,
startSideEffectCounter,
startSideEffectCounter,
Expand Down
19 changes: 17 additions & 2 deletions yarn-project/simulator/src/public/executor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { type PublicExecutionRequest } from '@aztec/circuit-types';
import { type AvmSimulationStats } from '@aztec/circuit-types/stats';
import { Fr, Gas, type GlobalVariables, type Header, type Nullifier, type TxContext } from '@aztec/circuits.js';
import {
Fr,
Gas,
type GlobalVariables,
type Header,
type Nullifier,
PublicAccumulatedDataArrayLengths,
PublicValidationRequestArrayLengths,
type TxContext,
} from '@aztec/circuits.js';
import { createDebugLogger } from '@aztec/foundation/log';
import { Timer } from '@aztec/foundation/timer';
import { type TelemetryClient } from '@aztec/telemetry-client';
Expand Down Expand Up @@ -46,6 +55,8 @@ export class PublicExecutor {
pendingSiloedNullifiers: Nullifier[],
transactionFee: Fr = Fr.ZERO,
startSideEffectCounter: number = 0,
previousValidationRequestArrayLengths: PublicValidationRequestArrayLengths = PublicValidationRequestArrayLengths.empty(),
previousAccumulatedDataArrayLengths: PublicAccumulatedDataArrayLengths = PublicAccumulatedDataArrayLengths.empty(),
): Promise<PublicExecutionResult> {
const address = executionRequest.contractAddress;
const selector = executionRequest.callContext.functionSelector;
Expand All @@ -54,7 +65,11 @@ export class PublicExecutor {
PublicExecutor.log.verbose(`[AVM] Executing public external function ${fnName}.`);
const timer = new Timer();

const trace = new PublicSideEffectTrace(startSideEffectCounter);
const trace = new PublicSideEffectTrace(
startSideEffectCounter,
previousValidationRequestArrayLengths,
previousAccumulatedDataArrayLengths,
);
const avmPersistableState = AvmPersistableStateManager.newWithPendingSiloedNullifiers(
this.worldStateDB,
trace,
Expand Down
57 changes: 57 additions & 0 deletions yarn-project/simulator/src/public/side_effect_trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
MAX_PUBLIC_DATA_READS_PER_TX,
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
MAX_UNENCRYPTED_LOGS_PER_TX,
PublicAccumulatedDataArrayLengths,
PublicValidationRequestArrayLengths,
} from '@aztec/circuits.js';
import { Fr } from '@aztec/foundation/fields';
import { SerializableContractInstance } from '@aztec/types/contracts';
Expand Down Expand Up @@ -367,6 +369,61 @@ describe('Side Effect Trace', () => {
SideEffectLimitReachedError,
);
});

it('PreviousValidationRequestArrayLengths and PreviousAccumulatedDataArrayLengths contribute to limits', () => {
trace = new PublicSideEffectTrace(
0,
new PublicValidationRequestArrayLengths(
MAX_NOTE_HASH_READ_REQUESTS_PER_TX,
MAX_NULLIFIER_READ_REQUESTS_PER_TX,
MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX,
MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX,
MAX_PUBLIC_DATA_READS_PER_TX,
),
new PublicAccumulatedDataArrayLengths(
MAX_NOTE_HASHES_PER_TX,
MAX_NULLIFIERS_PER_TX,
MAX_L2_TO_L1_MSGS_PER_TX,
0,
0,
MAX_UNENCRYPTED_LOGS_PER_TX,
MAX_PUBLIC_DATA_READS_PER_TX,
0,
),
);
expect(() => trace.tracePublicStorageRead(new Fr(42), new Fr(42), new Fr(42), true, true)).toThrow(
SideEffectLimitReachedError,
);
expect(() => trace.tracePublicStorageWrite(new Fr(42), new Fr(42), new Fr(42))).toThrow(
SideEffectLimitReachedError,
);
expect(() => trace.traceNoteHashCheck(new Fr(42), new Fr(42), new Fr(42), true)).toThrow(
SideEffectLimitReachedError,
);
expect(() => trace.traceNewNoteHash(new Fr(42), new Fr(42))).toThrow(SideEffectLimitReachedError);
expect(() => trace.traceNullifierCheck(new Fr(42), new Fr(42), new Fr(42), false, true)).toThrow(
SideEffectLimitReachedError,
);
expect(() => trace.traceNullifierCheck(new Fr(42), new Fr(42), new Fr(42), true, true)).toThrow(
SideEffectLimitReachedError,
);
expect(() => trace.traceNewNullifier(new Fr(42), new Fr(42))).toThrow(SideEffectLimitReachedError);
expect(() => trace.traceL1ToL2MessageCheck(new Fr(42), new Fr(42), new Fr(42), true)).toThrow(
SideEffectLimitReachedError,
);
expect(() => trace.traceNewL2ToL1Message(new Fr(42), new Fr(42), new Fr(42))).toThrow(
SideEffectLimitReachedError,
);
expect(() => trace.traceUnencryptedLog(new Fr(42), [new Fr(42), new Fr(42)])).toThrow(
SideEffectLimitReachedError,
);
expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: false })).toThrow(
SideEffectLimitReachedError,
);
expect(() => trace.traceGetContractInstance({ ...contractInstance, exists: true })).toThrow(
SideEffectLimitReachedError,
);
});
});

it('Should trace nested calls', () => {
Expand Down
52 changes: 41 additions & 11 deletions yarn-project/simulator/src/public/side_effect_trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import {
MAX_UNENCRYPTED_LOGS_PER_TX,
NoteHash,
Nullifier,
PublicAccumulatedDataArrayLengths,
type PublicInnerCallRequest,
PublicValidationRequestArrayLengths,
ReadRequest,
TreeLeafReadRequest,
} from '@aztec/circuits.js';
Expand Down Expand Up @@ -73,6 +75,8 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
constructor(
/** The counter of this trace's first side effect. */
public readonly startSideEffectCounter: number = 0,
private readonly previousValidationRequestArrayLengths: PublicValidationRequestArrayLengths = PublicValidationRequestArrayLengths.empty(),
private readonly previousAccumulatedDataArrayLengths: PublicAccumulatedDataArrayLengths = PublicAccumulatedDataArrayLengths.empty(),
) {
this.sideEffectCounter = startSideEffectCounter;
this.avmCircuitHints = AvmExecutionHints.empty();
Expand All @@ -90,11 +94,14 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
this.sideEffectCounter++;
}

// TODO(dbanks12): checks against tx-wide limit need access to parent trace's length
// TODO(dbanks12): checks against tx-wide limit don't take into account side effects in child/nested traces

public tracePublicStorageRead(storageAddress: Fr, slot: Fr, value: Fr, _exists: boolean, _cached: boolean) {
// NOTE: exists and cached are unused for now but may be used for optimizations or kernel hints later
if (this.contractStorageReads.length >= MAX_PUBLIC_DATA_READS_PER_TX) {
if (
this.contractStorageReads.length + this.previousValidationRequestArrayLengths.publicDataReads >=
MAX_PUBLIC_DATA_READS_PER_TX
) {
throw new SideEffectLimitReachedError('contract storage read', MAX_PUBLIC_DATA_READS_PER_TX);
}
this.contractStorageReads.push(
Expand All @@ -108,7 +115,10 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
}

public tracePublicStorageWrite(storageAddress: Fr, slot: Fr, value: Fr) {
if (this.contractStorageUpdateRequests.length >= MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) {
if (
this.contractStorageUpdateRequests.length + this.previousAccumulatedDataArrayLengths.publicDataUpdateRequests >=
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
) {
throw new SideEffectLimitReachedError('contract storage write', MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX);
}
this.contractStorageUpdateRequests.push(
Expand All @@ -121,7 +131,10 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
// TODO(8287): _exists can be removed once we have the vm properly handling the equality check
public traceNoteHashCheck(_storageAddress: Fr, noteHash: Fr, leafIndex: Fr, exists: boolean) {
// NOTE: storageAddress is unused but will be important when an AVM circuit processes an entire enqueued call
if (this.noteHashReadRequests.length >= MAX_NOTE_HASH_READ_REQUESTS_PER_TX) {
if (
this.noteHashReadRequests.length + this.previousValidationRequestArrayLengths.noteHashReadRequests >=
MAX_NOTE_HASH_READ_REQUESTS_PER_TX
) {
throw new SideEffectLimitReachedError('note hash read request', MAX_NOTE_HASH_READ_REQUESTS_PER_TX);
}
this.noteHashReadRequests.push(new TreeLeafReadRequest(noteHash, leafIndex));
Expand All @@ -132,7 +145,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
}

public traceNewNoteHash(_storageAddress: Fr, noteHash: Fr) {
if (this.noteHashes.length >= MAX_NOTE_HASHES_PER_TX) {
if (this.noteHashes.length + this.previousAccumulatedDataArrayLengths.noteHashes >= MAX_NOTE_HASHES_PER_TX) {
throw new SideEffectLimitReachedError('note hash', MAX_NOTE_HASHES_PER_TX);
}
this.noteHashes.push(new NoteHash(noteHash, this.sideEffectCounter));
Expand Down Expand Up @@ -161,7 +174,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {

public traceNewNullifier(_storageAddress: Fr, nullifier: Fr) {
// NOTE: storageAddress is unused but will be important when an AVM circuit processes an entire enqueued call
if (this.nullifiers.length >= MAX_NULLIFIERS_PER_TX) {
if (this.nullifiers.length + this.previousAccumulatedDataArrayLengths.nullifiers >= MAX_NULLIFIERS_PER_TX) {
throw new SideEffectLimitReachedError('nullifier', MAX_NULLIFIERS_PER_TX);
}
this.nullifiers.push(new Nullifier(nullifier, this.sideEffectCounter, /*noteHash=*/ Fr.ZERO));
Expand All @@ -172,7 +185,10 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
// TODO(8287): _exists can be removed once we have the vm properly handling the equality check
public traceL1ToL2MessageCheck(_contractAddress: Fr, msgHash: Fr, msgLeafIndex: Fr, exists: boolean) {
// NOTE: contractAddress is unused but will be important when an AVM circuit processes an entire enqueued call
if (this.l1ToL2MsgReadRequests.length >= MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX) {
if (
this.l1ToL2MsgReadRequests.length + this.previousValidationRequestArrayLengths.l1ToL2MsgReadRequests >=
MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX
) {
throw new SideEffectLimitReachedError('l1 to l2 message read request', MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_TX);
}
this.l1ToL2MsgReadRequests.push(new TreeLeafReadRequest(msgHash, msgLeafIndex));
Expand All @@ -183,7 +199,10 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
}

public traceNewL2ToL1Message(_contractAddress: Fr, recipient: Fr, content: Fr) {
if (this.newL2ToL1Messages.length >= MAX_L2_TO_L1_MSGS_PER_TX) {
if (
this.newL2ToL1Messages.length + this.previousAccumulatedDataArrayLengths.l2ToL1Msgs >=
MAX_L2_TO_L1_MSGS_PER_TX
) {
throw new SideEffectLimitReachedError('l2 to l1 message', MAX_L2_TO_L1_MSGS_PER_TX);
}
const recipientAddress = EthAddress.fromField(recipient);
Expand All @@ -193,7 +212,10 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
}

public traceUnencryptedLog(contractAddress: Fr, log: Fr[]) {
if (this.unencryptedLogs.length >= MAX_UNENCRYPTED_LOGS_PER_TX) {
if (
this.allUnencryptedLogs.length + this.previousAccumulatedDataArrayLengths.unencryptedLogsHashes >=
MAX_UNENCRYPTED_LOGS_PER_TX
) {
throw new SideEffectLimitReachedError('unencrypted log', MAX_UNENCRYPTED_LOGS_PER_TX);
}
const ulog = new UnencryptedL2Log(
Expand Down Expand Up @@ -264,6 +286,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
);
this.sideEffectCounter = result.endSideEffectCounter.toNumber();
// when a nested call returns, caller accepts its updated counter
// TODO(dbanks12): should not accept logs if nested call reverted
this.allUnencryptedLogs.push(...result.allUnencryptedLogs.logs);
// NOTE: eventually if the AVM circuit processes an entire enqueued call,
// this function will accept all of the nested's side effects into this instance
Expand Down Expand Up @@ -348,13 +371,20 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface {
// sequencer from lying and saying "this nullifier exists, but MAX_NULLIFIER_RRS has been reached, so I'm
// going to skip the read request and just revert instead" when the nullifier actually doesn't exist
// (or vice versa). So, if either maximum has been reached, any nullifier-reading operation must error.
if (this.nullifierReadRequests.length >= MAX_NULLIFIER_READ_REQUESTS_PER_TX) {
if (
this.nullifierReadRequests.length + this.previousValidationRequestArrayLengths.nullifierReadRequests >=
MAX_NULLIFIER_READ_REQUESTS_PER_TX
) {
throw new SideEffectLimitReachedError(
`nullifier read request ${errorMsgOrigin}`,
MAX_NULLIFIER_READ_REQUESTS_PER_TX,
);
}
if (this.nullifierNonExistentReadRequests.length >= MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX) {
if (
this.nullifierNonExistentReadRequests.length +
this.previousValidationRequestArrayLengths.nullifierNonExistentReadRequests >=
MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX
) {
throw new SideEffectLimitReachedError(
`nullifier non-existent read request ${errorMsgOrigin}`,
MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_TX,
Expand Down

0 comments on commit daf6dc5

Please sign in to comment.