From 923962a49380ef5147e1fa6d255f54481d626c8d Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 9 Jul 2026 14:28:48 +0000 Subject: [PATCH] add batch is block in archive oracle --- .../src/oracle/get_membership_witness.nr | 28 +++++++++++++++---- .../aztec-nr/aztec/src/oracle/mod.nr | 7 ++++- .../aztec-nr/aztec/src/oracle/version.nr | 2 +- .../oracle/oracle_registry.ts | 8 ++++++ .../oracle/utility_execution.test.ts | 21 ++++++++++++++ .../oracle/utility_execution_oracle.ts | 16 +++++++++++ yarn-project/pxe/src/oracle_version.ts | 4 +-- yarn-project/txe/src/rpc_translator.ts | 10 +++++++ 8 files changed, 87 insertions(+), 9 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr b/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr index 1b77cd7d7885..d81e99a71067 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr @@ -1,8 +1,11 @@ -use crate::protocol::{ - abis::block_header::BlockHeader, - constants::{ARCHIVE_HEIGHT, NOTE_HASH_TREE_HEIGHT}, - merkle_tree::MembershipWitness, - traits::Hash, +use crate::{ + ephemeral::EphemeralArray, + protocol::{ + abis::block_header::BlockHeader, + constants::{ARCHIVE_HEIGHT, NOTE_HASH_TREE_HEIGHT}, + merkle_tree::MembershipWitness, + traits::Hash, + }, }; #[oracle(aztec_utl_getNoteHashMembershipWitness)] @@ -17,6 +20,12 @@ unconstrained fn get_block_hash_membership_witness_oracle( block_hash: Field, ) -> Option> {} +#[oracle(aztec_utl_areBlockHashesInArchive)] +unconstrained fn are_block_hashes_in_archive_oracle( + anchor_block_hash: Field, + block_hashes: EphemeralArray, +) -> EphemeralArray {} + // Note: get_nullifier_membership_witness function is implemented in get_nullifier_membership_witness.nr /// Returns a membership witness for a `note_hash` in the note hash tree whose root is defined in @@ -53,6 +62,15 @@ pub unconstrained fn get_maybe_block_hash_membership_witness( get_block_hash_membership_witness_oracle(anchor_block_hash, block_hash) } +/// Returns whether each block hash is present in the archive tree whose root is defined in `anchor_block_header`. +pub unconstrained fn are_block_hashes_in_archive( + anchor_block_header: BlockHeader, + block_hashes: EphemeralArray, +) -> EphemeralArray { + let anchor_block_hash = anchor_block_header.hash(); + are_block_hashes_in_archive_oracle(anchor_block_hash, block_hashes) +} + mod test { use crate::history::test::{create_note, NOTE_CREATED_AT}; use crate::note::note_interface::NoteHash; diff --git a/noir-projects/aztec-nr/aztec/src/oracle/mod.nr b/noir-projects/aztec-nr/aztec/src/oracle/mod.nr index 3fe750eb3e10..c84420c7fac0 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/mod.nr @@ -99,7 +99,12 @@ pub mod get_nullifier_membership_witness; ], )] pub mod get_public_data_witness; -#[generate_oracle_tests] +#[generate_oracle_tests_excluding( + @[ + // TODO: cover once the test resolver can serve EphemeralArray contents. + quote { are_block_hashes_in_archive_oracle }, + ], +)] pub mod get_membership_witness; #[generate_oracle_tests] pub mod keys; diff --git a/noir-projects/aztec-nr/aztec/src/oracle/version.nr b/noir-projects/aztec-nr/aztec/src/oracle/version.nr index 54924c79d5d3..cb77267bd8c5 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/version.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/version.nr @@ -11,7 +11,7 @@ /// immediately if AZTEC_NR_MINOR > PXE_MINOR because if a contract is updated to use a newer Aztec.nr dependency /// without actually using any of the new oracles then there is no reason to throw. pub global ORACLE_VERSION_MAJOR: Field = 30; -pub global ORACLE_VERSION_MINOR: Field = 6; +pub global ORACLE_VERSION_MINOR: Field = 7; /// Asserts that the version of the oracle is compatible with the version expected by the contract. pub fn assert_compatible_oracle_version() { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.ts index b9bba4a8efc5..18bf3305e570 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_registry.ts @@ -141,6 +141,14 @@ export const ORACLE_REGISTRY = { returnType: OPTION(MEMBERSHIP_WITNESS(ARCHIVE_HEIGHT)), }), + aztec_utl_areBlockHashesInArchive: makeEntry({ + params: [ + { name: 'anchorBlockHash', type: BLOCK_HASH }, + { name: 'blockHashes', type: EPHEMERAL_ARRAY(BLOCK_HASH) }, + ], + returnType: EPHEMERAL_ARRAY(BOOL), + }), + aztec_utl_getNullifierMembershipWitness: makeEntry({ params: [ { name: 'blockHash', type: BLOCK_HASH }, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts index 6e5c956302f8..3f7e6838ef02 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts @@ -741,6 +741,27 @@ describe('Utility Execution test suite', () => { expect(aztecNode.getBlockHashMembershipWitness).toHaveBeenCalledTimes(1); }); + it('returns aligned archive-membership booleans for block hash batches', async () => { + const service = new EphemeralArrayService(); + const oracle = makeOracle({ scopes: [scope] }); + const referenceBlockHash = await anchorBlockHeader.hash(); + const presentBlockHash = BlockHash.random(); + const missingBlockHash = BlockHash.random(); + const witness = MembershipWitness.empty(ARCHIVE_HEIGHT); + + aztecNode.getBlockHashMembershipWitness.mockImplementation((_referenceBlockHash, blockHash) => + Promise.resolve(blockHash.equals(presentBlockHash) ? witness : undefined), + ); + + const result = await oracle.areBlockHashesInArchive( + referenceBlockHash, + EphemeralArray.fromValues(service, [presentBlockHash, missingBlockHash, presentBlockHash]), + ); + + expect(result.readAll(service)).toEqual([true, false, true]); + expect(aztecNode.getBlockHashMembershipWitness).toHaveBeenCalledTimes(2); + }); + it('reuses public storage reads within a utility execution', async () => { const oracle = makeOracle({ scopes: [scope] }); const blockHash = await anchorBlockHeader.hash(); 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 4672d421cfc9..983df2003f32 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 @@ -273,6 +273,22 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra return witness ? Option.some(witness) : Option.none(); } + /** Returns whether each block hash is present in the archive tree at the referenced block. */ + public async areBlockHashesInArchive( + referenceBlockHash: BlockHash, + blockHashes: EphemeralArray, + ): Promise> { + const hashes = blockHashes.readAll(this.ephemeralArrayService); + const memberships = await this.#queryWithBlockHashNotAfterAnchor(referenceBlockHash, () => + Promise.all( + hashes.map(blockHash => + this.aztecNodeReadCache.getBlockHashMembershipWitness(referenceBlockHash, blockHash).then(Boolean), + ), + ), + ); + return EphemeralArray.fromValues(this.ephemeralArrayService, memberships); + } + /** * Returns a nullifier membership witness for a given nullifier at a given block. * @param blockHash - The block hash at which to get the index. diff --git a/yarn-project/pxe/src/oracle_version.ts b/yarn-project/pxe/src/oracle_version.ts index 2072d50d221d..688b384f4d14 100644 --- a/yarn-project/pxe/src/oracle_version.ts +++ b/yarn-project/pxe/src/oracle_version.ts @@ -11,7 +11,7 @@ /// if AZTEC_NR_MINOR > PXE_MINOR because if a contract is updated to use a newer Aztec.nr dependency without actually /// using any of the new oracles then there is no reason to throw. export const ORACLE_VERSION_MAJOR = 30; -export const ORACLE_VERSION_MINOR = 6; +export const ORACLE_VERSION_MINOR = 7; /// This hash is computed from the `ORACLE_REGISTRY` declaration (each oracle's name, ordered parameter names and /// types, and return type) and is used to detect when the oracle interface changes. When it does, you need to either: @@ -19,4 +19,4 @@ export const ORACLE_VERSION_MINOR = 6; /// - increment only `ORACLE_VERSION_MINOR` if the change is additive (a new oracle was added). /// /// These constants must be kept in sync between this file and `noir-projects/aztec-nr/aztec/src/oracle/version.nr`. -export const ORACLE_INTERFACE_HASH = '6094994f539407001d2adce5b6a8792c08ef645ee39813e32390f6208e78bc16'; +export const ORACLE_INTERFACE_HASH = '416b0e7d3e6dca5803ebe2a65ea93f1e79759dc39ef8ec4c52eeba5e2b0f3fca'; diff --git a/yarn-project/txe/src/rpc_translator.ts b/yarn-project/txe/src/rpc_translator.ts index 0a8cbb71759f..4190dc9c65d4 100644 --- a/yarn-project/txe/src/rpc_translator.ts +++ b/yarn-project/txe/src/rpc_translator.ts @@ -572,6 +572,16 @@ export class RPCTranslator { }); } + // eslint-disable-next-line camelcase + aztec_utl_areBlockHashesInArchive(...inputs: ForeignCallArgs) { + return callTxeHandler({ + oracle: 'aztec_utl_areBlockHashesInArchive', + inputs, + handler: ([anchorBlockHash, blockHashes]) => + this.handlerAsUtility().areBlockHashesInArchive(anchorBlockHash, blockHashes), + }); + } + // eslint-disable-next-line camelcase aztec_utl_getLowNullifierMembershipWitness(...inputs: ForeignCallArgs) { return callTxeHandler({