From 244d2229fa23e8d1a0c65cfdcf5c27c93b457219 Mon Sep 17 00:00:00 2001 From: dbanks12 Date: Wed, 20 Nov 2024 20:38:13 +0000 Subject: [PATCH] feat: properly pipe AvmCircuitPublicInputs to witgen --- .../bb-prover/src/avm_proving.test.ts | 140 +--------------- .../structs/public_circuit_public_inputs.ts | 64 +++++++ .../src/avm_integration.test.ts | 139 ++------------- yarn-project/simulator/package.json | 2 +- .../simulator/src/avm/journal/journal.ts | 4 + yarn-project/simulator/src/index.ts | 1 - yarn-project/simulator/src/mocks/fixtures.ts | 72 -------- yarn-project/simulator/src/mocks/index.ts | 1 - .../public/enqueued_call_side_effect_trace.ts | 2 +- .../simulator/src/public/fixtures/index.ts | 158 ++++++++++++++++++ .../simulator/src/public/public_tx_context.ts | 4 + .../src/public/public_tx_simulator.ts | 9 +- .../simulator/src/public/side_effect_trace.ts | 2 +- yarn-project/txe/src/oracle/txe_oracle.ts | 56 +------ 14 files changed, 267 insertions(+), 387 deletions(-) delete mode 100644 yarn-project/simulator/src/mocks/fixtures.ts delete mode 100644 yarn-project/simulator/src/mocks/index.ts create mode 100644 yarn-project/simulator/src/public/fixtures/index.ts diff --git a/yarn-project/bb-prover/src/avm_proving.test.ts b/yarn-project/bb-prover/src/avm_proving.test.ts index 6923c705762..b5580a4d97f 100644 --- a/yarn-project/bb-prover/src/avm_proving.test.ts +++ b/yarn-project/bb-prover/src/avm_proving.test.ts @@ -1,41 +1,16 @@ -import { - AvmCircuitInputs, - AvmCircuitPublicInputs, - Gas, - GlobalVariables, - type PublicFunction, - PublicKeys, - SerializableContractInstance, - VerificationKeyData, -} from '@aztec/circuits.js'; -import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; -import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { Fr, Point } from '@aztec/foundation/fields'; +import { VerificationKeyData } from '@aztec/circuits.js'; +import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { openTmpStore } from '@aztec/kv-store/utils'; -import { AvmSimulator, PublicSideEffectTrace, type WorldStateDB } from '@aztec/simulator'; -import { - getAvmTestContractBytecode, - getAvmTestContractFunctionSelector, - initContext, - initExecutionEnvironment, - initPersistableStateManager, - resolveAvmTestContractAssertionMessage, -} from '@aztec/simulator/avm/fixtures'; -import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; -import { MerkleTrees } from '@aztec/world-state'; +import { simulateAvmTestContractGenerateCircuitInputs } from '@aztec/simulator/public/fixtures'; -import { mock } from 'jest-mock-extended'; import fs from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'path'; import { type BBSuccess, BB_RESULT, generateAvmProof, verifyAvmProof } from './bb/execute.js'; -import { getPublicInputs } from './test/test_avm.js'; import { extractAvmVkData } from './verification_key/verification_key_data.js'; const TIMEOUT = 180_000; -const TIMESTAMP = new Fr(99833); describe('AVM WitGen, proof generation and verification', () => { it( @@ -50,77 +25,8 @@ describe('AVM WitGen, proof generation and verification', () => { ); }); -/************************************************************************ - * Helpers - ************************************************************************/ - -/** - * If assertionErrString is set, we expect a (non exceptional halting) revert due to a failing assertion and - * we check that the revert reason error contains this string. However, the circuit must correctly prove the - * execution. - */ -const proveAndVerifyAvmTestContract = async ( - functionName: string, - calldata: Fr[] = [], - assertionErrString?: string, -) => { - const startSideEffectCounter = 0; - const functionSelector = getAvmTestContractFunctionSelector(functionName); - calldata = [functionSelector.toField(), ...calldata]; - const globals = GlobalVariables.empty(); - globals.timestamp = TIMESTAMP; - - const worldStateDB = mock(); - // - // Top level contract call - const bytecode = getAvmTestContractBytecode('public_dispatch'); - const fnSelector = getAvmTestContractFunctionSelector('public_dispatch'); - const publicFn: PublicFunction = { bytecode, selector: fnSelector }; - const contractClass = makeContractClassPublic(0, publicFn); - const contractInstance = makeContractInstanceFromClassId(contractClass.id); - - // The values here should match those in `avm_simulator.test.ts` - const instanceGet = new SerializableContractInstance({ - version: 1, - salt: new Fr(0x123), - deployer: new AztecAddress(new Fr(0x456)), - contractClassId: new Fr(0x789), - initializationHash: new Fr(0x101112), - publicKeys: new PublicKeys( - new Point(new Fr(0x131415), new Fr(0x161718), false), - new Point(new Fr(0x192021), new Fr(0x222324), false), - new Point(new Fr(0x252627), new Fr(0x282930), false), - new Point(new Fr(0x313233), new Fr(0x343536), false), - ), - }).withAddress(contractInstance.address); - - worldStateDB.getContractInstance - .mockResolvedValueOnce(contractInstance) - .mockResolvedValueOnce(instanceGet) // test gets deployer - .mockResolvedValueOnce(instanceGet) // test gets class id - .mockResolvedValueOnce(instanceGet) // test gets init hash - .mockResolvedValue(contractInstance); - worldStateDB.getContractClass.mockResolvedValue(contractClass); - - const storageValue = new Fr(5); - worldStateDB.storageRead.mockResolvedValue(Promise.resolve(storageValue)); - - const trace = new PublicSideEffectTrace(startSideEffectCounter); - const telemetry = new NoopTelemetryClient(); - const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork(); - worldStateDB.getMerkleInterface.mockReturnValue(merkleTrees); - const persistableState = initPersistableStateManager({ worldStateDB, trace, merkleTrees, doMerkleOperations: true }); - const environment = initExecutionEnvironment({ - functionSelector, - calldata, - globals, - address: contractInstance.address, - }); - const context = initContext({ env: environment, persistableState }); - - worldStateDB.getBytecode.mockResolvedValue(bytecode); - - const startGas = new Gas(context.machineState.gasLeft.daGas, context.machineState.gasLeft.l2Gas); +async function proveAndVerifyAvmTestContract(functionName: string, calldata: Fr[] = []) { + const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs(functionName, calldata); const internalLogger = createDebugLogger('aztec:avm-proving-test'); const logger = (msg: string, _data?: any) => internalLogger.verbose(msg); @@ -129,39 +35,11 @@ const proveAndVerifyAvmTestContract = async ( const bbPath = path.resolve('../../barretenberg/cpp/build/bin/bb'); const bbWorkingDirectory = await fs.mkdtemp(path.join(tmpdir(), 'bb-')); - // First we simulate (though it's not needed in this simple case). - const simulator = new AvmSimulator(context); - const avmResult = await simulator.execute(); - - if (assertionErrString == undefined) { - expect(avmResult.reverted).toBe(false); - } else { - // Explicit revert when an assertion failed. - expect(avmResult.reverted).toBe(true); - expect(avmResult.revertReason).toBeDefined(); - expect(resolveAvmTestContractAssertionMessage(functionName, avmResult.revertReason!, avmResult.output)).toContain( - assertionErrString, - ); - } - - const pxResult = trace.toPublicFunctionCallResult( - environment, - startGas, - /*bytecode=*/ simulator.getBytecode()!, - avmResult.finalize(), - functionName, - ); - - const avmCircuitInputs = new AvmCircuitInputs( - functionName, - /*calldata=*/ context.environment.calldata, - /*publicInputs=*/ getPublicInputs(pxResult), - /*avmHints=*/ pxResult.avmCircuitHints, - /*output*/ AvmCircuitPublicInputs.empty(), - ); - // Then we prove. const proofRes = await generateAvmProof(bbPath, bbWorkingDirectory, avmCircuitInputs, logger); + if (proofRes.status === BB_RESULT.FAILURE) { + internalLogger.error(`Proof generation failed: ${proofRes.reason}`); + } expect(proofRes.status).toEqual(BB_RESULT.SUCCESS); // Then we test VK extraction and serialization. @@ -173,4 +51,4 @@ const proveAndVerifyAvmTestContract = async ( const rawVkPath = path.join(succeededRes.vkPath!, 'vk'); const verificationRes = await verifyAvmProof(bbPath, succeededRes.proofPath!, rawVkPath, logger); expect(verificationRes.status).toBe(BB_RESULT.SUCCESS); -}; +} diff --git a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts index bb411de60c8..46437e0da40 100644 --- a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts @@ -10,6 +10,8 @@ import { } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; +import { inspect } from 'util'; + import { MAX_ENQUEUED_CALLS_PER_CALL, MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_CALL, @@ -324,4 +326,66 @@ export class PublicCircuitPublicInputs { reader.readField(), ); } + + [inspect.custom]() { + return `PublicCircuitPublicInputs { + callContext: ${inspect(this.callContext)}, + argsHash: ${inspect(this.argsHash)}, + returnsHash: ${inspect(this.returnsHash)}, + noteHashReadRequests: [${this.noteHashReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + nullifierReadRequests: [${this.nullifierReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + nullifierNonExistentReadRequests: [${this.nullifierNonExistentReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + l1ToL2MsgReadRequests: [${this.l1ToL2MsgReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + contractStorageUpdateRequests: [${this.contractStorageUpdateRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + contractStorageReads: [${this.contractStorageReads + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + publicCallRequests: [${this.publicCallRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + noteHashes: [${this.noteHashes + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + nullifiers: [${this.nullifiers + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + l2ToL1Msgs: [${this.l2ToL1Msgs + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + startSideEffectCounter: ${inspect(this.startSideEffectCounter)}, + endSideEffectCounter: ${inspect(this.endSideEffectCounter)}, + startSideEffectCounter: ${inspect(this.startSideEffectCounter)}, + unencryptedLogsHashes: [${this.unencryptedLogsHashes + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + historicalHeader: ${inspect(this.historicalHeader)}, + globalVariables: ${inspect(this.globalVariables)}, + proverAddress: ${inspect(this.proverAddress)}, + revertCode: ${inspect(this.revertCode)}, + startGasLeft: ${inspect(this.startGasLeft)}, + endGasLeft: ${inspect(this.endGasLeft)}, + transactionFee: ${inspect(this.transactionFee)}, + }`; + } } diff --git a/yarn-project/ivc-integration/src/avm_integration.test.ts b/yarn-project/ivc-integration/src/avm_integration.test.ts index 4bbb4e19633..63ed8fbaa5b 100644 --- a/yarn-project/ivc-integration/src/avm_integration.test.ts +++ b/yarn-project/ivc-integration/src/avm_integration.test.ts @@ -1,21 +1,4 @@ -import { - type BBSuccess, - BB_RESULT, - generateAvmProof, - generateProof, - getPublicInputs, - verifyProof, -} from '@aztec/bb-prover'; -import { - AvmCircuitInputs, - AvmCircuitPublicInputs, - AztecAddress, - Gas, - GlobalVariables, - type PublicFunction, - PublicKeys, - SerializableContractInstance, -} from '@aztec/circuits.js'; +import { type BBSuccess, BB_RESULT, generateAvmProof, generateProof, verifyProof } from '@aztec/bb-prover'; import { AVM_PROOF_LENGTH_IN_FIELDS, AVM_PUBLIC_COLUMN_MAX_SIZE, @@ -23,27 +6,14 @@ import { AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS, PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH, } from '@aztec/circuits.js/constants'; -import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; -import { Fr, Point } from '@aztec/foundation/fields'; +import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { BufferReader } from '@aztec/foundation/serialize'; -import { openTmpStore } from '@aztec/kv-store/utils'; import { type FixedLengthArray } from '@aztec/noir-protocol-circuits-types/types'; -import { AvmSimulator, PublicSideEffectTrace, type WorldStateDB } from '@aztec/simulator'; -import { - getAvmTestContractBytecode, - getAvmTestContractFunctionSelector, - initContext, - initExecutionEnvironment, - initPersistableStateManager, - resolveAvmTestContractAssertionMessage, -} from '@aztec/simulator/avm/fixtures'; -import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; -import { MerkleTrees } from '@aztec/world-state'; +import { simulateAvmTestContractGenerateCircuitInputs } from '@aztec/simulator/public/fixtures'; import { jest } from '@jest/globals'; import fs from 'fs/promises'; -import { mock } from 'jest-mock-extended'; import { tmpdir } from 'node:os'; import os from 'os'; import path from 'path'; @@ -153,107 +123,22 @@ describe('AVM Integration', () => { }); }); -// Helper - -const proveAvmTestContract = async ( - functionName: string, - calldata: Fr[] = [], - assertionErrString?: string, -): Promise => { - const worldStateDB = mock(); - const startSideEffectCounter = 0; - const functionSelector = getAvmTestContractFunctionSelector(functionName); - calldata = [functionSelector.toField(), ...calldata]; - const globals = GlobalVariables.empty(); - - // Top level contract call - const bytecode = getAvmTestContractBytecode('public_dispatch'); - const fnSelector = getAvmTestContractFunctionSelector('public_dispatch'); - const publicFn: PublicFunction = { bytecode, selector: fnSelector }; - const contractClass = makeContractClassPublic(0, publicFn); - const contractInstance = makeContractInstanceFromClassId(contractClass.id); - - const instanceGet = new SerializableContractInstance({ - version: 1, - salt: new Fr(0x123), - deployer: AztecAddress.fromNumber(0x456), - contractClassId: new Fr(0x789), - initializationHash: new Fr(0x101112), - publicKeys: new PublicKeys( - new Point(new Fr(0x131415), new Fr(0x161718), false), - new Point(new Fr(0x192021), new Fr(0x222324), false), - new Point(new Fr(0x252627), new Fr(0x282930), false), - new Point(new Fr(0x313233), new Fr(0x343536), false), - ), - }).withAddress(contractInstance.address); - - worldStateDB.getContractInstance - .mockResolvedValueOnce(contractInstance) - .mockResolvedValueOnce(instanceGet) // test gets deployer - .mockResolvedValueOnce(instanceGet) // test gets class id - .mockResolvedValueOnce(instanceGet) // test gets init hash - .mockResolvedValue(contractInstance); - - worldStateDB.getContractClass.mockResolvedValue(contractClass); - - const storageValue = new Fr(5); - worldStateDB.storageRead.mockResolvedValue(storageValue); +async function proveAvmTestContract(functionName: string, calldata: Fr[] = []): Promise { + const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs(functionName, calldata); - const trace = new PublicSideEffectTrace(startSideEffectCounter); - const telemetry = new NoopTelemetryClient(); - const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork(); - worldStateDB.getMerkleInterface.mockReturnValue(merkleTrees); - const persistableState = initPersistableStateManager({ worldStateDB, trace, merkleTrees, doMerkleOperations: true }); - const environment = initExecutionEnvironment({ - functionSelector, - calldata, - globals, - address: contractInstance.address, - }); - const context = initContext({ env: environment, persistableState }); - - worldStateDB.getBytecode.mockResolvedValue(bytecode); - - const startGas = new Gas(context.machineState.gasLeft.daGas, context.machineState.gasLeft.l2Gas); + const internalLogger = createDebugLogger('aztec:avm-proving-test'); + const logger = (msg: string, _data?: any) => internalLogger.verbose(msg); - // Use a simple contract that emits a side effect // The paths for the barretenberg binary and the write path are hardcoded for now. const bbPath = path.resolve('../../barretenberg/cpp/build/bin/bb'); const bbWorkingDirectory = await fs.mkdtemp(path.join(tmpdir(), 'bb-')); - // First we simulate (though it's not needed in this simple case). - const simulator = new AvmSimulator(context); - const avmResult = await simulator.execute(); - - if (assertionErrString == undefined) { - expect(avmResult.reverted).toBe(false); - } else { - // Explicit revert when an assertion failed. - expect(avmResult.reverted).toBe(true); - expect(avmResult.revertReason).toBeDefined(); - expect(resolveAvmTestContractAssertionMessage(functionName, avmResult.revertReason!, avmResult.output)).toContain( - assertionErrString, - ); - } - - const pxResult = trace.toPublicFunctionCallResult( - environment, - startGas, - /*bytecode=*/ simulator.getBytecode()!, - avmResult.finalize(), - functionName, - ); - - const avmCircuitInputs = new AvmCircuitInputs( - functionName, - /*calldata=*/ context.environment.calldata, - /*publicInputs=*/ getPublicInputs(pxResult), - /*avmHints=*/ pxResult.avmCircuitHints, - AvmCircuitPublicInputs.empty(), - ); // Then we prove. - const proofRes = await generateAvmProof(bbPath, bbWorkingDirectory, avmCircuitInputs, logger.info); + const proofRes = await generateAvmProof(bbPath, bbWorkingDirectory, avmCircuitInputs, logger); + if (proofRes.status === BB_RESULT.FAILURE) { + internalLogger.error(`Proof generation failed: ${proofRes.reason}`); + } expect(proofRes.status).toEqual(BB_RESULT.SUCCESS); return proofRes as BBSuccess; -}; +} diff --git a/yarn-project/simulator/package.json b/yarn-project/simulator/package.json index 31b561d6d6f..2832153c30a 100644 --- a/yarn-project/simulator/package.json +++ b/yarn-project/simulator/package.json @@ -4,7 +4,7 @@ "type": "module", "exports": { ".": "./dest/index.js", - "./avm/fixtures": "./dest/avm/fixtures/index.js" + "./public/fixtures": "./dest/public/fixtures/index.js" }, "typedocOptions": { "entryPoints": [ diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 637ec02d7fe..dd62d155e73 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -69,6 +69,8 @@ export class AvmPersistableStateManager { worldStateDB: WorldStateDB, trace: PublicSideEffectTraceInterface, pendingSiloedNullifiers: Fr[], + doMerkleOperations: boolean = false, + merkleTrees?: MerkleTreeWriteOperations, ) { const parentNullifiers = NullifierManager.newWithPendingSiloedNullifiers(worldStateDB, pendingSiloedNullifiers); return new AvmPersistableStateManager( @@ -76,6 +78,8 @@ export class AvmPersistableStateManager { trace, /*publicStorage=*/ new PublicStorage(worldStateDB), /*nullifiers=*/ parentNullifiers.fork(), + doMerkleOperations, + merkleTrees, ); } diff --git a/yarn-project/simulator/src/index.ts b/yarn-project/simulator/src/index.ts index fc5a4653413..f8095f6baf7 100644 --- a/yarn-project/simulator/src/index.ts +++ b/yarn-project/simulator/src/index.ts @@ -4,5 +4,4 @@ export * from './client/index.js'; export * from './common/index.js'; export * from './public/index.js'; export * from './providers/index.js'; -export * from './mocks/index.js'; export * from './stats/index.js'; diff --git a/yarn-project/simulator/src/mocks/fixtures.ts b/yarn-project/simulator/src/mocks/fixtures.ts deleted file mode 100644 index 9c5bd74e06d..00000000000 --- a/yarn-project/simulator/src/mocks/fixtures.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { SimulationError } from '@aztec/circuit-types'; -import { ARGS_LENGTH, Fr, Gas } from '@aztec/circuits.js'; -import { makeAztecAddress, makeSelector } from '@aztec/circuits.js/testing'; -import { FunctionType } from '@aztec/foundation/abi'; -import { padArrayEnd } from '@aztec/foundation/collection'; - -import { type EnqueuedPublicCallExecutionResult } from '../public/execution.js'; - -export class PublicExecutionResultBuilder { - private _returnValues: Fr[] = []; - private _reverted = false; - private _revertReason: SimulationError | undefined = undefined; - - constructor() {} - - static empty(basicRevert = false) { - const builder = new PublicExecutionResultBuilder(); - if (basicRevert) { - builder.withReverted(new SimulationError('Simulation failed', [])); - } - return builder; - } - - static fromPublicExecutionRequest({ - returnValues = [new Fr(1n)], - revertReason = undefined, - }: { - returnValues?: Fr[]; - revertReason?: SimulationError; - }): PublicExecutionResultBuilder { - const builder = new PublicExecutionResultBuilder(); - - builder.withReturnValues(...returnValues); - if (revertReason) { - builder.withReverted(revertReason); - } - - return builder; - } - - withReturnValues(...values: Fr[]): PublicExecutionResultBuilder { - this._returnValues.push(...values); - return this; - } - - withReverted(reason: SimulationError): PublicExecutionResultBuilder { - this._reverted = true; - this._revertReason = reason; - return this; - } - - build(overrides: Partial = {}): EnqueuedPublicCallExecutionResult { - return { - endGasLeft: Gas.empty(), - endSideEffectCounter: Fr.ZERO, - returnValues: padArrayEnd(this._returnValues, Fr.ZERO, 4), // TODO(#5450) Need to use the proper return values here - reverted: this._reverted, - revertReason: this._revertReason, - ...overrides, - }; - } -} - -export const makeFunctionCall = ( - name = 'function', - to = makeAztecAddress(30), - selector = makeSelector(5), - type = FunctionType.PUBLIC, - args = new Array(ARGS_LENGTH).fill(Fr.ZERO), - isStatic = false, - returnTypes = [], -) => ({ name, to, selector, type, args, isStatic, returnTypes }); diff --git a/yarn-project/simulator/src/mocks/index.ts b/yarn-project/simulator/src/mocks/index.ts deleted file mode 100644 index dd1a464237b..00000000000 --- a/yarn-project/simulator/src/mocks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './fixtures.js'; 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 6cc11d08fd6..768243bf473 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 @@ -320,7 +320,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI public traceNewNoteHash( contractAddress: AztecAddress, noteHash: Fr, - leafIndex: Fr, + leafIndex: Fr = Fr.zero(), path: Fr[] = emptyNoteHashPath(), ) { if (this.noteHashes.length + this.previousSideEffectArrayLengths.noteHashes >= MAX_NOTE_HASHES_PER_TX) { diff --git a/yarn-project/simulator/src/public/fixtures/index.ts b/yarn-project/simulator/src/public/fixtures/index.ts new file mode 100644 index 00000000000..8c0f53fab32 --- /dev/null +++ b/yarn-project/simulator/src/public/fixtures/index.ts @@ -0,0 +1,158 @@ +import { PublicExecutionRequest, Tx } from '@aztec/circuit-types'; +import { + type AvmCircuitInputs, + CallContext, + DEFAULT_GAS_LIMIT, + Gas, + GasFees, + GasSettings, + GlobalVariables, + Header, + MAX_L2_GAS_PER_ENQUEUED_CALL, + PartialPrivateTailPublicInputsForPublic, + PrivateKernelTailCircuitPublicInputs, + type PublicFunction, + PublicKeys, + RollupValidationRequests, + SerializableContractInstance, + TxConstantData, + TxContext, +} from '@aztec/circuits.js'; +import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { Fr, Point } from '@aztec/foundation/fields'; +import { openTmpStore } from '@aztec/kv-store/utils'; +import { PublicTxSimulator, type WorldStateDB } from '@aztec/simulator'; +import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; +import { MerkleTrees } from '@aztec/world-state'; + +import { mock } from 'jest-mock-extended'; + +import { getAvmTestContractBytecode, getAvmTestContractFunctionSelector } from '../../avm/fixtures/index.js'; + +const TIMESTAMP = new Fr(99833); + +/** + * If assertionErrString is set, we expect a (non exceptional halting) revert due to a failing assertion and + * we check that the revert reason error contains this string. However, the circuit must correctly prove the + * execution. + */ +export async function simulateAvmTestContractGenerateCircuitInputs( + functionName: string, + calldata: Fr[] = [], + assertionErrString?: string, +): Promise { + const sender = AztecAddress.random(); + const functionSelector = getAvmTestContractFunctionSelector(functionName); + calldata = [functionSelector.toField(), ...calldata]; + + const globalVariables = GlobalVariables.empty(); + globalVariables.gasFees = GasFees.default(); + globalVariables.timestamp = TIMESTAMP; + + const worldStateDB = mock(); + const telemetry = new NoopTelemetryClient(); + const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork(); + worldStateDB.getMerkleInterface.mockReturnValue(merkleTrees); + + // Top level contract call + const bytecode = getAvmTestContractBytecode('public_dispatch'); + const dispatchSelector = getAvmTestContractFunctionSelector('public_dispatch'); + const publicFn: PublicFunction = { bytecode, selector: dispatchSelector }; + const contractClass = makeContractClassPublic(0, publicFn); + const contractInstance = makeContractInstanceFromClassId(contractClass.id); + + // The values here should match those in `avm_simulator.test.ts` + const instanceGet = new SerializableContractInstance({ + version: 1, + salt: new Fr(0x123), + deployer: new AztecAddress(new Fr(0x456)), + contractClassId: new Fr(0x789), + initializationHash: new Fr(0x101112), + publicKeys: new PublicKeys( + new Point(new Fr(0x131415), new Fr(0x161718), false), + new Point(new Fr(0x192021), new Fr(0x222324), false), + new Point(new Fr(0x252627), new Fr(0x282930), false), + new Point(new Fr(0x313233), new Fr(0x343536), false), + ), + }).withAddress(contractInstance.address); + worldStateDB.getContractInstance + .mockResolvedValueOnce(contractInstance) + .mockResolvedValueOnce(instanceGet) // test gets deployer + .mockResolvedValueOnce(instanceGet) // test gets class id + .mockResolvedValueOnce(instanceGet) // test gets init hash + .mockResolvedValue(contractInstance); + worldStateDB.getContractClass.mockResolvedValue(contractClass); + worldStateDB.getBytecode.mockResolvedValue(bytecode); + + const storageValue = new Fr(5); + worldStateDB.storageRead.mockResolvedValue(Promise.resolve(storageValue)); + + const simulator = new PublicTxSimulator( + merkleTrees, + worldStateDB, + new NoopTelemetryClient(), + globalVariables, + /*doMerkleOperations=*/ true, + ); + + const callContext = new CallContext(sender, contractInstance.address, dispatchSelector, /*isStaticCall=*/ false); + const executionRequest = new PublicExecutionRequest(callContext, calldata); + + const tx: Tx = createTxForPublicCall(executionRequest); + + const avmResult = await simulator.simulate(tx); + + if (assertionErrString == undefined) { + expect(avmResult.revertCode.isOK()).toBe(true); + } else { + // Explicit revert when an assertion failed. + expect(avmResult.revertCode.isOK()).toBe(false); + expect(avmResult.revertReason).toBeDefined(); + expect(avmResult.revertReason?.getMessage()).toContain(assertionErrString); + } + + const avmCircuitInputs: AvmCircuitInputs = avmResult.avmProvingRequest.inputs; + return avmCircuitInputs; +} + +/** + * Craft a carrier transaction for a public call for simulation by PublicTxSimulator. + */ +export function createTxForPublicCall( + executionRequest: PublicExecutionRequest, + gasUsedByPrivate: Gas = Gas.empty(), + isTeardown: boolean = false, +): Tx { + const callRequest = executionRequest.toCallRequest(); + // use max limits + const gasLimits = new Gas(DEFAULT_GAS_LIMIT, MAX_L2_GAS_PER_ENQUEUED_CALL); + + const forPublic = PartialPrivateTailPublicInputsForPublic.empty(); + // TODO(#9269): Remove this fake nullifier method as we move away from 1st nullifier as hash. + forPublic.nonRevertibleAccumulatedData.nullifiers[0] = Fr.random(); // fake tx nullifier + if (isTeardown) { + forPublic.publicTeardownCallRequest = callRequest; + } else { + forPublic.revertibleAccumulatedData.publicCallRequests[0] = callRequest; + } + + const teardownGasLimits = isTeardown ? gasLimits : Gas.empty(); + const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty()); + const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings); + const constantData = new TxConstantData(Header.empty(), txContext, Fr.zero(), Fr.zero()); + + const txData = new PrivateKernelTailCircuitPublicInputs( + constantData, + RollupValidationRequests.empty(), + /*gasUsed=*/ gasUsedByPrivate, + AztecAddress.zero(), + forPublic, + ); + const tx = isTeardown ? Tx.newWithTxData(txData, executionRequest) : Tx.newWithTxData(txData); + if (!isTeardown) { + tx.enqueuedPublicFunctionCalls[0] = executionRequest; + } + + return tx; +} diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index 4cb1390e2dc..65c287fc769 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -70,6 +70,7 @@ export class PublicTxContext { private readonly nonRevertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, private readonly revertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, public trace: PublicEnqueuedCallSideEffectTrace, // FIXME(dbanks12): should be private + private doMerkleOperations: boolean, ) { this.log = createDebugLogger(`aztec:public_tx_context`); this.gasUsed = startGasUsed; @@ -80,6 +81,7 @@ export class PublicTxContext { worldStateDB: WorldStateDB, tx: Tx, globalVariables: GlobalVariables, + doMerkleOperations: boolean, ) { const nonRevertibleAccumulatedDataFromPrivate = tx.data.forPublic!.nonRevertibleAccumulatedData; const revertibleAccumulatedDataFromPrivate = tx.data.forPublic!.revertibleAccumulatedData; @@ -113,6 +115,7 @@ export class PublicTxContext { worldStateDB, trace, nonRevertibleNullifiersFromPrivate, + doMerkleOperations, ); return new PublicTxContext( @@ -131,6 +134,7 @@ export class PublicTxContext { tx.data.forPublic!.nonRevertibleAccumulatedData, tx.data.forPublic!.revertibleAccumulatedData, enqueuedCallTrace, + doMerkleOperations, ); } diff --git a/yarn-project/simulator/src/public/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator.ts index a4d31296500..44801eff13f 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.ts @@ -58,6 +58,7 @@ export class PublicTxSimulator { client: TelemetryClient, private globalVariables: GlobalVariables, private realAvmProvingRequests: boolean = true, + private doMerkleOperations: boolean = false, ) { this.log = createDebugLogger(`aztec:public_tx_simulator`); this.metrics = new ExecutorMetrics(client, 'PublicTxSimulator'); @@ -71,7 +72,13 @@ export class PublicTxSimulator { async simulate(tx: Tx): Promise { this.log.verbose(`Processing tx ${tx.getTxHash()}`); - const context = await PublicTxContext.create(this.db, this.worldStateDB, tx, this.globalVariables); + const context = await PublicTxContext.create( + this.db, + this.worldStateDB, + tx, + this.globalVariables, + this.doMerkleOperations, + ); // add new contracts to the contracts db so that their functions may be found and called // TODO(#4073): This is catching only private deployments, when we add public ones, we'll diff --git a/yarn-project/simulator/src/public/side_effect_trace.ts b/yarn-project/simulator/src/public/side_effect_trace.ts index 2ec074470e7..ac1f4a98f16 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.ts @@ -199,7 +199,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { public traceNewNoteHash( _contractAddress: AztecAddress, noteHash: Fr, - leafIndex: Fr, + leafIndex: Fr = Fr.zero(), path: Fr[] = emptyNoteHashPath(), ) { if (this.noteHashes.length >= MAX_NOTE_HASHES_PER_TX) { diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 65811c2d6db..eec5749bf8e 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -8,7 +8,6 @@ import { PublicDataWitness, PublicExecutionRequest, SimulationError, - Tx, type UnencryptedL2Log, } from '@aztec/circuit-types'; import { type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; @@ -16,31 +15,23 @@ import { CallContext, type ContractInstance, type ContractInstanceWithAddress, - DEFAULT_GAS_LIMIT, Gas, GasFees, - GasSettings, GlobalVariables, Header, IndexedTaggingSecret, type KeyValidationRequest, type L1_TO_L2_MSG_TREE_HEIGHT, - MAX_L2_GAS_PER_ENQUEUED_CALL, NULLIFIER_SUBTREE_HEIGHT, type NULLIFIER_TREE_HEIGHT, type NullifierLeafPreimage, PRIVATE_CONTEXT_INPUTS_LENGTH, type PUBLIC_DATA_TREE_HEIGHT, PUBLIC_DISPATCH_SELECTOR, - PartialPrivateTailPublicInputsForPublic, PrivateContextInputs, - PrivateKernelTailCircuitPublicInputs, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, type PublicDataWrite, - RollupValidationRequests, - TxConstantData, - TxContext, computeContractClassId, computeTaggingSecret, deriveKeys, @@ -81,6 +72,7 @@ import { toACVMWitness, witnessMapToFields, } from '@aztec/simulator'; +import { createTxForPublicCall } from '@aztec/simulator/public/fixtures'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; @@ -663,7 +655,6 @@ export class TXE implements TypedOracle { const globalVariables = GlobalVariables.empty(); globalVariables.chainId = this.chainId; - globalVariables.chainId = this.chainId; globalVariables.version = this.version; globalVariables.blockNumber = new Fr(this.blockNumber); globalVariables.gasFees = GasFees.default(); @@ -676,7 +667,10 @@ export class TXE implements TypedOracle { /*realAvmProvingRequests=*/ false, ); - const tx = this.createTxForPublicCall(executionRequest, isTeardown); + // When setting up a teardown call, we tell it that + // private execution used Gas(1, 1) so it can compute a tx fee. + const gasUsedByPrivate = isTeardown ? new Gas(1, 1) : Gas.empty(); + const tx = createTxForPublicCall(executionRequest, gasUsedByPrivate, isTeardown); const result = await simulator.simulate(tx); return Promise.resolve(result); @@ -886,44 +880,4 @@ export class TXE implements TypedOracle { return preimage.value; } - - /** - * Craft a carrier transaction for a public call. - */ - private createTxForPublicCall(executionRequest: PublicExecutionRequest, teardown: boolean): Tx { - const callRequest = executionRequest.toCallRequest(); - // use max limits - const gasLimits = new Gas(DEFAULT_GAS_LIMIT, MAX_L2_GAS_PER_ENQUEUED_CALL); - - const forPublic = PartialPrivateTailPublicInputsForPublic.empty(); - // TODO(#9269): Remove this fake nullifier method as we move away from 1st nullifier as hash. - forPublic.nonRevertibleAccumulatedData.nullifiers[0] = Fr.random(); // fake tx nullifier - if (teardown) { - forPublic.publicTeardownCallRequest = callRequest; - } else { - forPublic.revertibleAccumulatedData.publicCallRequests[0] = callRequest; - } - - // When setting up a teardown call, we tell it that - // private execution "used" Gas(1, 1) so it can compute a tx fee. - const gasUsedByPrivate = teardown ? new Gas(1, 1) : Gas.empty(); - const teardownGasLimits = teardown ? gasLimits : Gas.empty(); - const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty()); - const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings); - const constantData = new TxConstantData(Header.empty(), txContext, Fr.zero(), Fr.zero()); - - const txData = new PrivateKernelTailCircuitPublicInputs( - constantData, - RollupValidationRequests.empty(), - /*gasUsed=*/ gasUsedByPrivate, - AztecAddress.zero(), - forPublic, - ); - const tx = teardown ? Tx.newWithTxData(txData, executionRequest) : Tx.newWithTxData(txData); - if (!teardown) { - tx.enqueuedPublicFunctionCalls[0] = executionRequest; - } - - return tx; - } }