diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts index 17a165e6babe..0d18d9ed747c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts @@ -70,25 +70,19 @@ export interface IUtilityExecutionOracle { getNoteHashMembershipWitness( anchorBlockHash: BlockHash, noteHash: Fr, - ): Promise | undefined>; + ): Promise>; getBlockHashMembershipWitness( anchorBlockHash: BlockHash, blockHash: BlockHash, ): Promise | undefined>; - getNullifierMembershipWitness( - anchorBlockHash: BlockHash, - nullifier: Fr, - ): Promise; - getPublicDataWitness(anchorBlockHash: BlockHash, leafSlot: Fr): Promise; - getLowNullifierMembershipWitness( - anchorBlockHash: BlockHash, - nullifier: Fr, - ): Promise; - getBlockHeader(blockNumber: BlockNumber): Promise; + getNullifierMembershipWitness(anchorBlockHash: BlockHash, nullifier: Fr): Promise; + getPublicDataWitness(anchorBlockHash: BlockHash, leafSlot: Fr): Promise; + getLowNullifierMembershipWitness(anchorBlockHash: BlockHash, nullifier: Fr): Promise; + getBlockHeader(blockNumber: BlockNumber): Promise; getPublicKeysAndPartialAddress( account: AztecAddress, ): Promise<{ publicKeys: PublicKeys; partialAddress: PartialAddress } | undefined>; - getAuthWitness(messageHash: Fr): Promise; + getAuthWitness(messageHash: Fr): Promise; getNotes( owner: AztecAddress | undefined, storageSlot: Fr, @@ -139,7 +133,7 @@ export interface IUtilityExecutionOracle { numEntries: number, scope: AztecAddress, ): Promise; - decryptAes128(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise; + decryptAes128(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise; getSharedSecrets(address: AztecAddress, ephPksSlot: Fr, contractAddress: AztecAddress): Promise; setContractSyncCacheInvalid(contractAddress: AztecAddress, scopes: AztecAddress[]): void; emitOffchainEffect(data: Fr[]): Promise; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts index 670d2bdc86b1..0a3fffe870bf 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts @@ -238,11 +238,6 @@ export class Oracle { const parsedNoteHash = Fr.fromString(noteHash); const witness = await this.handlerAsUtility().getNoteHashMembershipWitness(parsedAnchorBlockHash, parsedNoteHash); - if (!witness) { - throw new Error( - `Note hash ${noteHash} not found in the note hash tree at anchor block hash ${parsedAnchorBlockHash.toString()}.`, - ); - } return witness.toNoirRepresentation(); } @@ -268,11 +263,6 @@ export class Oracle { const parsedNullifier = Fr.fromString(nullifier); const witness = await this.handlerAsUtility().getNullifierMembershipWitness(parsedBlockHash, parsedNullifier); - if (!witness) { - throw new Error( - `Nullifier witness not found for nullifier ${parsedNullifier} at block hash ${parsedBlockHash.toString()}.`, - ); - } return witness.toNoirRepresentation(); } @@ -285,11 +275,6 @@ export class Oracle { const parsedNullifier = Fr.fromString(nullifier); const witness = await this.handlerAsUtility().getLowNullifierMembershipWitness(parsedBlockHash, parsedNullifier); - if (!witness) { - throw new Error( - `Low nullifier witness not found for nullifier ${parsedNullifier} at block hash ${parsedBlockHash.toString()}.`, - ); - } return witness.toNoirRepresentation(); } @@ -302,11 +287,6 @@ export class Oracle { const parsedLeafSlot = Fr.fromString(leafSlot); const witness = await this.handlerAsUtility().getPublicDataWitness(parsedBlockHash, parsedLeafSlot); - if (!witness) { - throw new Error( - `Public data witness not found for slot ${parsedLeafSlot} at block hash ${parsedBlockHash.toString()}.`, - ); - } return witness.toNoirRepresentation(); } @@ -315,9 +295,6 @@ export class Oracle { const parsedBlockNumber = Fr.fromString(blockNumber).toNumber(); const header = await this.handlerAsUtility().getBlockHeader(BlockNumber(parsedBlockNumber)); - if (!header) { - throw new Error(`Block header not found for block ${parsedBlockNumber}.`); - } return header.toFields().map(toACVMField); } @@ -325,9 +302,6 @@ export class Oracle { async aztec_utl_getAuthWitness([messageHash]: ACVMField[]): Promise { const messageHashField = Fr.fromString(messageHash); const witness = await this.handlerAsUtility().getAuthWitness(messageHashField); - if (!witness) { - throw new Error(`Unknown auth witness for message hash ${messageHashField}`); - } return [witness.map(toACVMField)]; } @@ -782,11 +756,11 @@ export class Oracle { const symKeyBuffer = fromUintArray(symKey, 8); // Noir Option is encoded as [is_some: Field, storage: Field[], length: Field]. - try { - const plaintext = await this.handlerAsUtility().decryptAes128(ciphertext, ivBuffer, symKeyBuffer); + const plaintext = await this.handlerAsUtility().decryptAes128(ciphertext, ivBuffer, symKeyBuffer); + if (plaintext) { const [storage, length] = bufferToBoundedVec(plaintext, ciphertextBVecStorage.length); return [toACVMField(1), storage, length]; - } catch { + } else { const zeroStorage = Array(ciphertextBVecStorage.length).fill(toACVMField(0)); return [toACVMField(0), zeroStorage, toACVMField(0)]; } diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts index 14b728ce67b4..27f789b4dff9 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts @@ -201,13 +201,19 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param noteHash - The note hash to find in the note hash tree. * @returns The membership witness containing the leaf index and sibling path */ - public getNoteHashMembershipWitness( + public async getNoteHashMembershipWitness( blockHash: BlockHash, noteHash: Fr, - ): Promise | undefined> { - return this.#queryWithBlockHashNotAfterAnchor(blockHash, () => + ): Promise> { + const witness = await this.#queryWithBlockHashNotAfterAnchor(blockHash, () => this.aztecNode.getNoteHashMembershipWitness(blockHash, noteHash), ); + if (!witness) { + throw new Error( + `Note hash ${noteHash} not found in the note hash tree at anchor block hash ${blockHash.toString()}.`, + ); + } + return witness; } /** @@ -239,13 +245,14 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param nullifier - Nullifier we try to find witness for. * @returns The nullifier membership witness (if found). */ - public getNullifierMembershipWitness( - blockHash: BlockHash, - nullifier: Fr, - ): Promise { - return this.#queryWithBlockHashNotAfterAnchor(blockHash, () => + public async getNullifierMembershipWitness(blockHash: BlockHash, nullifier: Fr): Promise { + const witness = await this.#queryWithBlockHashNotAfterAnchor(blockHash, () => this.aztecNode.getNullifierMembershipWitness(blockHash, nullifier), ); + if (!witness) { + throw new Error(`Nullifier witness not found for nullifier ${nullifier} at block hash ${blockHash.toString()}.`); + } + return witness; } /** @@ -257,13 +264,19 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier * we are trying to prove non-inclusion for. */ - public getLowNullifierMembershipWitness( + public async getLowNullifierMembershipWitness( blockHash: BlockHash, nullifier: Fr, - ): Promise { - return this.#queryWithBlockHashNotAfterAnchor(blockHash, () => + ): Promise { + const witness = await this.#queryWithBlockHashNotAfterAnchor(blockHash, () => this.aztecNode.getLowNullifierMembershipWitness(blockHash, nullifier), ); + if (!witness) { + throw new Error( + `Low nullifier witness not found for nullifier ${nullifier} at block hash ${blockHash.toString()}.`, + ); + } + return witness; } /** @@ -272,10 +285,14 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param leafSlot - The slot of the public data tree to get the witness for. * @returns - The witness */ - public getPublicDataWitness(blockHash: BlockHash, leafSlot: Fr): Promise { - return this.#queryWithBlockHashNotAfterAnchor(blockHash, () => + public async getPublicDataWitness(blockHash: BlockHash, leafSlot: Fr): Promise { + const witness = await this.#queryWithBlockHashNotAfterAnchor(blockHash, () => this.aztecNode.getPublicDataWitness(blockHash, leafSlot), ); + if (!witness) { + throw new Error(`Public data witness not found for slot ${leafSlot} at block hash ${blockHash.toString()}.`); + } + return witness; } /** @@ -283,7 +300,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param blockNumber - The number of a block of which to get the block header. * @returns Block extracted from a block with block number `blockNumber`. */ - public async getBlockHeader(blockNumber: BlockNumber): Promise { + public async getBlockHeader(blockNumber: BlockNumber): Promise { const anchorBlockNumber = this.anchorBlockHeader.getBlockNumber(); if (blockNumber > anchorBlockNumber) { throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); @@ -295,7 +312,10 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } const block = await this.aztecNode.getBlock(blockNumber); - return block?.header; + if (!block?.header) { + throw new Error(`Block header not found for block ${blockNumber}.`); + } + return block.header; } /** @@ -342,8 +362,12 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @param messageHash - Hash of the message to authenticate. * @returns Authentication witness for the requested message hash, or undefined if not found. */ - public getAuthWitness(messageHash: Fr): Promise { - return Promise.resolve(this.authWitnesses.find(w => w.requestHash.equals(messageHash))?.witness); + public getAuthWitness(messageHash: Fr): Promise { + const witness = this.authWitnesses.find(w => w.requestHash.equals(messageHash))?.witness; + if (!witness) { + throw new Error(`Unknown auth witness for message hash ${messageHash}`); + } + return Promise.resolve(witness); } /** @@ -671,9 +695,13 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } // TODO(#11849): consider replacing this oracle with a pure Noir implementation of aes decryption. - public decryptAes128(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise { - const aes128 = new Aes128(); - return aes128.decryptBufferCBC(ciphertext, iv, symKey); + public async decryptAes128(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise { + try { + const aes128 = new Aes128(); + return await aes128.decryptBufferCBC(ciphertext, iv, symKey); + } catch { + return undefined; + } } /** diff --git a/yarn-project/txe/src/constants.ts b/yarn-project/txe/src/constants.ts index 24230b9217a4..49307f0e814a 100644 --- a/yarn-project/txe/src/constants.ts +++ b/yarn-project/txe/src/constants.ts @@ -1,3 +1,9 @@ +import { PRIVATE_LOG_CIPHERTEXT_LEN } from '@aztec/constants'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; export const DEFAULT_ADDRESS = AztecAddress.fromNumber(42); + +// Arbitrarily set at 64 because we need a bound. Nothing inherent about it. +export const MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY = 64; +// Must match MAX_OFFCHAIN_EFFECT_LEN in noir-projects/aztec-nr/aztec/src/test/helpers/txe_oracles.nr. +export const MAX_OFFCHAIN_EFFECT_LEN = 2 + PRIVATE_LOG_CIPHERTEXT_LEN; diff --git a/yarn-project/txe/src/oracle/interfaces.ts b/yarn-project/txe/src/oracle/interfaces.ts index 0d0ecdc845cc..4ee05b93157b 100644 --- a/yarn-project/txe/src/oracle/interfaces.ts +++ b/yarn-project/txe/src/oracle/interfaces.ts @@ -38,6 +38,11 @@ export interface IAvmExecutionOracle { nullifierExists(siloedNullifier: Fr): Promise; storageWrite(slot: Fr, value: Fr): Promise; storageRead(slot: Fr, contractAddress: AztecAddress): Promise; + returndataSize(): Promise; + returndataCopy(rdOffset: number, copySize: number): Promise; + call(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise; + staticCall(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise; + successCopy(): Promise; } /** diff --git a/yarn-project/txe/src/oracle/txe_oracle_public_context.ts b/yarn-project/txe/src/oracle/txe_oracle_public_context.ts index 3631cc067d69..84e24a696896 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_public_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_public_context.ts @@ -122,6 +122,36 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle { return value; } + returndataSize(): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + + returndataCopy(_rdOffset: number, _copySize: number): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + + call(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + + staticCall(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + + successCopy(): Promise { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', + ); + } + async close(): Promise { this.logger.debug('Exiting Public Context, building block with collected side effects', { blockNumber: this.globalVariables.blockNumber, diff --git a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts index 24c1ce0d3239..0b0bf3316f2f 100644 --- a/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts +++ b/yarn-project/txe/src/oracle/txe_oracle_top_level_context.ts @@ -1,4 +1,8 @@ -import { CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants'; +import { + CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, + MAX_PRIVATE_LOGS_PER_TX, + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, +} from '@aztec/constants'; import { BlockNumber } from '@aztec/foundation/branded-types'; import { Schnorr } from '@aztec/foundation/crypto/schnorr'; import { Fr } from '@aztec/foundation/curves/bn254'; @@ -175,11 +179,16 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl const txEffects = block!.body.txEffects[0]; + const privateLogs = txEffects.privateLogs; + if (privateLogs.length > MAX_PRIVATE_LOGS_PER_TX) { + throw new Error(`${privateLogs.length} private logs exceed max ${MAX_PRIVATE_LOGS_PER_TX}`); + } + return { txHash: txEffects.txHash, noteHashes: txEffects.noteHashes, nullifiers: txEffects.nullifiers, - privateLogs: txEffects.privateLogs, + privateLogs, }; } diff --git a/yarn-project/txe/src/oracle/txe_private_execution_oracle.ts b/yarn-project/txe/src/oracle/txe_private_execution_oracle.ts new file mode 100644 index 000000000000..e0dc84d1a163 --- /dev/null +++ b/yarn-project/txe/src/oracle/txe_private_execution_oracle.ts @@ -0,0 +1,30 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; +import { PrivateExecutionOracle } from '@aztec/pxe/simulator'; +import type { FunctionSelector } from '@aztec/stdlib/abi'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; + +/** + * TXE-specific subclass of PrivateExecutionOracle that forbids operations not supported in + * TestEnvironment::private_context. TXE uses dedicated oracle flows (e.g. private_call) instead. + */ +export class TXEPrivateExecutionOracle extends PrivateExecutionOracle { + override callPrivateFunction( + _targetContractAddress: AztecAddress, + _functionSelector: FunctionSelector, + _argsHash: Fr, + _sideEffectCounter: number, + _isStaticCall: boolean, + ): Promise<{ endSideEffectCounter: Fr; returnsHash: Fr }> { + throw new Error( + 'Contract calls are forbidden inside a `TestEnvironment::private_context`, use `private_call` instead', + ); + } + + override assertValidPublicCalldata(_calldataHash: Fr): Promise { + throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context'); + } + + override notifyRevertiblePhaseStart(_minRevertibleSideEffectCounter: number): Promise { + throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context'); + } +} diff --git a/yarn-project/txe/src/rpc_translator.ts b/yarn-project/txe/src/rpc_translator.ts index bb5fa4b8d356..bef1d2f19ce2 100644 --- a/yarn-project/txe/src/rpc_translator.ts +++ b/yarn-project/txe/src/rpc_translator.ts @@ -5,7 +5,6 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, - PRIVATE_LOG_CIPHERTEXT_LEN, PRIVATE_LOG_SIZE_IN_FIELDS, } from '@aztec/constants'; import { BlockNumber } from '@aztec/foundation/branded-types'; @@ -21,8 +20,8 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { BlockHash } from '@aztec/stdlib/block'; import { GasSettings } from '@aztec/stdlib/gas'; +import { MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY, MAX_OFFCHAIN_EFFECT_LEN } from './constants.js'; import type { IAvmExecutionOracle, ITxeExecutionOracle } from './oracle/interfaces.js'; -import { TXE_ORACLE_VERSION_MAJOR } from './txe_oracle_version.js'; import type { TXESessionStateHandler } from './txe_session.js'; import { type ForeignCallArray, @@ -118,17 +117,7 @@ export class RPCTranslator { const major = fromSingle(foreignMajor).toNumber(); const minor = fromSingle(foreignMinor).toNumber(); - if (major !== TXE_ORACLE_VERSION_MAJOR) { - const hint = - major > TXE_ORACLE_VERSION_MAJOR - ? 'The test was compiled with a newer version of Aztec.nr than your test environment supports. Upgrade your test environment to a compatible version.' - : 'The test was compiled with an older version of Aztec.nr than your test environment supports. Recompile the test with a compatible version of Aztec.nr.'; - throw new Error( - `Incompatible test environment version: ${hint} See https://docs.aztec.network/errors/12 (expected test oracle major version ${TXE_ORACLE_VERSION_MAJOR}, got ${major})`, - ); - } - - this.stateHandler.setTxeOracleVersion({ major, minor }); + this.stateHandler.setTxeOracleVersion(major, minor); return toForeignCallResult([]); } @@ -329,10 +318,6 @@ export class RPCTranslator { async aztec_txe_getLastTxEffects() { const { txHash, noteHashes, nullifiers, privateLogs } = await this.handlerAsTxe().getLastTxEffects(); - if (privateLogs.length > MAX_PRIVATE_LOGS_PER_TX) { - throw new Error(`${privateLogs.length} private logs exceed max ${MAX_PRIVATE_LOGS_PER_TX}`); - } - // Same workaround as `aztec_txe_getPrivateEvents`: Noir cannot yet return nested structs with arrays, so we return // a flat multidimensional array plus per-log lengths and the total count, and reassemble into a // `BoundedVec>` on the Noir side. Each log contributes only its emitted fields. The rest @@ -364,21 +349,8 @@ export class RPCTranslator { // eslint-disable-next-line camelcase aztec_txe_getLastCallOffchainEffects() { - // This oracle returns all offchain effect payloads (messages, authwit requests, etc.) emitted by the last top-level call, - // MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY is arbitrarily set at 64 because we need a bound. Nothing inherent about it. - const MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY = 64; - // Must match MAX_OFFCHAIN_EFFECT_LEN in txe_oracles.nr. - const MAX_OFFCHAIN_EFFECT_LEN = 2 + PRIVATE_LOG_CIPHERTEXT_LEN; - const { effects } = this.stateHandler.getLastCallOffchainEffects(); - if (effects.length > MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY) { - throw new Error(`${effects.length} offchain effects exceed max ${MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY}`); - } - if (effects.some(e => e.length > MAX_OFFCHAIN_EFFECT_LEN)) { - throw new Error(`Some offchain effect has length larger than max ${MAX_OFFCHAIN_EFFECT_LEN}`); - } - const rawArrayStorage = effects .map(e => e.concat(Array(MAX_OFFCHAIN_EFFECT_LEN - e.length).fill(new Fr(0)))) .concat( @@ -510,10 +482,6 @@ export class RPCTranslator { const leafSlot = fromSingle(foreignLeafSlot); const witness = await this.handlerAsUtility().getPublicDataWitness(blockHash, leafSlot); - - if (!witness) { - throw new Error(`Public data witness not found for slot ${leafSlot} at block ${blockHash.toString()}.`); - } return toForeignCallResult(witness.toNoirRepresentation()); } @@ -589,12 +557,10 @@ export class RPCTranslator { }), ); - // Now we convert each sub-array to an array of ForeignCallSingles const returnDataAsArrayOfForeignCallSingleArrays = returnDataAsArrayOfArrays.map(subArray => subArray.map(toSingle), ); - // At last we convert the array of arrays to a bounded vec of arrays return toForeignCallResult( arrayOfArraysToBoundedVecOfArrays(returnDataAsArrayOfForeignCallSingleArrays, maxNotes, packedHintedNoteLength), ); @@ -716,16 +682,27 @@ export class RPCTranslator { } // eslint-disable-next-line camelcase - aztec_prv_callPrivateFunction( - _foreignTargetContractAddress: ForeignCallSingle, - _foreignFunctionSelector: ForeignCallSingle, - _foreignArgsHash: ForeignCallSingle, - _foreignSideEffectCounter: ForeignCallSingle, - _foreignIsStaticCall: ForeignCallSingle, + async aztec_prv_callPrivateFunction( + foreignTargetContractAddress: ForeignCallSingle, + foreignFunctionSelector: ForeignCallSingle, + foreignArgsHash: ForeignCallSingle, + foreignSideEffectCounter: ForeignCallSingle, + foreignIsStaticCall: ForeignCallSingle, ) { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::private_context`, use `private_call` instead', + const targetContractAddress = addressFromSingle(foreignTargetContractAddress); + const functionSelector = FunctionSelector.fromField(fromSingle(foreignFunctionSelector)); + const argsHash = fromSingle(foreignArgsHash); + const sideEffectCounter = fromSingle(foreignSideEffectCounter).toNumber(); + const isStaticCall = fromSingle(foreignIsStaticCall).toBool(); + + const { endSideEffectCounter, returnsHash } = await this.handlerAsPrivate().callPrivateFunction( + targetContractAddress, + functionSelector, + argsHash, + sideEffectCounter, + isStaticCall, ); + return toForeignCallResult([[endSideEffectCounter, returnsHash].map(toSingle)]); } // eslint-disable-next-line camelcase @@ -737,10 +714,6 @@ export class RPCTranslator { const nullifier = fromSingle(foreignNullifier); const witness = await this.handlerAsUtility().getNullifierMembershipWitness(blockHash, nullifier); - - if (!witness) { - throw new Error(`Nullifier membership witness not found at block ${blockHash}.`); - } return toForeignCallResult(witness.toNoirRepresentation()); } @@ -749,21 +722,21 @@ export class RPCTranslator { const messageHash = fromSingle(foreignMessageHash); const authWitness = await this.handlerAsUtility().getAuthWitness(messageHash); - - if (!authWitness) { - throw new Error(`Auth witness not found for message hash ${messageHash}.`); - } return toForeignCallResult([toArray(authWitness)]); } // eslint-disable-next-line camelcase - public aztec_prv_assertValidPublicCalldata(_foreignCalldataHash: ForeignCallSingle) { - throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context'); + public async aztec_prv_assertValidPublicCalldata(foreignCalldataHash: ForeignCallSingle) { + const calldataHash = fromSingle(foreignCalldataHash); + await this.handlerAsPrivate().assertValidPublicCalldata(calldataHash); + return toForeignCallResult([]); } // eslint-disable-next-line camelcase - public aztec_prv_notifyRevertiblePhaseStart(_foreignMinRevertibleSideEffectCounter: ForeignCallSingle) { - throw new Error('Enqueueing public calls is not supported in TestEnvironment::private_context'); + public async aztec_prv_notifyRevertiblePhaseStart(foreignMinRevertibleSideEffectCounter: ForeignCallSingle) { + const minRevertibleSideEffectCounter = fromSingle(foreignMinRevertibleSideEffectCounter).toNumber(); + await this.handlerAsPrivate().notifyRevertiblePhaseStart(minRevertibleSideEffectCounter); + return toForeignCallResult([]); } // eslint-disable-next-line camelcase @@ -785,10 +758,6 @@ export class RPCTranslator { const blockNumber = BlockNumber(fromSingle(foreignBlockNumber).toNumber()); const header = await this.handlerAsUtility().getBlockHeader(blockNumber); - - if (!header) { - throw new Error(`Block header not found for block ${blockNumber}.`); - } return toForeignCallResult(header.toFields().map(toSingle)); } @@ -801,10 +770,6 @@ export class RPCTranslator { const noteHash = fromSingle(foreignNoteHash); const witness = await this.handlerAsUtility().getNoteHashMembershipWitness(blockHash, noteHash); - - if (!witness) { - throw new Error(`Note hash ${noteHash} not found in the note hash tree at block ${blockHash.toString()}.`); - } return toForeignCallResult(witness.toNoirRepresentation()); } @@ -830,10 +795,6 @@ export class RPCTranslator { const nullifier = fromSingle(foreignNullifier); const witness = await this.handlerAsUtility().getLowNullifierMembershipWitness(blockHash, nullifier); - - if (!witness) { - throw new Error(`Low nullifier witness not found for nullifier ${nullifier} at block ${blockHash}.`); - } return toForeignCallResult(witness.toNoirRepresentation()); } @@ -1033,14 +994,14 @@ export class RPCTranslator { const symKey = fromUintArray(foreignSymKey, 8); // Noir Option is encoded as [is_some: Field, storage: Field[], length: Field]. - try { - const plaintextBuffer = await this.handlerAsUtility().decryptAes128(ciphertext, iv, symKey); + const plaintextBuffer = await this.handlerAsUtility().decryptAes128(ciphertext, iv, symKey); + if (plaintextBuffer) { const [storage, length] = arrayToBoundedVec( bufferToU8Array(plaintextBuffer), foreignCiphertextBVecStorage.length, ); return toForeignCallResult([toSingle(new Fr(1)), storage, length]); - } catch { + } else { const zeroStorage = toArray(Array(foreignCiphertextBVecStorage.length).fill(new Fr(0))); return toForeignCallResult([toSingle(new Fr(0)), zeroStorage, toSingle(new Fr(0))]); } @@ -1232,52 +1193,60 @@ export class RPCTranslator { } // eslint-disable-next-line camelcase - aztec_avm_returndataSize() { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + async aztec_avm_returndataSize() { + const result = await this.handlerAsAvm().returndataSize(); + return toForeignCallResult([toSingle(result)]); } // eslint-disable-next-line camelcase - aztec_avm_returndataCopy(_foreignRdOffset: ForeignCallSingle, _foreignCopySize: ForeignCallSingle) { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + async aztec_avm_returndataCopy(foreignRdOffset: ForeignCallSingle, foreignCopySize: ForeignCallSingle) { + const rdOffset = fromSingle(foreignRdOffset).toNumber(); + const copySize = fromSingle(foreignCopySize).toNumber(); + const result = await this.handlerAsAvm().returndataCopy(rdOffset, copySize); + return toForeignCallResult([toArray(result)]); } // eslint-disable-next-line camelcase - aztec_avm_call( - _foreignL2Gas: ForeignCallSingle, - _foreignDaGas: ForeignCallSingle, - _foreignAddress: ForeignCallSingle, - _foreignLength: ForeignCallSingle, - _foreignArgs: ForeignCallArray, + async aztec_avm_call( + foreignL2Gas: ForeignCallSingle, + foreignDaGas: ForeignCallSingle, + foreignAddress: ForeignCallSingle, + foreignLength: ForeignCallSingle, + foreignArgs: ForeignCallArray, ) { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + const l2Gas = fromSingle(foreignL2Gas).toNumber(); + const daGas = fromSingle(foreignDaGas).toNumber(); + const address = addressFromSingle(foreignAddress); + const argsLength = fromSingle(foreignLength).toNumber(); + const args = fromArray(foreignArgs); + const result = await this.handlerAsAvm().call(l2Gas, daGas, address, argsLength, args); + return toForeignCallResult([toArray(result)]); } // eslint-disable-next-line camelcase - aztec_avm_staticCall( - _foreignL2Gas: ForeignCallSingle, - _foreignDaGas: ForeignCallSingle, - _foreignAddress: ForeignCallSingle, - _foreignLength: ForeignCallSingle, - _foreignArgs: ForeignCallArray, + async aztec_avm_staticCall( + foreignL2Gas: ForeignCallSingle, + foreignDaGas: ForeignCallSingle, + foreignAddress: ForeignCallSingle, + foreignLength: ForeignCallSingle, + foreignArgs: ForeignCallArray, ) { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + const l2Gas = fromSingle(foreignL2Gas).toNumber(); + const daGas = fromSingle(foreignDaGas).toNumber(); + const address = addressFromSingle(foreignAddress); + const argsLength = fromSingle(foreignLength).toNumber(); + const args = fromArray(foreignArgs); + const result = await this.handlerAsAvm().staticCall(l2Gas, daGas, address, argsLength, args); + return toForeignCallResult([toArray(result)]); } // eslint-disable-next-line camelcase - aztec_avm_successCopy() { - throw new Error( - 'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead', - ); + async aztec_avm_successCopy() { + const result = await this.handlerAsAvm().successCopy(); + return toForeignCallResult([toSingle(result)]); } + // TODO(F-674): Move orchestration logic into the handler so the transport layer is pure serialize→delegate→deserialize. // eslint-disable-next-line camelcase async aztec_txe_privateCallNewFlow( foreignFromIsSome: ForeignCallSingle, @@ -1341,6 +1310,7 @@ export class RPCTranslator { return toForeignCallResult([toArray(returnValues)]); } + // TODO(F-674): Move orchestration logic into the handler so the transport layer is pure serialize→delegate→deserialize. // eslint-disable-next-line camelcase async aztec_txe_executeUtilityFunction( foreignTargetContractAddress: ForeignCallSingle, @@ -1373,6 +1343,7 @@ export class RPCTranslator { return toForeignCallResult([toArray(returnValues)]); } + // TODO(F-674): Move orchestration logic into the handler so the transport layer is pure serialize→delegate→deserialize. // eslint-disable-next-line camelcase async aztec_txe_publicCallNewFlow( foreignFromIsSome: ForeignCallSingle, diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 2c7c7fd493fb..32f674eeab30 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -24,7 +24,6 @@ import { type IPrivateExecutionOracle, type IUtilityExecutionOracle, Oracle, - PrivateExecutionOracle, UtilityExecutionOracle, } from '@aztec/pxe/simulator'; import { @@ -46,10 +45,11 @@ import { CallContext, GlobalVariables, OFFCHAIN_MESSAGE_IDENTIFIER, TxContext } import { z } from 'zod'; -import { DEFAULT_ADDRESS } from './constants.js'; +import { DEFAULT_ADDRESS, MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY, MAX_OFFCHAIN_EFFECT_LEN } from './constants.js'; import type { IAvmExecutionOracle, ITxeExecutionOracle } from './oracle/interfaces.js'; import { TXEOraclePublicContext } from './oracle/txe_oracle_public_context.js'; import { TXEOracleTopLevelContext } from './oracle/txe_oracle_top_level_context.js'; +import { TXEPrivateExecutionOracle } from './oracle/txe_private_execution_oracle.js'; import { RPCTranslator } from './rpc_translator.js'; import { TXEArchiver } from './state_machine/archiver.js'; import { TXEStateMachine } from './state_machine/index.js'; @@ -111,7 +111,7 @@ export type TXEOracleFunctionName = Exclude< export interface TXESessionStateHandler { /** Records the TXE oracle version reported by the Noir test code for diagnostics. */ - setTxeOracleVersion(version: { major: number; minor: number }): void; + setTxeOracleVersion(major: number, minor: number): void; enterTopLevelState(): Promise; enterPublicState(contractAddress?: AztecAddress): Promise; @@ -397,7 +397,16 @@ export class TXESession implements TXESessionStateHandler { getLastCallOffchainEffects(): { effects: Fr[][] } { this.lastCallInfo.queried = true; - return { effects: this.lastCallInfo.offchainEffects }; + const effects = this.lastCallInfo.offchainEffects; + + if (effects.length > MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY) { + throw new Error(`${effects.length} offchain effects exceed max ${MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY}`); + } + if (effects.some(e => e.length > MAX_OFFCHAIN_EFFECT_LEN)) { + throw new Error(`Some offchain effect has length larger than max ${MAX_OFFCHAIN_EFFECT_LEN}`); + } + + return { effects }; } getLastCallContext(): { txHash: Fr; anchorBlockTimestamp: bigint } { @@ -405,9 +414,19 @@ export class TXESession implements TXESessionStateHandler { return { txHash, anchorBlockTimestamp }; } - setTxeOracleVersion(version: { major: number; minor: number }): void { - this.txeOracleVersion = version; - this.logger.debug(`Test compiled with test oracle version ${version.major}.${version.minor}`); + setTxeOracleVersion(major: number, minor: number): void { + if (major !== TXE_ORACLE_VERSION_MAJOR) { + const hint = + major > TXE_ORACLE_VERSION_MAJOR + ? 'The test was compiled with a newer version of Aztec.nr than your test environment supports. Upgrade your test environment to a compatible version.' + : 'The test was compiled with an older version of Aztec.nr than your test environment supports. Recompile the test with a compatible version of Aztec.nr.'; + throw new Error( + `Incompatible test environment version: ${hint} See https://docs.aztec.network/errors/12 (expected test oracle major version ${TXE_ORACLE_VERSION_MAJOR}, got ${major})`, + ); + } + + this.txeOracleVersion = { major, minor }; + this.logger.debug(`Test compiled with test oracle version ${major}.${minor}`); } async enterTopLevelState() { @@ -489,7 +508,7 @@ export class TXESession implements TXESessionStateHandler { const taggingIndexCache = new ExecutionTaggingIndexCache(); const utilityExecutor = this.utilityExecutorForContractSync(anchorBlock); - this.oracleHandler = new PrivateExecutionOracle({ + this.oracleHandler = new TXEPrivateExecutionOracle({ argsHash: Fr.ZERO, txContext: new TxContext(this.chainId, this.version, gasSettings), callContext: new CallContext(AztecAddress.ZERO, contractAddress, FunctionSelector.empty(), false), @@ -530,7 +549,7 @@ export class TXESession implements TXESessionStateHandler { // via `anchorBlockNumber`, "latest" would be the wrong anchor for offchain-message semantics. this.setLastCallContext(Fr.ZERO, anchorBlock!.globalVariables.timestamp); - return (this.oracleHandler as PrivateExecutionOracle).getPrivateContextInputs(); + return (this.oracleHandler as TXEPrivateExecutionOracle).getPrivateContextInputs(); } async enterPublicState(contractAddress?: AztecAddress) {