From bef22e272f95293db82ed89d2f368e5dfa960ded Mon Sep 17 00:00:00 2001 From: AztecBot Date: Fri, 10 Jul 2026 12:44:38 +0000 Subject: [PATCH 1/3] fix: bound getTxEffects oracle receipt reads --- .../oracle/utility_execution.test.ts | 38 +++++++++++++++++++ .../oracle/utility_execution_oracle.ts | 7 +++- 2 files changed, 44 insertions(+), 1 deletion(-) 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 995de85f15f1..34635d4e67c7 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 @@ -3,6 +3,7 @@ import { BlockNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded- import { Grumpkin } from '@aztec/foundation/crypto/grumpkin'; import { Fr } from '@aztec/foundation/curves/bn254'; import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; +import { promiseWithResolvers } from '@aztec/foundation/promise'; import { MembershipWitness } from '@aztec/foundation/trees'; import type { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; @@ -761,6 +762,43 @@ describe('Utility Execution test suite', () => { expect(aztecNode.getTxReceipt).toHaveBeenCalledTimes(3); }); + it('bounds concurrent batched tx-effect reads', async () => { + const service = new EphemeralArrayService(); + const oracle = makeOracle({ scopes: [scope] }); + const txHashes = Array.from({ length: 17 }, () => TxHash.random()); + const reads = txHashes.map(txHash => ({ + txHash, + deferred: promiseWithResolvers>(), + })); + + aztecNode.getTxReceipt.mockImplementation(txHash => { + const read = reads.find(({ txHash: candidate }) => candidate.equals(txHash)); + if (!read) { + throw new Error(`unexpected tx hash ${txHash}`); + } + return read.deferred.promise; + }); + + const resultPromise = oracle.getTxEffects(EphemeralArray.fromValues(service, txHashes)); + await Promise.resolve(); + await Promise.resolve(); + + expect(aztecNode.getTxReceipt).toHaveBeenCalledTimes(16); + + reads[0].deferred.resolve(makeMinedReceipt(reads[0].txHash)); + await Promise.resolve(); + await Promise.resolve(); + + expect(aztecNode.getTxReceipt).toHaveBeenCalledTimes(17); + + for (const read of reads.slice(1)) { + read.deferred.resolve(makeMinedReceipt(read.txHash)); + } + + const result = await resultPromise; + expect(result.readAll(service).every(option => option.isSome())).toBe(true); + }); + it('does not share batched tx-effect reads across utility executions', async () => { const service = new EphemeralArrayService(); const txHash = TxHash.random(); 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 125650e48871..45d3fdac45c9 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 @@ -1,4 +1,5 @@ import { ARCHIVE_HEIGHT, type NOTE_HASH_TREE_HEIGHT } from '@aztec/constants'; +import { asyncPool } from '@aztec/foundation/async-pool'; import type { BlockNumber } from '@aztec/foundation/branded-types'; import { uniqueBy } from '@aztec/foundation/collection'; import { Aes128 } from '@aztec/foundation/crypto/aes128'; @@ -79,6 +80,8 @@ import type { TransientArrayService } from '../transient_array_service.js'; import { buildACIRCallback } from './acir_callback.js'; import type { IMiscOracle, IUtilityExecutionOracle } from './interfaces.js'; +const TX_EFFECT_READ_CONCURRENCY = 16; + /** Args for UtilityExecutionOracle constructor. */ export type UtilityExecutionOracleArgs = { callContext: CallContext; @@ -696,7 +699,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } const uniqueTxHashes = uniqueBy(hashes, h => h.toString()); - const options = await Promise.all(uniqueTxHashes.map(txHash => this.#getTxEffectOption(txHash))); + const options = await asyncPool(TX_EFFECT_READ_CONCURRENCY, uniqueTxHashes, txHash => + this.#getTxEffectOption(txHash), + ); const optionsByHash = new Map(uniqueTxHashes.map((txHash, i) => [txHash.toString(), options[i]])); return EphemeralArray.fromValues( From 53c8038659e3abb38ff7af57bb8a85dd798d280e Mon Sep 17 00:00:00 2001 From: AztecBot Date: Fri, 10 Jul 2026 12:53:43 +0000 Subject: [PATCH 2/3] update PR #24640 --- .../oracle/utility_execution.test.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 34635d4e67c7..1686c51e882b 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 @@ -36,11 +36,13 @@ import { CallContext, Capsule, DroppedTxReceipt, + type GetTxReceiptOptions, GlobalVariables, MinedTxReceipt, TxEffect, TxExecutionResult, TxHash, + type TxReceipt, TxStatus, } from '@aztec/stdlib/tx'; @@ -771,13 +773,15 @@ describe('Utility Execution test suite', () => { deferred: promiseWithResolvers>(), })); - aztecNode.getTxReceipt.mockImplementation(txHash => { - const read = reads.find(({ txHash: candidate }) => candidate.equals(txHash)); - if (!read) { - throw new Error(`unexpected tx hash ${txHash}`); - } - return read.deferred.promise; - }); + aztecNode.getTxReceipt.mockImplementation( + (txHash: TxHash) => { + const read = reads.find(({ txHash: candidate }) => candidate.equals(txHash)); + if (!read) { + throw new Error(`unexpected tx hash ${txHash}`); + } + return read.deferred.promise as Promise>; + }, + ); const resultPromise = oracle.getTxEffects(EphemeralArray.fromValues(service, txHashes)); await Promise.resolve(); From 5c6cae224d7307d03fac32a461832f8ea4244e4c Mon Sep 17 00:00:00 2001 From: AztecBot Date: Fri, 10 Jul 2026 13:07:54 +0000 Subject: [PATCH 3/3] update PR #24640 --- .../oracle/utility_execution.test.ts | 10 ++++++++-- .../oracle/utility_execution_oracle.ts | 12 ++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) 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 1686c51e882b..3cdc8bd81bcd 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 @@ -793,11 +793,17 @@ describe('Utility Execution test suite', () => { await Promise.resolve(); await Promise.resolve(); - expect(aztecNode.getTxReceipt).toHaveBeenCalledTimes(17); + expect(aztecNode.getTxReceipt).toHaveBeenCalledTimes(16); - for (const read of reads.slice(1)) { + for (const read of reads.slice(1, 16)) { read.deferred.resolve(makeMinedReceipt(read.txHash)); } + await Promise.resolve(); + await Promise.resolve(); + + expect(aztecNode.getTxReceipt).toHaveBeenCalledTimes(17); + + reads[16].deferred.resolve(makeMinedReceipt(reads[16].txHash)); const result = await resultPromise; expect(result.readAll(service).every(option => option.isSome())).toBe(true); 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 45d3fdac45c9..55dd06363f57 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 @@ -1,5 +1,4 @@ import { ARCHIVE_HEIGHT, type NOTE_HASH_TREE_HEIGHT } from '@aztec/constants'; -import { asyncPool } from '@aztec/foundation/async-pool'; import type { BlockNumber } from '@aztec/foundation/branded-types'; import { uniqueBy } from '@aztec/foundation/collection'; import { Aes128 } from '@aztec/foundation/crypto/aes128'; @@ -699,9 +698,14 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } const uniqueTxHashes = uniqueBy(hashes, h => h.toString()); - const options = await asyncPool(TX_EFFECT_READ_CONCURRENCY, uniqueTxHashes, txHash => - this.#getTxEffectOption(txHash), - ); + const options: Option[] = []; + for (let i = 0; i < uniqueTxHashes.length; i += TX_EFFECT_READ_CONCURRENCY) { + options.push( + ...(await Promise.all( + uniqueTxHashes.slice(i, i + TX_EFFECT_READ_CONCURRENCY).map(txHash => this.#getTxEffectOption(txHash)), + )), + ); + } const optionsByHash = new Map(uniqueTxHashes.map((txHash, i) => [txHash.toString(), options[i]])); return EphemeralArray.fromValues(