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..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 @@ -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'; @@ -35,11 +36,13 @@ import { CallContext, Capsule, DroppedTxReceipt, + type GetTxReceiptOptions, GlobalVariables, MinedTxReceipt, TxEffect, TxExecutionResult, TxHash, + type TxReceipt, TxStatus, } from '@aztec/stdlib/tx'; @@ -761,6 +764,51 @@ 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: 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(); + 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(16); + + 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); + }); + 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..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 @@ -79,6 +79,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 +698,14 @@ 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: 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(