From 99da35f612be836b1f706fc9251493a32face49f Mon Sep 17 00:00:00 2001 From: fcarreiro Date: Wed, 17 Apr 2024 10:53:06 +0000 Subject: [PATCH] feat(avm-simulator): fix L1TOL2MESSAGEEXISTS opcode --- .../pxe/src/simulator_oracle/index.ts | 5 +++ .../simulator/src/avm/avm_simulator.test.ts | 5 +-- .../simulator/src/avm/fixtures/index.ts | 22 +---------- .../simulator/src/avm/journal/journal.test.ts | 3 +- .../simulator/src/avm/journal/journal.ts | 25 +++---------- .../src/avm/opcodes/accrued_substate.test.ts | 37 +++++++++++++++---- yarn-project/simulator/src/public/db.ts | 6 +++ .../simulator/src/public/public_executor.ts | 4 ++ 8 files changed, 54 insertions(+), 53 deletions(-) diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 56e65ee0e816..d5ba6c1a032d 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -159,6 +159,11 @@ export class SimulatorOracle implements DBOracle { return new MessageLoadOracleInputs(messageIndex, siblingPath); } + // Only used in public. + public getL1ToL2LeafValue(_leafIndex: bigint): Promise { + throw new Error('Unimplemented in private!'); + } + /** * Gets the index of a commitment in the note hash tree. * @param commitment - The commitment. diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 6eeb992c72ea..411d6c8fa84b 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -20,7 +20,6 @@ import { initContext, initExecutionEnvironment, initGlobalVariables, - initL1ToL2MessageOracleInput, initMachineState, randomMemoryBytes, randomMemoryFields, @@ -484,9 +483,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { const calldata = [msgHash, leafIndex]; const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - jest - .spyOn(context.persistableState.hostStorage.commitmentsDb, 'getL1ToL2MembershipWitness') - .mockResolvedValue(initL1ToL2MessageOracleInput(leafIndex.toBigInt())); + jest.spyOn(context.persistableState.hostStorage.commitmentsDb, 'getL1ToL2LeafValue').mockResolvedValue(msgHash); const bytecode = getAvmTestContractBytecode('l1_to_l2_msg_exists'); const results = await new AvmSimulator(context).executeBytecode(bytecode); diff --git a/yarn-project/simulator/src/avm/fixtures/index.ts b/yarn-project/simulator/src/avm/fixtures/index.ts index c03fa8181660..ef9233bb686f 100644 --- a/yarn-project/simulator/src/avm/fixtures/index.ts +++ b/yarn-project/simulator/src/avm/fixtures/index.ts @@ -1,5 +1,4 @@ -import { SiblingPath } from '@aztec/circuit-types'; -import { GasFees, GasSettings, GlobalVariables, Header, L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/circuits.js'; +import { GasFees, GasSettings, GlobalVariables, Header } from '@aztec/circuits.js'; import { FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -8,12 +7,7 @@ import { Fr } from '@aztec/foundation/fields'; import { mock } from 'jest-mock-extended'; import merge from 'lodash.merge'; -import { - type CommitmentsDB, - MessageLoadOracleInputs, - type PublicContractsDB, - type PublicStateDB, -} from '../../index.js'; +import { type CommitmentsDB, type PublicContractsDB, type PublicStateDB } from '../../index.js'; import { AvmContext } from '../avm_context.js'; import { AvmContextInputs, AvmExecutionEnvironment } from '../avm_execution_environment.js'; import { AvmMachineState } from '../avm_machine_state.js'; @@ -111,18 +105,6 @@ export function allSameExcept(original: any, overrides: any): any { return merge({}, original, overrides); } -/** - * Create an empty L1ToL2Message oracle input - */ -export function initL1ToL2MessageOracleInput( - leafIndex?: bigint, -): MessageLoadOracleInputs { - return new MessageLoadOracleInputs( - leafIndex ?? 0n, - new SiblingPath(L1_TO_L2_MSG_TREE_HEIGHT, Array(L1_TO_L2_MSG_TREE_HEIGHT)), - ); -} - /** * Adjust the user index to account for the AvmContextInputs size. * This is a hack for testing, and should go away once AvmContextInputs themselves go away. diff --git a/yarn-project/simulator/src/avm/journal/journal.test.ts b/yarn-project/simulator/src/avm/journal/journal.test.ts index 400d70669215..db6c901c6b21 100644 --- a/yarn-project/simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/simulator/src/avm/journal/journal.test.ts @@ -6,7 +6,6 @@ import { Fr } from '@aztec/foundation/fields'; import { type MockProxy, mock } from 'jest-mock-extended'; import { type CommitmentsDB, type PublicContractsDB, type PublicStateDB } from '../../index.js'; -import { initL1ToL2MessageOracleInput } from '../fixtures/index.js'; import { HostStorage } from './host_storage.js'; import { AvmPersistableStateManager, type JournalData } from './journal.js'; @@ -129,7 +128,7 @@ describe('journal', () => { const utxo = new Fr(2); const leafIndex = new Fr(42); - commitmentsDb.getL1ToL2MembershipWitness.mockResolvedValue(initL1ToL2MessageOracleInput(leafIndex.toBigInt())); + commitmentsDb.getL1ToL2LeafValue.mockResolvedValue(utxo); const exists = await journal.checkL1ToL2MessageExists(utxo, leafIndex); expect(exists).toEqual(true); diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 5972aeb391e0..d980b7b3dd6a 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -1,7 +1,7 @@ import { UnencryptedL2Log } from '@aztec/circuit-types'; import { AztecAddress, EthAddress, L2ToL1Message } from '@aztec/circuits.js'; import { EventSelector } from '@aztec/foundation/abi'; -import { Fr } from '@aztec/foundation/fields'; +import { type Fr } from '@aztec/foundation/fields'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { type HostStorage } from './host_storage.js'; @@ -171,24 +171,11 @@ export class AvmPersistableStateManager { * @returns exists - whether the message exists in the L1 to L2 Messages tree */ public async checkL1ToL2MessageExists(msgHash: Fr, msgLeafIndex: Fr): Promise { - let exists = false; - try { - // The following 2 values are used to compute a message nullifier. Given that here we do not care about getting - // non-nullified messages we can just pass in random values and the nullifier check will effectively be ignored - // (no nullifier will be found). - const ignoredContractAddress = AztecAddress.random(); - const ignoredSecret = Fr.random(); - const gotMessage = await this.hostStorage.commitmentsDb.getL1ToL2MembershipWitness( - ignoredContractAddress, - msgHash, - ignoredSecret, - ); - exists = gotMessage !== undefined && gotMessage.index == msgLeafIndex.toBigInt(); - } catch { - // error getting message - doesn't exist! - exists = false; - } - this.log.debug(`l1ToL2Messages(${msgHash})@${msgLeafIndex} ?? exists: ${exists}.`); + const valueAtIndex = await this.hostStorage.commitmentsDb.getL1ToL2LeafValue(msgLeafIndex.toBigInt()); + const exists = valueAtIndex?.equals(msgHash) ?? false; + this.log.debug( + `l1ToL2Messages(@${msgLeafIndex}) ?? exists: ${exists}, expected: ${msgHash}, found: ${valueAtIndex}.`, + ); this.trace.traceL1ToL2MessageCheck(msgHash, msgLeafIndex, exists); return Promise.resolve(exists); } diff --git a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts index 5eac2f364f49..6e34f72af9d3 100644 --- a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts @@ -8,12 +8,7 @@ import { type CommitmentsDB } from '../../index.js'; import { type AvmContext } from '../avm_context.js'; import { Field, Uint8 } from '../avm_memory_types.js'; import { InstructionExecutionError } from '../errors.js'; -import { - initContext, - initExecutionEnvironment, - initHostStorage, - initL1ToL2MessageOracleInput, -} from '../fixtures/index.js'; +import { initContext, initExecutionEnvironment, initHostStorage } from '../fixtures/index.js'; import { AvmPersistableStateManager } from '../journal/journal.js'; import { EmitNoteHash, @@ -348,7 +343,7 @@ describe('Accrued Substate', () => { // mock commitments db to show message exists const commitmentsDb = mock(); - commitmentsDb.getL1ToL2MembershipWitness.mockResolvedValue(initL1ToL2MessageOracleInput(leafIndex.toBigInt())); + commitmentsDb.getL1ToL2LeafValue.mockResolvedValue(msgHash.toFr()); const hostStorage = initHostStorage({ commitmentsDb }); context = initContext({ persistableState: new AvmPersistableStateManager(hostStorage) }); @@ -356,7 +351,6 @@ describe('Accrued Substate', () => { context.machineState.memory.set(msgLeafIndexOffset, leafIndex); await new L1ToL2MessageExists(/*indirect=*/ 0, msgHashOffset, msgLeafIndexOffset, existsOffset).execute(context); - // never created, doesn't exist! const exists = context.machineState.memory.getAs(existsOffset); expect(exists).toEqual(new Uint8(1)); @@ -365,6 +359,33 @@ describe('Accrued Substate', () => { expect.objectContaining({ leafIndex: leafIndex.toFr(), msgHash: msgHash.toFr(), exists: true }), ]); }); + + it('Should correctly show false when another L1ToL2 message exists at that leaf', async () => { + const msgHash = new Field(69n); + const leafIndex = new Field(42n); + const msgHashOffset = 0; + const msgLeafIndexOffset = 1; + const existsOffset = 2; + + // mock commitments db to show message exists + const commitmentsDb = mock(); + commitmentsDb.getL1ToL2LeafValue.mockResolvedValue(Fr.ZERO); + const hostStorage = initHostStorage({ commitmentsDb }); + context = initContext({ persistableState: new AvmPersistableStateManager(hostStorage) }); + + context.machineState.memory.set(msgHashOffset, msgHash); + context.machineState.memory.set(msgLeafIndexOffset, leafIndex); + await new L1ToL2MessageExists(/*indirect=*/ 0, msgHashOffset, msgLeafIndexOffset, existsOffset).execute(context); + + // never created, doesn't exist! + const exists = context.machineState.memory.getAs(existsOffset); + expect(exists).toEqual(new Uint8(0)); + + const journalState = context.persistableState.flush(); + expect(journalState.l1ToL2MessageChecks).toEqual([ + expect.objectContaining({ leafIndex: leafIndex.toFr(), msgHash: msgHash.toFr(), exists: false }), + ]); + }); }); describe('EmitUnencryptedLog', () => { diff --git a/yarn-project/simulator/src/public/db.ts b/yarn-project/simulator/src/public/db.ts index 88d2a66025a9..68e08315d6b7 100644 --- a/yarn-project/simulator/src/public/db.ts +++ b/yarn-project/simulator/src/public/db.ts @@ -92,6 +92,12 @@ export interface CommitmentsDB { secret: Fr, ): Promise>; + /** + * @param leafIndex the leaf to look up + * @returns The l1 to l2 leaf value or undefined if not found. + */ + getL1ToL2LeafValue(leafIndex: bigint): Promise; + /** * Gets the index of a commitment in the note hash tree. * @param commitment - The commitment. diff --git a/yarn-project/simulator/src/public/public_executor.ts b/yarn-project/simulator/src/public/public_executor.ts index 1f1bc28115ed..c40a815732b6 100644 --- a/yarn-project/simulator/src/public/public_executor.ts +++ b/yarn-project/simulator/src/public/public_executor.ts @@ -257,6 +257,10 @@ export class WorldStateDB implements CommitmentsDB { return new MessageLoadOracleInputs(messageIndex, siblingPath); } + public async getL1ToL2LeafValue(leafIndex: bigint): Promise { + return await this.db.getLeafValue(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, leafIndex); + } + public async getCommitmentIndex(commitment: Fr): Promise { return await this.db.findLeafIndex(MerkleTreeId.NOTE_HASH_TREE, commitment); }