From 21db667db4d1073a2d9862c265d9a5319c66e1c8 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Tue, 16 Dec 2025 15:48:14 +0000 Subject: [PATCH 001/140] extract getContractInstance from PXEOracleInterface --- .../contract_function_simulator.ts | 12 +++++++++-- .../execution_data_provider.ts | 9 +------- .../oracle/common.ts | 17 +++++++++++++++ .../oracle/oracle_version_is_checked.test.ts | 16 +++++++++----- .../oracle/private_execution.test.ts | 14 ++++++++----- .../oracle/private_execution.ts | 5 ++++- .../oracle/private_execution_oracle.ts | 21 +++++++++++++++++-- .../oracle/utility_execution.test.ts | 12 +++++++---- .../oracle/utility_execution_oracle.ts | 5 ++++- yarn-project/pxe/src/pxe.ts | 2 +- .../oracle/txe_oracle_top_level_context.ts | 10 ++++++++- yarn-project/txe/src/txe_session.ts | 2 ++ 12 files changed, 95 insertions(+), 30 deletions(-) create mode 100644 yarn-project/pxe/src/contract_function_simulator/oracle/common.ts diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 1c3b09c349fb..047c43c5b55e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -87,6 +87,7 @@ export class ContractFunctionSimulator { constructor( private executionDataProvider: ExecutionDataProvider, + private contractDataProvider: ContractDataProvider, private simulator: CircuitSimulator, ) { this.log = createLogger('simulator'); @@ -116,7 +117,12 @@ export class ContractFunctionSimulator { ): Promise { const simulatorSetupTimer = new Timer(); - await verifyCurrentClassId(contractAddress, this.executionDataProvider, anchorBlockHeader); + await verifyCurrentClassId( + contractAddress, + this.executionDataProvider, + this.contractDataProvider, + anchorBlockHeader, + ); const entryPointArtifact = await this.executionDataProvider.getFunctionArtifact(contractAddress, selector); @@ -155,6 +161,7 @@ export class ContractFunctionSimulator { noteCache, taggingIndexCache, this.executionDataProvider, + this.contractDataProvider, 0, // totalPublicArgsCount startSideEffectCounter, undefined, // log @@ -226,7 +233,7 @@ export class ContractFunctionSimulator { anchorBlockHeader: BlockHeader, scopes?: AztecAddress[], ): Promise { - await verifyCurrentClassId(call.to, this.executionDataProvider, anchorBlockHeader); + await verifyCurrentClassId(call.to, this.executionDataProvider, this.contractDataProvider, anchorBlockHeader); const entryPointArtifact = await this.executionDataProvider.getFunctionArtifact(call.to, call.selector); @@ -240,6 +247,7 @@ export class ContractFunctionSimulator { [], anchorBlockHeader, this.executionDataProvider, + this.contractDataProvider, undefined, scopes, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 072c975a8160..25c885e3fa91 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -5,7 +5,7 @@ import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2Block } from '@aztec/stdlib/block'; -import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; +import type { CompleteAddress } from '@aztec/stdlib/contract'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import type { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; import type { NoteStatus } from '@aztec/stdlib/note'; @@ -47,13 +47,6 @@ export type ExecutionStats = { * The interface for the data layer required to perform private and utility execution. */ export interface ExecutionDataProvider { - /** - * Returns a contract instance associated with an address, if available. - * @param address - Address. - * @returns A contract instance. - */ - getContractInstance(address: AztecAddress): Promise; - /** * Retrieve the complete address associated to a given address. * @param account - The account address. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts new file mode 100644 index 000000000000..0a177919ff1d --- /dev/null +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -0,0 +1,17 @@ +import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import type { ContractInstance } from '@aztec/stdlib/contract'; + +import type { ContractDataProvider } from '../../storage/index.js'; + +// TODO: this might not be the final home for these functions, +// it's just a way of starting to dissolve PXEOracleInterface +export async function getContractInstance( + address: AztecAddress, + contractDataProvider: ContractDataProvider, +): Promise { + const instance = await contractDataProvider.getContractInstance(address); + if (!instance) { + throw new Error(`No contract instance found for address ${address.toString()}`); + } + return instance; +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 291c5f1b450c..c433f3f9527b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -3,12 +3,13 @@ import { OracleVersionCheckContractArtifact } from '@aztec/noir-test-contracts.j import { WASMSimulator } from '@aztec/simulator/client'; import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { ContractInstance } from '@aztec/stdlib/contract'; +import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract'; import { GasFees, GasSettings } from '@aztec/stdlib/gas'; import { BlockHeader, HashedValues, TxContext, TxExecutionRequest } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; +import { ContractDataProvider } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -16,22 +17,27 @@ describe('Oracle Version Check test suite', () => { const simulator = new WASMSimulator(); let executionDataProvider: ReturnType>; + let contractDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; beforeEach(async () => { executionDataProvider = mock(); + contractDataProvider = mock(); // Mock basic oracle responses executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); - executionDataProvider.getContractInstance.mockResolvedValue({ + + contractAddress = await AztecAddress.random(); + + contractDataProvider.getContractInstance.mockResolvedValue({ currentContractClassId: new Fr(42), originalContractClassId: new Fr(42), - } as ContractInstance); + address: contractAddress, + } as ContractInstanceWithAddress); - acirSimulator = new ContractFunctionSimulator(executionDataProvider, simulator); - contractAddress = await AztecAddress.random(); + acirSimulator = new ContractFunctionSimulator(executionDataProvider, contractDataProvider, simulator); }); describe('private function execution', () => { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 20ec21edbd2e..bd0d38b95e68 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -34,7 +34,7 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter } from '@aztec/stdlib/block'; import { CompleteAddress, - type ContractInstance, + type ContractInstanceWithAddress, getContractClassFromArtifact, getContractInstanceFromInstantiationParams, } from '@aztec/stdlib/contract'; @@ -60,6 +60,7 @@ import { jest } from '@jest/globals'; import { Matcher, type MatcherCreator, type MockProxy, mock } from 'jest-mock-extended'; import { toFunctionSelector } from 'viem'; +import { ContractDataProvider } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import type { NoteData } from './interfaces.js'; @@ -102,6 +103,7 @@ describe('Private Execution test suite', () => { const simulator = new WASMSimulator(); let executionDataProvider: MockProxy; + let contractDataProvider: MockProxy; let acirSimulator: ContractFunctionSimulator; let anchorBlockHeader = BlockHeader.empty(); @@ -143,10 +145,11 @@ describe('Private Execution test suite', () => { contracts[address.toString()] = artifact; const contractClass = await getContractClassFromArtifact(artifact); - executionDataProvider.getContractInstance.calledWith(aztecAddressMatcher(address)).mockResolvedValue({ + contractDataProvider.getContractInstance.calledWith(aztecAddressMatcher(address)).mockResolvedValue({ currentContractClassId: contractClass.id, originalContractClassId: contractClass.id, - } as ContractInstance); + address, + } as ContractInstanceWithAddress); }; const runSimulator = async ({ @@ -266,6 +269,7 @@ describe('Private Execution test suite', () => { beforeEach(async () => { trees = {}; executionDataProvider = mock(); + contractDataProvider = mock(); contracts = {}; executionDataProvider.getKeyValidationRequest.mockImplementation( async (pkMHash: Fr, contractAddress: AztecAddress) => { @@ -334,7 +338,7 @@ describe('Private Execution test suite', () => { }, ); - acirSimulator = new ContractFunctionSimulator(executionDataProvider, simulator); + acirSimulator = new ContractFunctionSimulator(executionDataProvider, contractDataProvider, simulator); }); describe('no constructor', () => { @@ -398,7 +402,7 @@ describe('Private Execution test suite', () => { constructorArgs: initArgs, salt: Fr.random(), }); - executionDataProvider.getContractInstance.mockResolvedValue(instance); + contractDataProvider.getContractInstance.mockResolvedValue(instance); const executionResult = await runSimulator({ args: initArgs, artifact: StatefulTestContractArtifact, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts index 9e1dc1362182..0c5109d01548 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts @@ -27,7 +27,9 @@ import type { CircuitWitnessGenerationStats } from '@aztec/stdlib/stats'; import { BlockHeader, PrivateCallExecutionResult } from '@aztec/stdlib/tx'; import type { UInt64 } from '@aztec/stdlib/types'; +import { ContractDataProvider } from '../../storage/index.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; +import { getContractInstance } from './common.js'; import { Oracle } from './oracle.js'; import type { PrivateExecutionOracle } from './private_execution_oracle.js'; @@ -188,9 +190,10 @@ export async function readCurrentClassId( export async function verifyCurrentClassId( contractAddress: AztecAddress, executionDataProvider: ExecutionDataProvider, + contractDataProvider: ContractDataProvider, header: BlockHeader, ) { - const instance = await executionDataProvider.getContractInstance(contractAddress); + const instance = await getContractInstance(contractAddress, contractDataProvider); const currentClassId = await readCurrentClassId( contractAddress, instance, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 04f34fa7b2bb..4699096bc63a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -26,6 +26,7 @@ import { type TxContext, } from '@aztec/stdlib/tx'; +import type { ContractDataProvider } from '../../storage/index.js'; import { Tag } from '../../tagging/tag.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import type { ExecutionNoteCache } from '../execution_note_cache.js'; @@ -79,6 +80,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP private readonly noteCache: ExecutionNoteCache, private readonly taggingIndexCache: ExecutionTaggingIndexCache, executionDataProvider: ExecutionDataProvider, + contractDataProvider: ContractDataProvider, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, log = createLogger('simulator:client_execution_context'), @@ -86,7 +88,16 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP private senderForTags?: AztecAddress, private simulator?: CircuitSimulator, ) { - super(callContext.contractAddress, authWitnesses, capsules, anchorBlockHeader, executionDataProvider, log, scopes); + super( + callContext.contractAddress, + authWitnesses, + capsules, + anchorBlockHeader, + executionDataProvider, + contractDataProvider, + log, + scopes, + ); } public getPrivateContextInputs(): PrivateContextInputs { @@ -494,7 +505,12 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP isStaticCall = isStaticCall || this.callContext.isStaticCall; - await verifyCurrentClassId(targetContractAddress, this.executionDataProvider, this.anchorBlockHeader); + await verifyCurrentClassId( + targetContractAddress, + this.executionDataProvider, + this.contractDataProvider, + this.anchorBlockHeader, + ); const targetArtifact = await this.executionDataProvider.getFunctionArtifact( targetContractAddress, @@ -516,6 +532,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.noteCache, this.taggingIndexCache, this.executionDataProvider, + this.contractDataProvider, this.totalPublicCalldataCount, sideEffectCounter, this.log, 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 5ec9b6958728..bc9a730da120 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,12 +3,13 @@ import { StatefulTestContractArtifact } from '@aztec/noir-test-contracts.js/Stat import { WASMSimulator } from '@aztec/simulator/client'; import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { CompleteAddress, type ContractInstance } from '@aztec/stdlib/contract'; +import { CompleteAddress, type ContractInstanceWithAddress } from '@aztec/stdlib/contract'; import { Note } from '@aztec/stdlib/note'; import { BlockHeader } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; +import { ContractDataProvider } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -16,6 +17,7 @@ describe('Utility Execution test suite', () => { const simulator = new WASMSimulator(); let executionDataProvider: ReturnType>; + let contractDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; const ownerSecretKey = Fr.fromHexString('2dcc5485a58316776299be08c78fa3788a1a7961ae30dc747fb1be17692a8d32'); @@ -26,7 +28,8 @@ describe('Utility Execution test suite', () => { beforeEach(async () => { executionDataProvider = mock(); - acirSimulator = new ContractFunctionSimulator(executionDataProvider, simulator); + contractDataProvider = mock(); + acirSimulator = new ContractFunctionSimulator(executionDataProvider, contractDataProvider, simulator); const ownerCompleteAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(ownerSecretKey, Fr.random()); owner = ownerCompleteAddress.address; @@ -50,10 +53,11 @@ describe('Utility Execution test suite', () => { executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); executionDataProvider.getFunctionArtifact.mockResolvedValue(artifact); - executionDataProvider.getContractInstance.mockResolvedValue({ + contractDataProvider.getContractInstance.mockResolvedValue({ currentContractClassId: new Fr(42), originalContractClassId: new Fr(42), - } as ContractInstance); + address: contractAddress, + } as ContractInstanceWithAddress); executionDataProvider.getNotes.mockResolvedValue( notes.map((note, index) => ({ contractAddress, 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 12f06e948b01..49267462a2c2 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 @@ -12,9 +12,11 @@ import type { NoteStatus } from '@aztec/stdlib/note'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; +import type { ContractDataProvider } from '../../storage/index.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; +import { getContractInstance } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; /** @@ -33,6 +35,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly capsules: Capsule[], // TODO(#12425): Rename to transientCapsules protected readonly anchorBlockHeader: BlockHeader, protected readonly executionDataProvider: ExecutionDataProvider, + protected readonly contractDataProvider: ContractDataProvider, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], ) {} @@ -147,7 +150,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns A contract instance. */ public utilityGetContractInstance(address: AztecAddress): Promise { - return this.executionDataProvider.getContractInstance(address); + return getContractInstance(address, this.contractDataProvider); } /** diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index ba53f280397b..090266d0f9cd 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -195,7 +195,7 @@ export class PXE { this.privateEventDataProvider, this.log, ); - return new ContractFunctionSimulator(pxeOracleInterface, this.simulator); + return new ContractFunctionSimulator(pxeOracleInterface, this.contractDataProvider, this.simulator); } #contextualizeError(err: Error, ...context: string[]): Error { 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 5c05e9b81d91..012a23dab1ef 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 @@ -302,6 +302,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl noteCache, taggingIndexCache, this.pxeOracleInterface, + this.contractDataProvider, 0, 1, undefined, // log @@ -610,7 +611,14 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl try { const anchorBlockHeader = await this.stateMachine.anchorBlockDataProvider.getBlockHeader(); - const oracle = new UtilityExecutionOracle(call.to, [], [], anchorBlockHeader, this.pxeOracleInterface); + const oracle = new UtilityExecutionOracle( + call.to, + [], + [], + anchorBlockHeader, + this.pxeOracleInterface, + this.contractDataProvider, + ); const acirExecutionResult = await new WASMSimulator() .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) .catch((err: Error) => { diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 1e84ade1a9ac..b3554e8e5b12 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -306,6 +306,7 @@ export class TXESession implements TXESessionStateHandler { noteCache, taggingIndexCache, this.pxeOracleInterface, + this.contractDataProvider, ); // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary @@ -361,6 +362,7 @@ export class TXESession implements TXESessionStateHandler { [], anchorBlockHeader, this.pxeOracleInterface, + this.contractDataProvider, ); this.state = { name: 'UTILITY' }; From 511fb1868ff1b766615e044985560a696be16a67 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Tue, 16 Dec 2025 16:51:44 +0000 Subject: [PATCH 002/140] extract getFunctionArtifact from PXEOracleInterface --- .../contract_function_simulator.ts | 5 ++-- .../execution_data_provider.ts | 15 +---------- .../oracle/common.ts | 17 ++++++++++++ .../oracle/index.ts | 1 + .../oracle/oracle_version_is_checked.test.ts | 4 +-- .../oracle/private_execution.test.ts | 5 ++-- .../oracle/private_execution_oracle.ts | 4 ++- .../oracle/utility_execution.test.ts | 2 +- .../pxe_oracle_interface.ts | 27 ++----------------- .../src/entrypoints/client/bundle/index.ts | 1 + .../pxe/src/entrypoints/client/lazy/index.ts | 1 + .../pxe/src/entrypoints/server/index.ts | 1 + .../oracle/txe_oracle_top_level_context.ts | 3 ++- 13 files changed, 38 insertions(+), 48 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 047c43c5b55e..0aeaa4ccfec6 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -74,6 +74,7 @@ import type { ExecutionDataProvider } from './execution_data_provider.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; import { HashedValuesCache } from './hashed_values_cache.js'; +import { getFunctionArtifact } from './oracle/common.js'; import { Oracle } from './oracle/oracle.js'; import { executePrivateFunction, verifyCurrentClassId } from './oracle/private_execution.js'; import { PrivateExecutionOracle } from './oracle/private_execution_oracle.js'; @@ -124,7 +125,7 @@ export class ContractFunctionSimulator { anchorBlockHeader, ); - const entryPointArtifact = await this.executionDataProvider.getFunctionArtifact(contractAddress, selector); + const entryPointArtifact = await getFunctionArtifact(contractAddress, selector, this.contractDataProvider); if (entryPointArtifact.functionType !== FunctionType.PRIVATE) { throw new Error(`Cannot run ${entryPointArtifact.functionType} function as private`); @@ -235,7 +236,7 @@ export class ContractFunctionSimulator { ): Promise { await verifyCurrentClassId(call.to, this.executionDataProvider, this.contractDataProvider, anchorBlockHeader); - const entryPointArtifact = await this.executionDataProvider.getFunctionArtifact(call.to, call.selector); + const entryPointArtifact = await getFunctionArtifact(call.to, call.selector, this.contractDataProvider); if (entryPointArtifact.functionType !== FunctionType.UTILITY) { throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 25c885e3fa91..10087b30b17a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -2,7 +2,7 @@ import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { BlockNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { Point } from '@aztec/foundation/curves/grumpkin'; -import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; +import type { FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress } from '@aztec/stdlib/contract'; @@ -83,19 +83,6 @@ export interface ExecutionDataProvider { scopes?: AztecAddress[], ): Promise; - /** - * Retrieve the artifact information of a specific function within a contract. - * The function is identified by its selector, which is a unique identifier generated from the function signature. - * - * @param contractAddress - The contract address. - * @param selector - The corresponding function selector. - * @returns A Promise that resolves to a FunctionArtifact object. - */ - getFunctionArtifact( - contractAddress: AztecAddress, - selector: FunctionSelector, - ): Promise; - /** * Generates a stable function name for debug purposes. * @param contractAddress - The contract address. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 0a177919ff1d..2da6d39c5706 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,3 +1,4 @@ +import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractInstance } from '@aztec/stdlib/contract'; @@ -15,3 +16,19 @@ export async function getContractInstance( } return instance; } + +export async function getFunctionArtifact( + contractAddress: AztecAddress, + selector: FunctionSelector, + contractDataProvider: ContractDataProvider, +): Promise { + const artifact = await contractDataProvider.getFunctionArtifact(contractAddress, selector); + if (!artifact) { + throw new Error(`Function artifact not found for contract ${contractAddress} and selector ${selector}.`); + } + const debug = await contractDataProvider.getFunctionDebugMetadata(contractAddress, selector); + return { + ...artifact, + debug, + }; +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/index.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/index.ts index 10156a3340bc..63e4c7aadf6c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/index.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/index.ts @@ -2,6 +2,7 @@ import type { Oracle } from './oracle.js'; export * from './oracle.js'; export * from './interfaces.js'; +export * from './common.js'; /** * A conditional type that takes a type `T` and returns a union of its method names. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index c433f3f9527b..6903b5420618 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -47,7 +47,7 @@ describe('Oracle Version Check test suite', () => { ...OracleVersionCheckContractArtifact.functions.find(f => f.name === 'private_function')!, contractName: OracleVersionCheckContractArtifact.name, }; - executionDataProvider.getFunctionArtifact.mockResolvedValue(privateFunctionArtifact); + contractDataProvider.getFunctionArtifact.mockResolvedValue(privateFunctionArtifact); // Form the execution request for the private function const selector = await FunctionSelector.fromNameAndParameters( @@ -87,7 +87,7 @@ describe('Oracle Version Check test suite', () => { ...OracleVersionCheckContractArtifact.functions.find(f => f.name === 'utility_function')!, contractName: OracleVersionCheckContractArtifact.name, }; - executionDataProvider.getFunctionArtifact.mockResolvedValue(utilityFunctionArtifact); + contractDataProvider.getFunctionArtifact.mockResolvedValue(utilityFunctionArtifact); // Form the execution request for the utility function const execRequest: FunctionCall = { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index bd0d38b95e68..4c707a67c9b4 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -310,7 +310,8 @@ describe('Private Execution test suite', () => { executionDataProvider.getLastUsedIndexAsSender.mockImplementation((_secret: DirectionalAppTaggingSecret) => { return Promise.resolve(undefined); }); - executionDataProvider.getFunctionArtifact.mockImplementation(async (address, selector) => { + + contractDataProvider.getFunctionArtifact.mockImplementation(async (address, selector) => { const contract = contracts[address.toString()]; if (!contract) { throw new Error(`Contract not found: ${address}`); @@ -574,7 +575,7 @@ describe('Private Execution test suite', () => { expect(result.returnValues).toEqual([new Fr(privateIncrement)]); // First fetch of the function artifact is the parent contract - expect(executionDataProvider.getFunctionArtifact.mock.calls[1]).toEqual([childAddress, childSelector]); + expect(contractDataProvider.getFunctionArtifact.mock.calls[1]).toEqual([childAddress, childSelector]); expect(result.nestedExecutionResults).toHaveLength(1); expect(result.nestedExecutionResults[0].returnValues).toEqual([new Fr(privateIncrement)]); expect(result.publicInputs.privateCallRequests.array[0].callContext).toEqual( diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 4699096bc63a..25c4f76cab00 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -33,6 +33,7 @@ import type { ExecutionNoteCache } from '../execution_note_cache.js'; import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import type { HashedValuesCache } from '../hashed_values_cache.js'; import { pickNotes } from '../pick_notes.js'; +import { getFunctionArtifact } from './common.js'; import type { IPrivateExecutionOracle, NoteData } from './interfaces.js'; import { executePrivateFunction, verifyCurrentClassId } from './private_execution.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; @@ -512,9 +513,10 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.anchorBlockHeader, ); - const targetArtifact = await this.executionDataProvider.getFunctionArtifact( + const targetArtifact = await getFunctionArtifact( targetContractAddress, functionSelector, + this.contractDataProvider, ); const derivedTxContext = this.txContext.clone(); 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 bc9a730da120..96c9aa30e1b1 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 @@ -52,7 +52,7 @@ describe('Utility Execution test suite', () => { const notes: Note[] = [...Array(5).fill(buildNote(1n)), ...Array(2).fill(buildNote(2n))]; executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); - executionDataProvider.getFunctionArtifact.mockResolvedValue(artifact); + contractDataProvider.getFunctionArtifact.mockResolvedValue(artifact); contractDataProvider.getContractInstance.mockResolvedValue({ currentContractClassId: new Fr(42), originalContractClassId: new Fr(42), diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 1814a459666c..de7798a864cd 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -4,10 +4,10 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { Point } from '@aztec/foundation/curves/grumpkin'; import { createLogger } from '@aztec/foundation/log'; import type { KeyStore } from '@aztec/key-store'; -import { EventSelector, type FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; +import { EventSelector, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; -import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; +import type { CompleteAddress } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/client'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; @@ -82,14 +82,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return completeAddress; } - async getContractInstance(address: AztecAddress): Promise { - const instance = await this.contractDataProvider.getContractInstance(address); - if (!instance) { - throw new Error(`No contract instance found for address ${address.toString()}`); - } - return instance; - } - async getNotes( contractAddress: AztecAddress, owner: AztecAddress | undefined, @@ -120,21 +112,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { ); } - async getFunctionArtifact( - contractAddress: AztecAddress, - selector: FunctionSelector, - ): Promise { - const artifact = await this.contractDataProvider.getFunctionArtifact(contractAddress, selector); - if (!artifact) { - throw new Error(`Function artifact not found for contract ${contractAddress} and selector ${selector}.`); - } - const debug = await this.contractDataProvider.getFunctionDebugMetadata(contractAddress, selector); - return { - ...artifact, - debug, - }; - } - /** * Fetches a message from the db, given its key. * @param contractAddress - Address of a contract by which the message was emitted. diff --git a/yarn-project/pxe/src/entrypoints/client/bundle/index.ts b/yarn-project/pxe/src/entrypoints/client/bundle/index.ts index da0b93454cc9..9d60537ef276 100644 --- a/yarn-project/pxe/src/entrypoints/client/bundle/index.ts +++ b/yarn-project/pxe/src/entrypoints/client/bundle/index.ts @@ -3,5 +3,6 @@ export * from '../../../config/index.js'; export * from '../../../error_enriching.js'; export * from '../../../storage/index.js'; export * from './utils.js'; +export * from '../../../contract_function_simulator/oracle/common.js'; export { PXEOracleInterface } from '../../../contract_function_simulator/pxe_oracle_interface.js'; export type { PXECreationOptions } from '../../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/entrypoints/client/lazy/index.ts b/yarn-project/pxe/src/entrypoints/client/lazy/index.ts index 2843fa7b4f3f..f84c9d5a79bb 100644 --- a/yarn-project/pxe/src/entrypoints/client/lazy/index.ts +++ b/yarn-project/pxe/src/entrypoints/client/lazy/index.ts @@ -3,5 +3,6 @@ export * from '../../../config/index.js'; export * from '../../../storage/index.js'; export * from '../../../error_enriching.js'; export * from './utils.js'; +export * from '../../../contract_function_simulator/oracle/common.js'; export { PXEOracleInterface } from '../../../contract_function_simulator/pxe_oracle_interface.js'; export { type PXECreationOptions } from '../../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/entrypoints/server/index.ts b/yarn-project/pxe/src/entrypoints/server/index.ts index f072905bab89..5a70a5108d17 100644 --- a/yarn-project/pxe/src/entrypoints/server/index.ts +++ b/yarn-project/pxe/src/entrypoints/server/index.ts @@ -3,6 +3,7 @@ export * from '../../config/index.js'; export * from '../../error_enriching.js'; export * from '../../storage/index.js'; export * from './utils.js'; +export * from '../../contract_function_simulator/oracle/common.js'; export { PXEOracleInterface } from '../../contract_function_simulator/pxe_oracle_interface.js'; export { ORACLE_VERSION } from '../../oracle_version.js'; export { type PXECreationOptions } from '../pxe_creation_options.js'; 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 012a23dab1ef..b0951670e56f 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 @@ -17,6 +17,7 @@ import { ORACLE_VERSION, PXEOracleInterface, enrichPublicSimulationError, + getFunctionArtifact, } from '@aztec/pxe/server'; import { ExecutionNoteCache, @@ -599,7 +600,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl to: targetContractAddress, }; - const entryPointArtifact = await this.pxeOracleInterface.getFunctionArtifact(call.to, call.selector); + const entryPointArtifact = await getFunctionArtifact(call.to, call.selector, this.contractDataProvider); if (entryPointArtifact.functionType !== FunctionType.UTILITY) { throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`); } From 3510ad579733db8663365df9008beb7fe75f3ecf Mon Sep 17 00:00:00 2001 From: mverzilli Date: Tue, 16 Dec 2025 16:55:07 +0000 Subject: [PATCH 003/140] extract getDebugFunctionName from PXEOracleInterface --- .../execution_data_provider.ts | 8 -------- .../oracle/private_execution_oracle.ts | 2 +- .../contract_function_simulator/pxe_oracle_interface.ts | 6 +----- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 10087b30b17a..ca0c32cd7ac3 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -2,7 +2,6 @@ import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { BlockNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { Point } from '@aztec/foundation/curves/grumpkin'; -import type { FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress } from '@aztec/stdlib/contract'; @@ -83,13 +82,6 @@ export interface ExecutionDataProvider { scopes?: AztecAddress[], ): Promise; - /** - * Generates a stable function name for debug purposes. - * @param contractAddress - The contract address. - * @param selector - The corresponding function selector. - */ - getDebugFunctionName(contractAddress: AztecAddress, selector: FunctionSelector): Promise; - /** * Gets the index of a nullifier in the nullifier tree. * @param nullifier - The nullifier. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 25c4f76cab00..9b7a6a61e37c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -647,7 +647,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP } public getDebugFunctionName() { - return this.executionDataProvider.getDebugFunctionName(this.contractAddress, this.callContext.functionSelector); + return this.contractDataProvider.getDebugFunctionName(this.contractAddress, this.callContext.functionSelector); } public utilityEmitOffchainEffect(data: Fr[]): Promise { diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index de7798a864cd..3041e0ef3e33 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -4,7 +4,7 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { Point } from '@aztec/foundation/curves/grumpkin'; import { createLogger } from '@aztec/foundation/log'; import type { KeyStore } from '@aztec/key-store'; -import { EventSelector, FunctionSelector } from '@aztec/stdlib/abi'; +import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress } from '@aztec/stdlib/contract'; @@ -225,10 +225,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { } } - public getDebugFunctionName(contractAddress: AztecAddress, selector: FunctionSelector): Promise { - return this.contractDataProvider.getDebugFunctionName(contractAddress, selector); - } - /** * Returns the full contents of your address book. * This is used when calculating tags for incoming notes by deriving the shared secret, the contract-siloed tagging secret, and From cec02895286d57484df247db1b6e7af932548110 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 08:33:34 +0000 Subject: [PATCH 004/140] extract getNotes from PXEOracleInterface --- .../contract_function_simulator.ts | 5 +- .../execution_data_provider.ts | 22 --------- .../oracle/common.ts | 35 ++++++++++++- .../oracle/oracle_version_is_checked.test.ts | 11 ++++- .../oracle/private_execution.test.ts | 49 ++++++++++++------- .../oracle/private_execution_oracle.ts | 10 ++-- .../oracle/utility_execution.test.ts | 48 +++++++++++------- .../oracle/utility_execution_oracle.ts | 8 +-- .../pxe_oracle_interface.ts | 33 +------------ yarn-project/pxe/src/pxe.ts | 7 ++- .../oracle/txe_oracle_top_level_context.ts | 4 ++ yarn-project/txe/src/txe_session.test.ts | 1 + yarn-project/txe/src/txe_session.ts | 6 +++ 13 files changed, 139 insertions(+), 100 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 0aeaa4ccfec6..e8ce5168859a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -69,7 +69,7 @@ import { getFinalMinRevertibleSideEffectCounter, } from '@aztec/stdlib/tx'; -import type { ContractDataProvider } from '../storage/index.js'; +import type { ContractDataProvider, NoteDataProvider } from '../storage/index.js'; import type { ExecutionDataProvider } from './execution_data_provider.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; @@ -89,6 +89,7 @@ export class ContractFunctionSimulator { constructor( private executionDataProvider: ExecutionDataProvider, private contractDataProvider: ContractDataProvider, + private noteDataProvider: NoteDataProvider, private simulator: CircuitSimulator, ) { this.log = createLogger('simulator'); @@ -163,6 +164,7 @@ export class ContractFunctionSimulator { taggingIndexCache, this.executionDataProvider, this.contractDataProvider, + this.noteDataProvider, 0, // totalPublicArgsCount startSideEffectCounter, undefined, // log @@ -249,6 +251,7 @@ export class ContractFunctionSimulator { anchorBlockHeader, this.executionDataProvider, this.contractDataProvider, + this.noteDataProvider, undefined, scopes, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index ca0c32cd7ac3..ea2a1032d0c6 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -7,11 +7,9 @@ import type { L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress } from '@aztec/stdlib/contract'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import type { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; -import type { NoteStatus } from '@aztec/stdlib/note'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { NodeStats } from '@aztec/stdlib/tx'; -import type { NoteData } from './oracle/interfaces.js'; import type { MessageLoadOracleInputs } from './oracle/message_load_oracle_inputs.js'; /** @@ -62,26 +60,6 @@ export interface ExecutionDataProvider { */ getKeyValidationRequest(pkMHash: Fr, contractAddress: AztecAddress): Promise; - /** - * Retrieves a set of notes stored in the database for a given contract address and storage slot. - * The query result is paginated using 'limit' and 'offset' values. - * Returns an object containing an array of note data. - * - * @param contractAddress - The contract address of the notes. - * @param owner - The owner of the notes. If undefined, returns notes for all owners. - * @param storageSlot - The storage slot of the notes. - * @param status - The status of notes to fetch. - * @param scopes - The accounts whose notes we can access in this call. Currently optional and will default to all. - * @returns A Promise that resolves to an array of note data. - */ - getNotes( - contractAddress: AztecAddress, - owner: AztecAddress | undefined, - storageSlot: Fr, - status: NoteStatus, - scopes?: AztecAddress[], - ): Promise; - /** * Gets the index of a nullifier in the nullifier tree. * @param nullifier - The nullifier. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 2da6d39c5706..f40a045c8da2 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,8 +1,10 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractInstance } from '@aztec/stdlib/contract'; +import type { NoteStatus } from '@aztec/stdlib/note'; -import type { ContractDataProvider } from '../../storage/index.js'; +import type { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; // TODO: this might not be the final home for these functions, // it's just a way of starting to dissolve PXEOracleInterface @@ -32,3 +34,34 @@ export async function getFunctionArtifact( debug, }; } + +export async function getNotes( + contractAddress: AztecAddress, + owner: AztecAddress | undefined, + storageSlot: Fr, + status: NoteStatus, + noteDataProvider: NoteDataProvider, + scopes?: AztecAddress[], +) { + const noteDaos = await noteDataProvider.getNotes({ + contractAddress, + owner, + storageSlot, + status, + scopes, + }); + return noteDaos.map( + ({ contractAddress, owner, storageSlot, randomness, noteNonce, note, noteHash, siloedNullifier, index }) => ({ + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + note, + noteHash, + siloedNullifier, + // PXE can use this index to get full MembershipWitness + index, + }), + ); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 6903b5420618..b207df847828 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -9,7 +9,7 @@ import { BlockHeader, HashedValues, TxContext, TxExecutionRequest } from '@aztec import { mock } from 'jest-mock-extended'; -import { ContractDataProvider } from '../../storage/index.js'; +import { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -18,12 +18,14 @@ describe('Oracle Version Check test suite', () => { let executionDataProvider: ReturnType>; let contractDataProvider: ReturnType>; + let noteDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; beforeEach(async () => { executionDataProvider = mock(); contractDataProvider = mock(); + noteDataProvider = mock(); // Mock basic oracle responses executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); @@ -37,7 +39,12 @@ describe('Oracle Version Check test suite', () => { address: contractAddress, } as ContractInstanceWithAddress); - acirSimulator = new ContractFunctionSimulator(executionDataProvider, contractDataProvider, simulator); + acirSimulator = new ContractFunctionSimulator( + executionDataProvider, + contractDataProvider, + noteDataProvider, + simulator, + ); }); describe('private function execution', () => { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 4c707a67c9b4..cf7974199510 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -6,6 +6,7 @@ import { PUBLIC_DATA_TREE_HEIGHT, } from '@aztec/constants'; import { asyncMap } from '@aztec/foundation/async-map'; +import { BlockNumber } from '@aztec/foundation/branded-types'; import { times } from '@aztec/foundation/collection'; import { poseidon2Hash, poseidon2HashWithSeparator } from '@aztec/foundation/crypto/poseidon'; import { randomInt } from '@aztec/foundation/crypto/random'; @@ -31,7 +32,7 @@ import { getFunctionArtifactByName, } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter } from '@aztec/stdlib/block'; +import { type BlockParameter, L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress, type ContractInstanceWithAddress, @@ -44,7 +45,7 @@ import { KeyValidationRequest } from '@aztec/stdlib/kernel'; import { computeAppNullifierSecretKey, deriveKeys } from '@aztec/stdlib/keys'; import { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; import { L1Actor, L1ToL2Message, L2Actor } from '@aztec/stdlib/messaging'; -import { Note } from '@aztec/stdlib/note'; +import { Note, NoteDao } from '@aztec/stdlib/note'; import { makeBlockHeader } from '@aztec/stdlib/testing'; import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees'; import { @@ -54,16 +55,16 @@ import { StateReference, TxContext, TxExecutionRequest, + TxHash, } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; import { Matcher, type MatcherCreator, type MockProxy, mock } from 'jest-mock-extended'; import { toFunctionSelector } from 'viem'; -import { ContractDataProvider } from '../../storage/index.js'; +import { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; -import type { NoteData } from './interfaces.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; jest.setTimeout(60_000); @@ -104,6 +105,7 @@ describe('Private Execution test suite', () => { let executionDataProvider: MockProxy; let contractDataProvider: MockProxy; + let noteDataProvider: MockProxy; let acirSimulator: ContractFunctionSimulator; let anchorBlockHeader = BlockHeader.empty(); @@ -270,6 +272,7 @@ describe('Private Execution test suite', () => { trees = {}; executionDataProvider = mock(); contractDataProvider = mock(); + noteDataProvider = mock(); contracts = {}; executionDataProvider.getKeyValidationRequest.mockImplementation( async (pkMHash: Fr, contractAddress: AztecAddress) => { @@ -339,7 +342,12 @@ describe('Private Execution test suite', () => { }, ); - acirSimulator = new ContractFunctionSimulator(executionDataProvider, contractDataProvider, simulator); + acirSimulator = new ContractFunctionSimulator( + executionDataProvider, + contractDataProvider, + noteDataProvider, + simulator, + ); }); describe('no constructor', () => { @@ -363,7 +371,7 @@ describe('Private Execution test suite', () => { const mockFirstNullifier = new Fr(1111); let currentNoteIndex = 0n; - const buildNote = async (amount: bigint, owner: AztecAddress, storageSlot: Fr) => { + const buildNote = async (amount: bigint, owner: AztecAddress, storageSlot: Fr): Promise => { // WARNING: this is not actually how nonces are computed! // For the purpose of this test we use a mocked firstNullifier and and a random number // to compute the nonce. Proper nonces are only enforced later by the kernel/later circuits @@ -378,17 +386,21 @@ describe('Private Execution test suite', () => { // Note: The following does not correspond to how note hashing is generally done in real notes. const noteHash = await poseidon2Hash([storageSlot, ...note.items]); const randomness = Fr.random(); - return { + + return new NoteDao( + note, contractAddress, owner, storageSlot, randomness, noteNonce, - note, noteHash, - siloedNullifier: new Fr(0), - index: currentNoteIndex++, - }; + new Fr(0), + TxHash.random(), + BlockNumber(Math.abs(randomInt(1000))), + L2BlockHash.random().toString(), + currentNoteIndex++, + ); }; beforeEach(async () => { @@ -453,12 +465,13 @@ describe('Private Execution test suite', () => { it('should run the destroy_and_create function', async () => { const storageSlot = StatefulTestContractArtifact.storageLayout['notes'].slot; - const notes: NoteData[] = await Promise.all([ + const notes: NoteDao[] = await Promise.all([ buildNote(60n, ownerCompleteAddress.address, storageSlot), buildNote(80n, ownerCompleteAddress.address, storageSlot), ]); executionDataProvider.syncTaggedLogs.mockResolvedValue(); - executionDataProvider.getNotes.mockResolvedValue(notes); + + noteDataProvider.getNotes.mockResolvedValue(notes); const consumedNotes = await asyncMap(notes, async ({ note, noteNonce, randomness }) => { const noteHash = await computeNoteHash(note, owner, storageSlot, randomness); @@ -506,7 +519,7 @@ describe('Private Execution test suite', () => { const notes = await Promise.all([buildNote(balance, ownerCompleteAddress.address, storageSlot)]); executionDataProvider.syncTaggedLogs.mockResolvedValue(); - executionDataProvider.getNotes.mockResolvedValue(notes); + noteDataProvider.getNotes.mockResolvedValue(notes); const consumedNotes = await asyncMap(notes, async ({ note, noteNonce, randomness }) => { const noteHash = await computeNoteHash(note, owner, storageSlot, randomness); @@ -937,7 +950,7 @@ describe('Private Execution test suite', () => { it('should be able to insert, read, and nullify pending note hashes in one call', async () => { executionDataProvider.syncTaggedLogs.mockResolvedValue(); - executionDataProvider.getNotes.mockResolvedValue([]); + noteDataProvider.getNotes.mockResolvedValue([]); const amountToTransfer = 100n; @@ -987,7 +1000,7 @@ describe('Private Execution test suite', () => { it('should be able to insert, read, and nullify pending note hashes in nested calls', async () => { executionDataProvider.syncTaggedLogs.mockResolvedValue(); - executionDataProvider.getNotes.mockResolvedValue([]); + noteDataProvider.getNotes.mockResolvedValue([]); const amountToTransfer = 100n; @@ -1055,7 +1068,7 @@ describe('Private Execution test suite', () => { it('cant read a commitment that is inserted later in same call', async () => { executionDataProvider.syncTaggedLogs.mockResolvedValue(); - executionDataProvider.getNotes.mockResolvedValue([]); + noteDataProvider.getNotes.mockResolvedValue([]); const amountToTransfer = 100n; @@ -1096,7 +1109,7 @@ describe('Private Execution test suite', () => { // call_get_notes(owner: AztecAddress, storage_slot: Field, active_or_nullified: bool) const args = [owner, 2n, true]; executionDataProvider.syncTaggedLogs.mockResolvedValue(); - executionDataProvider.getNotes.mockResolvedValue([]); + noteDataProvider.getNotes.mockResolvedValue([]); await expect(() => runSimulator({ artifact: TestContractArtifact, functionName: 'call_get_notes', args, anchorBlockHeader }), diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 9b7a6a61e37c..8c2367dd9604 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -26,14 +26,14 @@ import { type TxContext, } from '@aztec/stdlib/tx'; -import type { ContractDataProvider } from '../../storage/index.js'; +import type { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import { Tag } from '../../tagging/tag.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import type { ExecutionNoteCache } from '../execution_note_cache.js'; import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import type { HashedValuesCache } from '../hashed_values_cache.js'; import { pickNotes } from '../pick_notes.js'; -import { getFunctionArtifact } from './common.js'; +import { getFunctionArtifact, getNotes } from './common.js'; import type { IPrivateExecutionOracle, NoteData } from './interfaces.js'; import { executePrivateFunction, verifyCurrentClassId } from './private_execution.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; @@ -82,6 +82,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP private readonly taggingIndexCache: ExecutionTaggingIndexCache, executionDataProvider: ExecutionDataProvider, contractDataProvider: ContractDataProvider, + noteDataProvider: NoteDataProvider, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, log = createLogger('simulator:client_execution_context'), @@ -96,6 +97,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP anchorBlockHeader, executionDataProvider, contractDataProvider, + noteDataProvider, log, scopes, ); @@ -328,11 +330,12 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP const pendingNotes = this.noteCache.getNotes(this.callContext.contractAddress, owner, storageSlot); const pendingNullifiers = this.noteCache.getNullifiers(this.callContext.contractAddress); - const dbNotes = await this.executionDataProvider.getNotes( + const dbNotes = await getNotes( this.callContext.contractAddress, owner, storageSlot, status, + this.noteDataProvider, this.scopes, ); const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value)); @@ -535,6 +538,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.taggingIndexCache, this.executionDataProvider, this.contractDataProvider, + this.noteDataProvider, this.totalPublicCalldataCount, sideEffectCounter, this.log, 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 96c9aa30e1b1..8a1e31fc41f8 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 @@ -1,15 +1,17 @@ +import { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { StatefulTestContractArtifact } from '@aztec/noir-test-contracts.js/StatefulTest'; import { WASMSimulator } from '@aztec/simulator/client'; import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress, type ContractInstanceWithAddress } from '@aztec/stdlib/contract'; -import { Note } from '@aztec/stdlib/note'; -import { BlockHeader } from '@aztec/stdlib/tx'; +import { Note, NoteDao } from '@aztec/stdlib/note'; +import { BlockHeader, TxHash } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; -import { ContractDataProvider } from '../../storage/index.js'; +import { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -18,6 +20,7 @@ describe('Utility Execution test suite', () => { let executionDataProvider: ReturnType>; let contractDataProvider: ReturnType>; + let noteDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; const ownerSecretKey = Fr.fromHexString('2dcc5485a58316776299be08c78fa3788a1a7961ae30dc747fb1be17692a8d32'); @@ -29,7 +32,13 @@ describe('Utility Execution test suite', () => { beforeEach(async () => { executionDataProvider = mock(); contractDataProvider = mock(); - acirSimulator = new ContractFunctionSimulator(executionDataProvider, contractDataProvider, simulator); + noteDataProvider = mock(); + acirSimulator = new ContractFunctionSimulator( + executionDataProvider, + contractDataProvider, + noteDataProvider, + simulator, + ); const ownerCompleteAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(ownerSecretKey, Fr.random()); owner = ownerCompleteAddress.address; @@ -58,19 +67,24 @@ describe('Utility Execution test suite', () => { originalContractClassId: new Fr(42), address: contractAddress, } as ContractInstanceWithAddress); - executionDataProvider.getNotes.mockResolvedValue( - notes.map((note, index) => ({ - contractAddress, - owner, - storageSlot: Fr.random(), - randomness: Fr.random(), - noteNonce: Fr.random(), - isSome: new Fr(1), - note, - noteHash: Fr.random(), - siloedNullifier: Fr.random(), - index: BigInt(index), - })), + noteDataProvider.getNotes.mockResolvedValue( + notes.map( + (note, index) => + new NoteDao( + note, + contractAddress, + owner, + Fr.random(), + Fr.random(), + Fr.random(), + Fr.random(), + Fr.random(), + TxHash.random(), + BlockNumber(Fr.random().toNumber()), + L2BlockHash.random().toString(), + BigInt(index), + ), + ), ); executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); 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 49267462a2c2..d61232078e76 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 @@ -12,11 +12,11 @@ import type { NoteStatus } from '@aztec/stdlib/note'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; -import type { ContractDataProvider } from '../../storage/index.js'; +import type { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; -import { getContractInstance } from './common.js'; +import { getContractInstance, getNotes } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; /** @@ -36,6 +36,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly anchorBlockHeader: BlockHeader, protected readonly executionDataProvider: ExecutionDataProvider, protected readonly contractDataProvider: ContractDataProvider, + protected readonly noteDataProvider: NoteDataProvider, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], ) {} @@ -202,11 +203,12 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra offset: number, status: NoteStatus, ): Promise { - const dbNotes = await this.executionDataProvider.getNotes( + const dbNotes = await getNotes( this.contractAddress, owner, storageSlot, status, + this.noteDataProvider, this.scopes, ); return pickNotes(dbNotes, { diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 3041e0ef3e33..bde3bc029a82 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -21,8 +21,7 @@ import { deriveEcdhSharedSecret, } from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; -import { Note, type NoteStatus } from '@aztec/stdlib/note'; -import { NoteDao } from '@aztec/stdlib/note'; +import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import { TxHash } from '@aztec/stdlib/tx'; @@ -82,36 +81,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return completeAddress; } - async getNotes( - contractAddress: AztecAddress, - owner: AztecAddress | undefined, - storageSlot: Fr, - status: NoteStatus, - scopes?: AztecAddress[], - ) { - const noteDaos = await this.noteDataProvider.getNotes({ - contractAddress, - owner, - storageSlot, - status, - scopes, - }); - return noteDaos.map( - ({ contractAddress, owner, storageSlot, randomness, noteNonce, note, noteHash, siloedNullifier, index }) => ({ - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - note, - noteHash, - siloedNullifier, - // PXE can use this index to get full MembershipWitness - index, - }), - ); - } - /** * Fetches a message from the db, given its key. * @param contractAddress - Address of a contract by which the message was emitted. diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 1ba60da9dc7e..05c717054185 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -198,7 +198,12 @@ export class PXE { this.privateEventDataProvider, this.log, ); - return new ContractFunctionSimulator(pxeOracleInterface, this.contractDataProvider, this.simulator); + return new ContractFunctionSimulator( + pxeOracleInterface, + this.contractDataProvider, + this.noteDataProvider, + this.simulator, + ); } #contextualizeError(err: Error, ...context: string[]): Error { 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 b0951670e56f..f9b0d064c9ff 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 @@ -14,6 +14,7 @@ import { TestDateProvider } from '@aztec/foundation/timer'; import type { KeyStore } from '@aztec/key-store'; import { AddressDataProvider, + NoteDataProvider, ORACLE_VERSION, PXEOracleInterface, enrichPublicSimulationError, @@ -92,6 +93,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl constructor( private stateMachine: TXEStateMachine, private contractDataProvider: TXEContractDataProvider, + private noteDataProvider: NoteDataProvider, private keyStore: KeyStore, private addressDataProvider: AddressDataProvider, private accountDataProvider: TXEAccountDataProvider, @@ -304,6 +306,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl taggingIndexCache, this.pxeOracleInterface, this.contractDataProvider, + this.noteDataProvider, 0, 1, undefined, // log @@ -619,6 +622,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl anchorBlockHeader, this.pxeOracleInterface, this.contractDataProvider, + this.noteDataProvider, ); const acirExecutionResult = await new WASMSimulator() .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) diff --git a/yarn-project/txe/src/txe_session.test.ts b/yarn-project/txe/src/txe_session.test.ts index 1d0dfe9f7b26..05cfb418f822 100644 --- a/yarn-project/txe/src/txe_session.test.ts +++ b/yarn-project/txe/src/txe_session.test.ts @@ -12,6 +12,7 @@ describe('TXESession.processFunction', () => { {} as any, // stateMachine {} as any, // oracleHandler {} as any, // contractDataProvider + {} as any, // noteDataProvider {} as any, // keyStore {} as any, // addressDataProvider {} as any, // accountDataProvider diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index b3554e8e5b12..11863f38baeb 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -120,6 +120,7 @@ export class TXESession implements TXESessionStateHandler { | IAvmExecutionOracle | ITxeExecutionOracle, private contractDataProvider: TXEContractDataProvider, + private noteDataProvider: NoteDataProvider, private keyStore: KeyStore, private addressDataProvider: AddressDataProvider, private accountDataProvider: TXEAccountDataProvider, @@ -168,6 +169,7 @@ export class TXESession implements TXESessionStateHandler { const topLevelOracleHandler = new TXEOracleTopLevelContext( stateMachine, contractDataProvider, + noteDataProvider, keyStore, addressDataProvider, accountDataProvider, @@ -184,6 +186,7 @@ export class TXESession implements TXESessionStateHandler { stateMachine, topLevelOracleHandler, contractDataProvider, + noteDataProvider, keyStore, addressDataProvider, accountDataProvider, @@ -250,6 +253,7 @@ export class TXESession implements TXESessionStateHandler { this.oracleHandler = new TXEOracleTopLevelContext( this.stateMachine, this.contractDataProvider, + this.noteDataProvider, this.keyStore, this.addressDataProvider, this.accountDataProvider, @@ -307,6 +311,7 @@ export class TXESession implements TXESessionStateHandler { taggingIndexCache, this.pxeOracleInterface, this.contractDataProvider, + this.noteDataProvider, ); // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary @@ -363,6 +368,7 @@ export class TXESession implements TXESessionStateHandler { anchorBlockHeader, this.pxeOracleInterface, this.contractDataProvider, + this.noteDataProvider, ); this.state = { name: 'UTILITY' }; From 2ebef273a9d856f20014c72c57985db9bcb8e974 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 08:54:11 +0000 Subject: [PATCH 005/140] extract getKeyValidationRequest from PXEOracleInterface --- .../contract_function_simulator.ts | 4 ++ .../execution_data_provider.ts | 10 +---- .../oracle/oracle_version_is_checked.test.ts | 4 ++ .../oracle/private_execution.test.ts | 44 ++++++++++--------- .../oracle/private_execution_oracle.ts | 4 ++ .../oracle/utility_execution.test.ts | 4 ++ .../oracle/utility_execution_oracle.ts | 4 +- .../pxe_oracle_interface.ts | 5 --- yarn-project/pxe/src/pxe.ts | 1 + .../oracle/txe_oracle_top_level_context.ts | 2 + yarn-project/txe/src/txe_session.ts | 2 + 11 files changed, 48 insertions(+), 36 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index e8ce5168859a..b9f879220909 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -21,6 +21,7 @@ import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fr } from '@aztec/foundation/curves/bn254'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; +import type { KeyStore } from '@aztec/key-store'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; import { protocolContractsHash } from '@aztec/protocol-contracts'; import { @@ -90,6 +91,7 @@ export class ContractFunctionSimulator { private executionDataProvider: ExecutionDataProvider, private contractDataProvider: ContractDataProvider, private noteDataProvider: NoteDataProvider, + private keyStore: KeyStore, private simulator: CircuitSimulator, ) { this.log = createLogger('simulator'); @@ -165,6 +167,7 @@ export class ContractFunctionSimulator { this.executionDataProvider, this.contractDataProvider, this.noteDataProvider, + this.keyStore, 0, // totalPublicArgsCount startSideEffectCounter, undefined, // log @@ -252,6 +255,7 @@ export class ContractFunctionSimulator { this.executionDataProvider, this.contractDataProvider, this.noteDataProvider, + this.keyStore, undefined, scopes, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 0210b6ac1ac2..d90a28fd4ee9 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -6,10 +6,10 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress } from '@aztec/stdlib/contract'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import type { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { NodeStats } from '@aztec/stdlib/tx'; + import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js'; import type { MessageLoadOracleInputs } from './oracle/message_load_oracle_inputs.js'; @@ -53,14 +53,6 @@ export interface ExecutionDataProvider { */ getCompleteAddress(account: AztecAddress): Promise; - /** - * Retrieve keys associated with a specific master public key and app address. - * @param pkMHash - The master public key hash. - * @returns A Promise that resolves to nullifier keys. - * @throws If the keys are not registered in the key store. - */ - getKeyValidationRequest(pkMHash: Fr, contractAddress: AztecAddress): Promise; - /** * Gets the index of a nullifier in the nullifier tree. * @param nullifier - The nullifier. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index b207df847828..20e92f38bdd3 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -1,4 +1,5 @@ import { Fr } from '@aztec/foundation/curves/bn254'; +import { KeyStore } from '@aztec/key-store'; import { OracleVersionCheckContractArtifact } from '@aztec/noir-test-contracts.js/OracleVersionCheck'; import { WASMSimulator } from '@aztec/simulator/client'; import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@aztec/stdlib/abi'; @@ -19,6 +20,7 @@ describe('Oracle Version Check test suite', () => { let executionDataProvider: ReturnType>; let contractDataProvider: ReturnType>; let noteDataProvider: ReturnType>; + let keyStore: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; @@ -26,6 +28,7 @@ describe('Oracle Version Check test suite', () => { executionDataProvider = mock(); contractDataProvider = mock(); noteDataProvider = mock(); + keyStore = mock(); // Mock basic oracle responses executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); @@ -43,6 +46,7 @@ describe('Oracle Version Check test suite', () => { executionDataProvider, contractDataProvider, noteDataProvider, + keyStore, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index f5d658f29bcc..d4dc3bf29e43 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -16,6 +16,7 @@ import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin'; import { EthAddress } from '@aztec/foundation/eth-address'; import { type Logger, createLogger } from '@aztec/foundation/log'; import type { FieldsOf } from '@aztec/foundation/types'; +import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb'; import { type AppendOnlyTree, Poseidon, StandardTree, newTree } from '@aztec/merkle-tree'; import { ChildContractArtifact } from '@aztec/noir-test-contracts.js/Child'; @@ -108,6 +109,7 @@ describe('Private Execution test suite', () => { let executionDataProvider: MockProxy; let contractDataProvider: MockProxy; let noteDataProvider: MockProxy; + let keyStore: MockProxy; let senderTaggingDataProvider: MockProxy; let aztecNode: MockProxy; let acirSimulator: ContractFunctionSimulator; @@ -279,6 +281,7 @@ describe('Private Execution test suite', () => { noteDataProvider = mock(); senderTaggingDataProvider = mock(); aztecNode = mock(); + keyStore = mock(); contracts = {}; // Mock the senderTaggingDataProvider getter @@ -301,27 +304,25 @@ describe('Private Execution test suite', () => { // on the input. aztecNode.getLogsByTags.mockImplementation((tags: Fr[]) => Promise.resolve(tags.map(() => []))); - executionDataProvider.getKeyValidationRequest.mockImplementation( - async (pkMHash: Fr, contractAddress: AztecAddress) => { - if (pkMHash.equals(await ownerCompleteAddress.publicKeys.masterNullifierPublicKey.hash())) { - return Promise.resolve( - new KeyValidationRequest( - ownerCompleteAddress.publicKeys.masterNullifierPublicKey, - await computeAppNullifierSecretKey(ownerNskM, contractAddress), - ), - ); - } - if (pkMHash.equals(await recipientCompleteAddress.publicKeys.masterNullifierPublicKey.hash())) { - return Promise.resolve( - new KeyValidationRequest( - recipientCompleteAddress.publicKeys.masterNullifierPublicKey, - await computeAppNullifierSecretKey(recipientNskM, contractAddress), - ), - ); - } - throw new Error(`Unknown master public key hash: ${pkMHash}`); - }, - ); + keyStore.getKeyValidationRequest.mockImplementation(async (pkMHash: Fr, contractAddress: AztecAddress) => { + if (pkMHash.equals(await ownerCompleteAddress.publicKeys.masterNullifierPublicKey.hash())) { + return Promise.resolve( + new KeyValidationRequest( + ownerCompleteAddress.publicKeys.masterNullifierPublicKey, + await computeAppNullifierSecretKey(ownerNskM, contractAddress), + ), + ); + } + if (pkMHash.equals(await recipientCompleteAddress.publicKeys.masterNullifierPublicKey.hash())) { + return Promise.resolve( + new KeyValidationRequest( + recipientCompleteAddress.publicKeys.masterNullifierPublicKey, + await computeAppNullifierSecretKey(recipientNskM, contractAddress), + ), + ); + } + throw new Error(`Unknown master public key hash: ${pkMHash}`); + }); // We call insertLeaves here with no leaves to populate empty public data tree root --> this is necessary to be // able to get ivpk_m during execution @@ -366,6 +367,7 @@ describe('Private Execution test suite', () => { executionDataProvider, contractDataProvider, noteDataProvider, + keyStore, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 1abbbdb3dc93..d5c55dd1932e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -2,6 +2,7 @@ import { MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS, PRIVATE_CONTEXT_INPUTS_LENGTH } import { Fr } from '@aztec/foundation/curves/bn254'; import { createLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; +import type { KeyStore } from '@aztec/key-store'; import { type CircuitSimulator, toACVMWitness } from '@aztec/simulator/client'; import { type FunctionAbi, @@ -84,6 +85,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP executionDataProvider: ExecutionDataProvider, contractDataProvider: ContractDataProvider, noteDataProvider: NoteDataProvider, + keyStore: KeyStore, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, log = createLogger('simulator:client_execution_context'), @@ -99,6 +101,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP executionDataProvider, contractDataProvider, noteDataProvider, + keyStore, log, scopes, ); @@ -545,6 +548,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.executionDataProvider, this.contractDataProvider, this.noteDataProvider, + this.keyStore, this.totalPublicCalldataCount, sideEffectCounter, this.log, 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 8a1e31fc41f8..2bdec6250157 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 @@ -1,5 +1,6 @@ import { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; +import type { KeyStore } from '@aztec/key-store'; import { StatefulTestContractArtifact } from '@aztec/noir-test-contracts.js/StatefulTest'; import { WASMSimulator } from '@aztec/simulator/client'; import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@aztec/stdlib/abi'; @@ -21,6 +22,7 @@ describe('Utility Execution test suite', () => { let executionDataProvider: ReturnType>; let contractDataProvider: ReturnType>; let noteDataProvider: ReturnType>; + let keyStore: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; const ownerSecretKey = Fr.fromHexString('2dcc5485a58316776299be08c78fa3788a1a7961ae30dc747fb1be17692a8d32'); @@ -33,10 +35,12 @@ describe('Utility Execution test suite', () => { executionDataProvider = mock(); contractDataProvider = mock(); noteDataProvider = mock(); + keyStore = mock(); acirSimulator = new ContractFunctionSimulator( executionDataProvider, contractDataProvider, noteDataProvider, + keyStore, simulator, ); 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 d61232078e76..a2f17fd68089 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 @@ -3,6 +3,7 @@ import { Aes128 } from '@aztec/foundation/crypto/aes128'; import { Fr } from '@aztec/foundation/curves/bn254'; import { Point } from '@aztec/foundation/curves/grumpkin'; import { LogLevels, applyStringFormatting, createLogger } from '@aztec/foundation/log'; +import type { KeyStore } from '@aztec/key-store'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; @@ -37,6 +38,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly executionDataProvider: ExecutionDataProvider, protected readonly contractDataProvider: ContractDataProvider, protected readonly noteDataProvider: NoteDataProvider, + protected readonly keyStore: KeyStore, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], ) {} @@ -66,7 +68,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @throws If the keys are not registered in the key store. */ public utilityGetKeyValidationRequest(pkMHash: Fr): Promise { - return this.executionDataProvider.getKeyValidationRequest(pkMHash, this.contractAddress); + return this.keyStore.getKeyValidationRequest(pkMHash, this.contractAddress); } /** diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index df2722869352..5554e227f6c6 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -9,7 +9,6 @@ import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/client'; -import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import { computeAddressSecret } from '@aztec/stdlib/keys'; import { PendingTaggedLog, @@ -71,10 +70,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { private log = createLogger('pxe:pxe_oracle_interface'), ) {} - getKeyValidationRequest(pkMHash: Fr, contractAddress: AztecAddress): Promise { - return this.keyStore.getKeyValidationRequest(pkMHash, contractAddress); - } - async getCompleteAddress(account: AztecAddress): Promise { const completeAddress = await this.addressDataProvider.getCompleteAddress(account); if (!completeAddress) { diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 3677a73c9167..5be81a3dbb9b 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -207,6 +207,7 @@ export class PXE { pxeOracleInterface, this.contractDataProvider, this.noteDataProvider, + this.keyStore, this.simulator, ); } 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 f9b0d064c9ff..289da7872260 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 @@ -307,6 +307,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.pxeOracleInterface, this.contractDataProvider, this.noteDataProvider, + this.keyStore, 0, 1, undefined, // log @@ -623,6 +624,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.pxeOracleInterface, this.contractDataProvider, this.noteDataProvider, + this.keyStore, ); const acirExecutionResult = await new WASMSimulator() .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 83bc70283ced..9a22c7a30150 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -315,6 +315,7 @@ export class TXESession implements TXESessionStateHandler { this.pxeOracleInterface, this.contractDataProvider, this.noteDataProvider, + this.keyStore, ); // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary @@ -372,6 +373,7 @@ export class TXESession implements TXESessionStateHandler { this.pxeOracleInterface, this.contractDataProvider, this.noteDataProvider, + this.keyStore, ); this.state = { name: 'UTILITY' }; From 61dec4171fdf9dccc6ed9053eb4d56c49fd3a41d Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 09:09:54 +0000 Subject: [PATCH 006/140] extract getCompleteAddress from PXEOracleInterface --- .../contract_function_simulator.ts | 5 ++++- .../execution_data_provider.ts | 9 --------- .../oracle/common.ts | 18 ++++++++++++++++-- .../oracle/oracle_version_is_checked.test.ts | 5 ++++- .../oracle/private_execution.test.ts | 9 ++++++--- .../oracle/private_execution_oracle.ts | 5 ++++- .../oracle/utility_execution.test.ts | 7 +++++-- .../oracle/utility_execution_oracle.ts | 7 ++++--- .../pxe_oracle_interface.ts | 19 ++++--------------- yarn-project/pxe/src/pxe.ts | 1 + .../oracle/txe_oracle_top_level_context.ts | 2 ++ yarn-project/txe/src/txe_session.ts | 2 ++ 12 files changed, 52 insertions(+), 37 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index b9f879220909..895ace91a4c1 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -70,7 +70,7 @@ import { getFinalMinRevertibleSideEffectCounter, } from '@aztec/stdlib/tx'; -import type { ContractDataProvider, NoteDataProvider } from '../storage/index.js'; +import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../storage/index.js'; import type { ExecutionDataProvider } from './execution_data_provider.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; @@ -92,6 +92,7 @@ export class ContractFunctionSimulator { private contractDataProvider: ContractDataProvider, private noteDataProvider: NoteDataProvider, private keyStore: KeyStore, + private addressDataProvider: AddressDataProvider, private simulator: CircuitSimulator, ) { this.log = createLogger('simulator'); @@ -168,6 +169,7 @@ export class ContractFunctionSimulator { this.contractDataProvider, this.noteDataProvider, this.keyStore, + this.addressDataProvider, 0, // totalPublicArgsCount startSideEffectCounter, undefined, // log @@ -256,6 +258,7 @@ export class ContractFunctionSimulator { this.contractDataProvider, this.noteDataProvider, this.keyStore, + this.addressDataProvider, undefined, scopes, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index d90a28fd4ee9..6ada42e5c078 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -4,7 +4,6 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2Block } from '@aztec/stdlib/block'; -import type { CompleteAddress } from '@aztec/stdlib/contract'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import type { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; @@ -45,14 +44,6 @@ export type ExecutionStats = { * The interface for the data layer required to perform private and utility execution. */ export interface ExecutionDataProvider { - /** - * Retrieve the complete address associated to a given address. - * @param account - The account address. - * @returns A complete address associated with the input address. - * @throws An error if the account is not registered in the database. - */ - getCompleteAddress(account: AztecAddress): Promise; - /** * Gets the index of a nullifier in the nullifier tree. * @param nullifier - The nullifier. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index f40a045c8da2..e82728182db0 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,10 +1,10 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { ContractInstance } from '@aztec/stdlib/contract'; +import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import type { NoteStatus } from '@aztec/stdlib/note'; -import type { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; // TODO: this might not be the final home for these functions, // it's just a way of starting to dissolve PXEOracleInterface @@ -65,3 +65,17 @@ export async function getNotes( }), ); } + +export async function getCompleteAddress( + account: AztecAddress, + addressDataProvider: AddressDataProvider, +): Promise { + const completeAddress = await addressDataProvider.getCompleteAddress(account); + if (!completeAddress) { + throw new Error( + `No public key registered for address ${account}. + Register it by calling pxe.addAccount(...).\nSee docs for context: https://docs.aztec.network/developers/resources/debugging/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount`, + ); + } + return completeAddress; +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 20e92f38bdd3..5131a7cc5b61 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -10,7 +10,7 @@ import { BlockHeader, HashedValues, TxContext, TxExecutionRequest } from '@aztec import { mock } from 'jest-mock-extended'; -import { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -21,6 +21,7 @@ describe('Oracle Version Check test suite', () => { let contractDataProvider: ReturnType>; let noteDataProvider: ReturnType>; let keyStore: ReturnType>; + let addressDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; @@ -29,6 +30,7 @@ describe('Oracle Version Check test suite', () => { contractDataProvider = mock(); noteDataProvider = mock(); keyStore = mock(); + addressDataProvider = mock(); // Mock basic oracle responses executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); @@ -47,6 +49,7 @@ describe('Oracle Version Check test suite', () => { contractDataProvider, noteDataProvider, keyStore, + addressDataProvider, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index d4dc3bf29e43..59a133b32c81 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -64,7 +64,7 @@ import { jest } from '@jest/globals'; import { Matcher, type MatcherCreator, type MockProxy, mock } from 'jest-mock-extended'; import { toFunctionSelector } from 'viem'; -import { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -109,6 +109,7 @@ describe('Private Execution test suite', () => { let executionDataProvider: MockProxy; let contractDataProvider: MockProxy; let noteDataProvider: MockProxy; + let addressDataProvider: MockProxy; let keyStore: MockProxy; let senderTaggingDataProvider: MockProxy; let aztecNode: MockProxy; @@ -279,6 +280,7 @@ describe('Private Execution test suite', () => { executionDataProvider = mock(); contractDataProvider = mock(); noteDataProvider = mock(); + addressDataProvider = mock(); senderTaggingDataProvider = mock(); aztecNode = mock(); keyStore = mock(); @@ -328,7 +330,7 @@ describe('Private Execution test suite', () => { // able to get ivpk_m during execution await insertLeaves([], 'publicData'); - executionDataProvider.getCompleteAddress.mockImplementation((address: AztecAddress) => { + addressDataProvider.getCompleteAddress.mockImplementation((address: AztecAddress) => { if (address.equals(owner)) { return Promise.resolve(ownerCompleteAddress); } @@ -368,6 +370,7 @@ describe('Private Execution test suite', () => { contractDataProvider, noteDataProvider, keyStore, + addressDataProvider, simulator, ); }); @@ -1115,7 +1118,7 @@ describe('Private Execution test suite', () => { const args = [completeAddress.address]; const pubKey = completeAddress.publicKeys.masterIncomingViewingPublicKey; - executionDataProvider.getCompleteAddress.mockResolvedValue(completeAddress); + addressDataProvider.getCompleteAddress.mockResolvedValue(completeAddress); const { entrypoint: result } = await runSimulator({ artifact: TestContractArtifact, functionName: 'get_master_incoming_viewing_public_key', diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index d5c55dd1932e..77e83e1b8151 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -27,7 +27,7 @@ import { type TxContext, } from '@aztec/stdlib/tx'; -import type { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import { syncSenderTaggingIndexes } from '../../tagging/sync/sync_sender_tagging_indexes.js'; import { Tag } from '../../tagging/tag.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -86,6 +86,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP contractDataProvider: ContractDataProvider, noteDataProvider: NoteDataProvider, keyStore: KeyStore, + addressDataProvider: AddressDataProvider, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, log = createLogger('simulator:client_execution_context'), @@ -102,6 +103,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP contractDataProvider, noteDataProvider, keyStore, + addressDataProvider, log, scopes, ); @@ -549,6 +551,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.contractDataProvider, this.noteDataProvider, this.keyStore, + this.addressDataProvider, this.totalPublicCalldataCount, sideEffectCounter, this.log, 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 2bdec6250157..18dfb77a3a7c 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 @@ -12,7 +12,7 @@ import { BlockHeader, TxHash } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; -import { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -23,6 +23,7 @@ describe('Utility Execution test suite', () => { let contractDataProvider: ReturnType>; let noteDataProvider: ReturnType>; let keyStore: ReturnType>; + let addressDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; const ownerSecretKey = Fr.fromHexString('2dcc5485a58316776299be08c78fa3788a1a7961ae30dc747fb1be17692a8d32'); @@ -36,18 +37,20 @@ describe('Utility Execution test suite', () => { contractDataProvider = mock(); noteDataProvider = mock(); keyStore = mock(); + addressDataProvider = mock(); acirSimulator = new ContractFunctionSimulator( executionDataProvider, contractDataProvider, noteDataProvider, keyStore, + addressDataProvider, simulator, ); const ownerCompleteAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(ownerSecretKey, Fr.random()); owner = ownerCompleteAddress.address; - executionDataProvider.getCompleteAddress.mockImplementation((account: AztecAddress) => { + addressDataProvider.getCompleteAddress.mockImplementation((account: AztecAddress) => { if (account.equals(owner)) { return Promise.resolve(ownerCompleteAddress); } 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 a2f17fd68089..3b4fb9664b1e 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 @@ -13,11 +13,11 @@ import type { NoteStatus } from '@aztec/stdlib/note'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; -import type { ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; -import { getContractInstance, getNotes } from './common.js'; +import { getCompleteAddress, getContractInstance, getNotes } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; /** @@ -39,6 +39,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly contractDataProvider: ContractDataProvider, protected readonly noteDataProvider: NoteDataProvider, protected readonly keyStore: KeyStore, + protected readonly addressDataProvider: AddressDataProvider, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], ) {} @@ -144,7 +145,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @throws An error if the account is not registered in the database. */ public utilityGetPublicKeysAndPartialAddress(account: AztecAddress): Promise { - return this.executionDataProvider.getCompleteAddress(account); + return getCompleteAddress(account, this.addressDataProvider); } /** diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 5554e227f6c6..3b6eca9309fd 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -6,7 +6,6 @@ import type { KeyStore } from '@aztec/key-store'; import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; -import type { CompleteAddress } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/client'; import { computeAddressSecret } from '@aztec/stdlib/keys'; @@ -46,6 +45,7 @@ import { EventValidationRequest } from './noir-structs/event_validation_request. import { LogRetrievalRequest } from './noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from './noir-structs/log_retrieval_response.js'; import { NoteValidationRequest } from './noir-structs/note_validation_request.js'; +import { getCompleteAddress } from './oracle/common.js'; import type { ProxiedNode } from './proxied_node.js'; /** @@ -70,17 +70,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { private log = createLogger('pxe:pxe_oracle_interface'), ) {} - async getCompleteAddress(account: AztecAddress): Promise { - const completeAddress = await this.addressDataProvider.getCompleteAddress(account); - if (!completeAddress) { - throw new Error( - `No public key registered for address ${account}. - Register it by calling pxe.addAccount(...).\nSee docs for context: https://docs.aztec.network/developers/resources/debugging/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount`, - ); - } - return completeAddress; - } - /** * Fetches a message from the db, given its key. * @param contractAddress - Address of a contract by which the message was emitted. @@ -209,7 +198,7 @@ export class PXEOracleInterface implements ExecutionDataProvider { sender: AztecAddress, recipient: AztecAddress, ) { - const senderCompleteAddress = await this.getCompleteAddress(sender); + const senderCompleteAddress = await getCompleteAddress(sender, this.addressDataProvider); const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); return DirectionalAppTaggingSecret.compute( senderCompleteAddress, @@ -237,7 +226,7 @@ export class PXEOracleInterface implements ExecutionDataProvider { contractAddress: AztecAddress, recipient: AztecAddress, ): Promise<{ secret: DirectionalAppTaggingSecret; index: number | undefined }[]> { - const recipientCompleteAddress = await this.getCompleteAddress(recipient); + const recipientCompleteAddress = await getCompleteAddress(recipient, this.addressDataProvider); const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); // We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves @@ -870,7 +859,7 @@ export class PXEOracleInterface implements ExecutionDataProvider { async getSharedSecret(address: AztecAddress, ephPk: Point): Promise { // TODO(#12656): return an app-siloed secret - const recipientCompleteAddress = await this.getCompleteAddress(address); + const recipientCompleteAddress = await getCompleteAddress(address, this.addressDataProvider); const ivskM = await this.keyStore.getMasterSecretKey( recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey, ); diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 5be81a3dbb9b..bf15ae167f30 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -208,6 +208,7 @@ export class PXE { this.contractDataProvider, this.noteDataProvider, this.keyStore, + this.addressDataProvider, this.simulator, ); } 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 289da7872260..635fdee83484 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 @@ -308,6 +308,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.contractDataProvider, this.noteDataProvider, this.keyStore, + this.addressDataProvider, 0, 1, undefined, // log @@ -625,6 +626,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.contractDataProvider, this.noteDataProvider, this.keyStore, + this.addressDataProvider, ); const acirExecutionResult = await new WASMSimulator() .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 9a22c7a30150..e30a85b42e39 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -316,6 +316,7 @@ export class TXESession implements TXESessionStateHandler { this.contractDataProvider, this.noteDataProvider, this.keyStore, + this.addressDataProvider, ); // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary @@ -374,6 +375,7 @@ export class TXESession implements TXESessionStateHandler { this.contractDataProvider, this.noteDataProvider, this.keyStore, + this.addressDataProvider, ); this.state = { name: 'UTILITY' }; From d1beacad6fe71371c81860cdd670cef0fc4bccf3 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 09:28:39 +0000 Subject: [PATCH 007/140] calculateDirectionalAppTaggingSecret --- .../execution_data_provider.ts | 14 -------------- .../contract_function_simulator/oracle/common.ts | 14 ++++++++++++++ .../oracle/private_execution.test.ts | 5 ----- .../oracle/private_execution_oracle.ts | 6 ++++-- .../pxe_oracle_interface.ts | 16 ---------------- 5 files changed, 18 insertions(+), 37 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 6ada42e5c078..013cdbebff93 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -5,7 +5,6 @@ import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2Block } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import type { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { NodeStats } from '@aztec/stdlib/tx'; @@ -140,19 +139,6 @@ export interface ExecutionDataProvider { */ assertCompatibleOracleVersion(version: number): void; - /** - * Calculates the directional app tagging secret for a given contract, sender and recipient. - * @param contractAddress - The contract address to silo the secret for - * @param sender - The address sending the note - * @param recipient - The address receiving the note - * @returns The directional app tagging secret - */ - calculateDirectionalAppTaggingSecret( - contractAddress: AztecAddress, - sender: AztecAddress, - recipient: AztecAddress, - ): Promise; - /** * Synchronizes the private logs tagged with scoped addresses and all the senders in the address book. Stores the found * logs in CapsuleArray ready for a later retrieval in Aztec.nr. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index e82728182db0..605c47f7993e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,7 +1,9 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; +import type { KeyStore } from '@aztec/key-store'; import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; +import { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; import type { NoteStatus } from '@aztec/stdlib/note'; import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; @@ -79,3 +81,15 @@ export async function getCompleteAddress( } return completeAddress; } + +export async function calculateDirectionalAppTaggingSecret( + contractAddress: AztecAddress, + sender: AztecAddress, + recipient: AztecAddress, + addressDataProvider: AddressDataProvider, + keyStore: KeyStore, +) { + const senderCompleteAddress = await getCompleteAddress(sender, addressDataProvider); + const senderIvsk = await keyStore.getMasterIncomingViewingSecretKey(sender); + return DirectionalAppTaggingSecret.compute(senderCompleteAddress, senderIvsk, recipient, contractAddress, recipient); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 59a133b32c81..b41782f040c0 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -45,7 +45,6 @@ import { computeNoteHashNonce, computeSecretHash, computeUniqueNoteHash, siloNot import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { KeyValidationRequest } from '@aztec/stdlib/kernel'; import { computeAppNullifierSecretKey, deriveKeys } from '@aztec/stdlib/keys'; -import { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; import { L1Actor, L1ToL2Message, L2Actor } from '@aztec/stdlib/messaging'; import { Note, NoteDao } from '@aztec/stdlib/note'; import { makeBlockHeader } from '@aztec/stdlib/testing'; @@ -353,10 +352,6 @@ describe('Private Execution test suite', () => { }); executionDataProvider.syncTaggedLogs.mockImplementation((_, __) => Promise.resolve()); - // Provide tagging-related mocks expected by private log emission - executionDataProvider.calculateDirectionalAppTaggingSecret.mockImplementation((_contract, _sender, _recipient) => { - return Promise.resolve(DirectionalAppTaggingSecret.fromString('0x1')); - }); executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); executionDataProvider.getPublicStorageAt.mockImplementation( diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 77e83e1b8151..15ee46f6bbc0 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -35,7 +35,7 @@ import type { ExecutionNoteCache } from '../execution_note_cache.js'; import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import type { HashedValuesCache } from '../hashed_values_cache.js'; import { pickNotes } from '../pick_notes.js'; -import { getFunctionArtifact, getNotes } from './common.js'; +import { calculateDirectionalAppTaggingSecret, getFunctionArtifact, getNotes } from './common.js'; import type { IPrivateExecutionOracle, NoteData } from './interfaces.js'; import { executePrivateFunction, verifyCurrentClassId } from './private_execution.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; @@ -222,10 +222,12 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP * @returns An app tag to be used in a log. */ public async privateGetNextAppTagAsSender(sender: AztecAddress, recipient: AztecAddress): Promise { - const secret = await this.executionDataProvider.calculateDirectionalAppTaggingSecret( + const secret = await calculateDirectionalAppTaggingSecret( this.contractAddress, sender, recipient, + this.addressDataProvider, + this.keyStore, ); const index = await this.#getIndexToUseForSecret(secret); diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 3b6eca9309fd..a98a8fb41351 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -193,22 +193,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return this.recipientTaggingDataProvider.getSenderAddresses(); } - public async calculateDirectionalAppTaggingSecret( - contractAddress: AztecAddress, - sender: AztecAddress, - recipient: AztecAddress, - ) { - const senderCompleteAddress = await getCompleteAddress(sender, this.addressDataProvider); - const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); - return DirectionalAppTaggingSecret.compute( - senderCompleteAddress, - senderIvsk, - recipient, - contractAddress, - recipient, - ); - } - /** * Returns the last used tagging indexes along with the directional app tagging secrets for a given recipient and all * the senders in the address book. From 13118f96a46c4d391633a5d50746323aadd7d1f9 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 09:37:22 +0000 Subject: [PATCH 008/140] getSharedSecret --- .../execution_data_provider.ts | 9 --------- .../oracle/common.ts | 17 ++++++++++++++++- .../oracle/utility_execution_oracle.ts | 4 ++-- .../pxe_oracle_interface.ts | 13 ------------- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 013cdbebff93..3d434df3b522 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -1,7 +1,6 @@ import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { BlockNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; -import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2Block } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; @@ -220,14 +219,6 @@ export interface ExecutionDataProvider { */ copyCapsule(contractAddress: AztecAddress, srcSlot: Fr, dstSlot: Fr, numEntries: number): Promise; - /** - * Retrieves the shared secret for a given address and ephemeral public key. - * @param address - The address to get the secret for. - * @param ephPk - The ephemeral public key to get the secret for. - * @returns The secret for the given address. - */ - getSharedSecret(address: AztecAddress, ephPk: Point): Promise; - /** * Returns the execution statistics collected during the simulator run. * @returns The execution statistics. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 605c47f7993e..7c15316d3bf8 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,9 +1,11 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; +import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { KeyStore } from '@aztec/key-store'; import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; -import { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; +import { computeAddressSecret } from '@aztec/stdlib/keys'; +import { DirectionalAppTaggingSecret, deriveEcdhSharedSecret } from '@aztec/stdlib/logs'; import type { NoteStatus } from '@aztec/stdlib/note'; import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; @@ -93,3 +95,16 @@ export async function calculateDirectionalAppTaggingSecret( const senderIvsk = await keyStore.getMasterIncomingViewingSecretKey(sender); return DirectionalAppTaggingSecret.compute(senderCompleteAddress, senderIvsk, recipient, contractAddress, recipient); } + +export async function getSharedSecret( + address: AztecAddress, + ephPk: Point, + addressDataProvider: AddressDataProvider, + keyStore: KeyStore, +): Promise { + // TODO(#12656): return an app-siloed secret + const recipientCompleteAddress = await getCompleteAddress(address, addressDataProvider); + const ivskM = await keyStore.getMasterSecretKey(recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey); + const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM); + return deriveEcdhSharedSecret(addressSecret, ephPk); +} 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 3b4fb9664b1e..028f40758f4c 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 @@ -17,7 +17,7 @@ import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; -import { getCompleteAddress, getContractInstance, getNotes } from './common.js'; +import { getCompleteAddress, getContractInstance, getNotes, getSharedSecret } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; /** @@ -374,6 +374,6 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } public utilityGetSharedSecret(address: AztecAddress, ephPk: Point): Promise { - return this.executionDataProvider.getSharedSecret(address, ephPk); + return getSharedSecret(address, ephPk, this.addressDataProvider, this.keyStore); } } diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index a98a8fb41351..5356057f9b87 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -1,6 +1,5 @@ import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import { Fr } from '@aztec/foundation/curves/bn254'; -import { Point } from '@aztec/foundation/curves/grumpkin'; import { createLogger } from '@aztec/foundation/log'; import type { KeyStore } from '@aztec/key-store'; import { EventSelector } from '@aztec/stdlib/abi'; @@ -8,14 +7,12 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/client'; -import { computeAddressSecret } from '@aztec/stdlib/keys'; import { PendingTaggedLog, PrivateLogWithTxData, PublicLog, PublicLogWithTxData, TxScopedL2Log, - deriveEcdhSharedSecret, } from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import { Note, NoteDao } from '@aztec/stdlib/note'; @@ -841,16 +838,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return this.capsuleDataProvider.copyCapsule(contractAddress, srcSlot, dstSlot, numEntries); } - async getSharedSecret(address: AztecAddress, ephPk: Point): Promise { - // TODO(#12656): return an app-siloed secret - const recipientCompleteAddress = await getCompleteAddress(address, this.addressDataProvider); - const ivskM = await this.keyStore.getMasterSecretKey( - recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey, - ); - const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM); - return deriveEcdhSharedSecret(addressSecret, ephPk); - } - // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. async #getPrivateLogsByTags(tags: Fr[]): Promise { From cad9544a318a942222fbe269913e118b331999b6 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 10:07:59 +0000 Subject: [PATCH 009/140] getL1ToL2MembershipWitness --- .../contract_function_simulator.ts | 4 +++ .../execution_data_provider.ts | 16 ---------- .../oracle/common.ts | 29 +++++++++++++++++++ .../oracle/oracle_version_is_checked.test.ts | 4 +++ .../oracle/private_execution.test.ts | 11 ++++--- .../oracle/private_execution_oracle.ts | 4 +++ .../oracle/utility_execution.test.ts | 4 +++ .../oracle/utility_execution_oracle.ts | 12 ++++++-- .../pxe_oracle_interface.ts | 27 ----------------- yarn-project/pxe/src/pxe.ts | 1 + .../oracle/txe_oracle_top_level_context.ts | 2 ++ yarn-project/txe/src/txe_session.ts | 2 ++ 12 files changed, 67 insertions(+), 49 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 895ace91a4c1..13033631f237 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -45,6 +45,7 @@ import { siloNoteHash, siloNullifier, } from '@aztec/stdlib/hash'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { PartialPrivateTailPublicInputsForPublic, PartialPrivateTailPublicInputsForRollup, @@ -93,6 +94,7 @@ export class ContractFunctionSimulator { private noteDataProvider: NoteDataProvider, private keyStore: KeyStore, private addressDataProvider: AddressDataProvider, + private aztecNode: AztecNode, private simulator: CircuitSimulator, ) { this.log = createLogger('simulator'); @@ -170,6 +172,7 @@ export class ContractFunctionSimulator { this.noteDataProvider, this.keyStore, this.addressDataProvider, + this.aztecNode, 0, // totalPublicArgsCount startSideEffectCounter, undefined, // log @@ -259,6 +262,7 @@ export class ContractFunctionSimulator { this.noteDataProvider, this.keyStore, this.addressDataProvider, + this.aztecNode, undefined, scopes, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 3d434df3b522..44d10ef2be65 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -1,4 +1,3 @@ -import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { BlockNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; @@ -8,7 +7,6 @@ import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } import type { NodeStats } from '@aztec/stdlib/tx'; import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js'; -import type { MessageLoadOracleInputs } from './oracle/message_load_oracle_inputs.js'; /** * Error thrown when a contract is not found in the database. @@ -56,20 +54,6 @@ export interface ExecutionDataProvider { */ getNullifierMembershipWitnessAtLatestBlock(nullifier: Fr): Promise; - /** - * Fetches a message from the db, given its key. - * @param contractAddress - Address of a contract by which the message was emitted. - * @param messageHash - Hash of the message. - * @param secret - Secret used to compute a nullifier. - * @dev Contract address and secret are only used to compute the nullifier to get non-nullified messages - * @returns The l1 to l2 membership witness (index of message in the tree and sibling path). - */ - getL1ToL2MembershipWitness( - contractAddress: AztecAddress, - messageHash: Fr, - secret: Fr, - ): Promise>; - /** * Fetches the index and sibling path of a leaf at a given block from a given tree. * @param blockNumber - The block number at which to get the membership witness. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 7c15316d3bf8..98b085283a56 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,14 +1,18 @@ +import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { KeyStore } from '@aztec/key-store'; import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { computeAddressSecret } from '@aztec/stdlib/keys'; import { DirectionalAppTaggingSecret, deriveEcdhSharedSecret } from '@aztec/stdlib/logs'; +import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; // TODO: this might not be the final home for these functions, // it's just a way of starting to dissolve PXEOracleInterface @@ -108,3 +112,28 @@ export async function getSharedSecret( const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM); return deriveEcdhSharedSecret(addressSecret, ephPk); } + +/** + * Fetches a message from the db, given its key. + * @param contractAddress - Address of a contract by which the message was emitted. + * @param messageHash - Hash of the message. + * @param secret - Secret used to compute a nullifier. + * @dev Contract address and secret are only used to compute the nullifier to get non-nullified messages + * @returns The l1 to l2 membership witness (index of message in the tree and sibling path). + */ +export async function getL1ToL2MembershipWitness( + contractAddress: AztecAddress, + messageHash: Fr, + secret: Fr, + aztecNode: AztecNode, +): Promise> { + const [messageIndex, siblingPath] = await getNonNullifiedL1ToL2MessageWitness( + aztecNode, + contractAddress, + messageHash, + secret, + ); + + // Assuming messageIndex is what you intended to use for the index in MessageLoadOracleInputs + return new MessageLoadOracleInputs(messageIndex, siblingPath); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 5131a7cc5b61..3ef9528483a2 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -6,6 +6,7 @@ import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract'; import { GasFees, GasSettings } from '@aztec/stdlib/gas'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { BlockHeader, HashedValues, TxContext, TxExecutionRequest } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; @@ -22,6 +23,7 @@ describe('Oracle Version Check test suite', () => { let noteDataProvider: ReturnType>; let keyStore: ReturnType>; let addressDataProvider: ReturnType>; + let aztecNode: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; @@ -31,6 +33,7 @@ describe('Oracle Version Check test suite', () => { noteDataProvider = mock(); keyStore = mock(); addressDataProvider = mock(); + aztecNode = mock(); // Mock basic oracle responses executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); @@ -50,6 +53,7 @@ describe('Oracle Version Check test suite', () => { noteDataProvider, keyStore, addressDataProvider, + aztecNode, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index b41782f040c0..ca685553dc05 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -33,7 +33,7 @@ import { getFunctionArtifactByName, } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { type BlockParameter, L2BlockHash } from '@aztec/stdlib/block'; +import { type BlockParameter, L2Block, L2BlockHash, wrapDataInBlock } from '@aztec/stdlib/block'; import { CompleteAddress, type ContractInstanceWithAddress, @@ -67,7 +67,6 @@ import { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../ import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; -import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; jest.setTimeout(60_000); @@ -366,6 +365,7 @@ describe('Private Execution test suite', () => { noteDataProvider, keyStore, addressDataProvider, + aztecNode, simulator, ); }); @@ -662,8 +662,11 @@ describe('Private Execution test suite', () => { const mockOracles = async () => { const tree = await insertLeaves([preimage.hash()], 'l1ToL2Messages'); - executionDataProvider.getL1ToL2MembershipWitness.mockImplementation(async () => { - return Promise.resolve(new MessageLoadOracleInputs(0n, await tree.getSiblingPath(0n, true))); + aztecNode.getL1ToL2MessageMembershipWitness.mockImplementation(async () => { + return Promise.resolve([0n, await tree.getSiblingPath(0n, true)]); + }); + aztecNode.findLeavesIndexes.mockImplementation(async () => { + return [await wrapDataInBlock(0n, await L2Block.random(BlockNumber(Fr.random().toNumber())))]; }); }; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 15ee46f6bbc0..5f8c3d1d2509 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -14,6 +14,7 @@ import { import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; +import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { PrivateContextInputs } from '@aztec/stdlib/kernel'; import type { ContractClassLog, DirectionalAppTaggingSecret, PreTag } from '@aztec/stdlib/logs'; import { Note, type NoteStatus } from '@aztec/stdlib/note'; @@ -87,6 +88,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP noteDataProvider: NoteDataProvider, keyStore: KeyStore, addressDataProvider: AddressDataProvider, + aztecNode: AztecNode, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, log = createLogger('simulator:client_execution_context'), @@ -104,6 +106,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP noteDataProvider, keyStore, addressDataProvider, + aztecNode, log, scopes, ); @@ -554,6 +557,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.noteDataProvider, this.keyStore, this.addressDataProvider, + this.aztecNode, this.totalPublicCalldataCount, sideEffectCounter, this.log, 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 18dfb77a3a7c..a2591735a9c3 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 @@ -7,6 +7,7 @@ import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress, type ContractInstanceWithAddress } from '@aztec/stdlib/contract'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { Note, NoteDao } from '@aztec/stdlib/note'; import { BlockHeader, TxHash } from '@aztec/stdlib/tx'; @@ -24,6 +25,7 @@ describe('Utility Execution test suite', () => { let noteDataProvider: ReturnType>; let keyStore: ReturnType>; let addressDataProvider: ReturnType>; + let aztecNode: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; const ownerSecretKey = Fr.fromHexString('2dcc5485a58316776299be08c78fa3788a1a7961ae30dc747fb1be17692a8d32'); @@ -38,12 +40,14 @@ describe('Utility Execution test suite', () => { noteDataProvider = mock(); keyStore = mock(); addressDataProvider = mock(); + aztecNode = mock(); acirSimulator = new ContractFunctionSimulator( executionDataProvider, contractDataProvider, noteDataProvider, keyStore, addressDataProvider, + aztecNode, simulator, ); 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 028f40758f4c..a6930e92d342 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 @@ -8,6 +8,7 @@ import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import { siloNullifier } from '@aztec/stdlib/hash'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import type { NoteStatus } from '@aztec/stdlib/note'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; @@ -17,7 +18,13 @@ import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; -import { getCompleteAddress, getContractInstance, getNotes, getSharedSecret } from './common.js'; +import { + getCompleteAddress, + getContractInstance, + getL1ToL2MembershipWitness, + getNotes, + getSharedSecret, +} from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; /** @@ -40,6 +47,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly noteDataProvider: NoteDataProvider, protected readonly keyStore: KeyStore, protected readonly addressDataProvider: AddressDataProvider, + protected readonly aztecNode: AztecNode, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], ) {} @@ -249,7 +257,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns The l1 to l2 membership witness (index of message in the tree and sibling path). */ public async utilityGetL1ToL2MembershipWitness(contractAddress: AztecAddress, messageHash: Fr, secret: Fr) { - return await this.executionDataProvider.getL1ToL2MembershipWitness(contractAddress, messageHash, secret); + return await getL1ToL2MembershipWitness(contractAddress, messageHash, secret, this.aztecNode); } /** diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 5356057f9b87..56266986fa2e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -1,4 +1,3 @@ -import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import { Fr } from '@aztec/foundation/curves/bn254'; import { createLogger } from '@aztec/foundation/log'; import type { KeyStore } from '@aztec/key-store'; @@ -14,13 +13,11 @@ import { PublicLogWithTxData, TxScopedL2Log, } from '@aztec/stdlib/logs'; -import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import { TxHash } from '@aztec/stdlib/tx'; import type { ExecutionDataProvider, ExecutionStats } from '../contract_function_simulator/execution_data_provider.js'; -import { MessageLoadOracleInputs } from '../contract_function_simulator/oracle/message_load_oracle_inputs.js'; import { ORACLE_VERSION } from '../oracle_version.js'; import type { AddressDataProvider } from '../storage/address_data_provider/address_data_provider.js'; import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; @@ -67,30 +64,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { private log = createLogger('pxe:pxe_oracle_interface'), ) {} - /** - * Fetches a message from the db, given its key. - * @param contractAddress - Address of a contract by which the message was emitted. - * @param messageHash - Hash of the message. - * @param secret - Secret used to compute a nullifier. - * @dev Contract address and secret are only used to compute the nullifier to get non-nullified messages - * @returns The l1 to l2 membership witness (index of message in the tree and sibling path). - */ - async getL1ToL2MembershipWitness( - contractAddress: AztecAddress, - messageHash: Fr, - secret: Fr, - ): Promise> { - const [messageIndex, siblingPath] = await getNonNullifiedL1ToL2MessageWitness( - this.aztecNode, - contractAddress, - messageHash, - secret, - ); - - // Assuming messageIndex is what you intended to use for the index in MessageLoadOracleInputs - return new MessageLoadOracleInputs(messageIndex, siblingPath); - } - async getNullifierIndex(nullifier: Fr) { return await this.#findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier); } diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index bf15ae167f30..7b642de0fd92 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -209,6 +209,7 @@ export class PXE { this.noteDataProvider, this.keyStore, this.addressDataProvider, + this.node, this.simulator, ); } 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 635fdee83484..a5bede299e77 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 @@ -309,6 +309,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.noteDataProvider, this.keyStore, this.addressDataProvider, + this.stateMachine.node, 0, 1, undefined, // log @@ -627,6 +628,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.noteDataProvider, this.keyStore, this.addressDataProvider, + this.stateMachine.node, ); const acirExecutionResult = await new WASMSimulator() .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index e30a85b42e39..17d7c4139ccc 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -317,6 +317,7 @@ export class TXESession implements TXESessionStateHandler { this.noteDataProvider, this.keyStore, this.addressDataProvider, + this.stateMachine.node, ); // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary @@ -376,6 +377,7 @@ export class TXESession implements TXESessionStateHandler { this.noteDataProvider, this.keyStore, this.addressDataProvider, + this.stateMachine.node, ); this.state = { name: 'UTILITY' }; From 37ea1da2214153a5504be8ae4748addf6e6b7c70 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 12:25:28 +0000 Subject: [PATCH 010/140] fix regression --- yarn-project/pxe/src/pxe.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 7b642de0fd92..fb32e84f5b8e 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -190,10 +190,15 @@ export class PXE { // Internal methods #getSimulatorForTx(overrides?: { contracts?: ContractOverrides }) { + const proxyContractDataProvider = ProxiedContractDataProviderFactory.create( + this.contractDataProvider, + overrides?.contracts, + ); + const pxeOracleInterface = new PXEOracleInterface( ProxiedNodeFactory.create(this.node), this.keyStore, - ProxiedContractDataProviderFactory.create(this.contractDataProvider, overrides?.contracts), + proxyContractDataProvider, this.noteDataProvider, this.capsuleDataProvider, this.anchorBlockDataProvider, @@ -205,7 +210,7 @@ export class PXE { ); return new ContractFunctionSimulator( pxeOracleInterface, - this.contractDataProvider, + proxyContractDataProvider, this.noteDataProvider, this.keyStore, this.addressDataProvider, From aabc3a01a9b1456c8e4dcb42fcd8705a8e1096eb Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 12:34:22 +0000 Subject: [PATCH 011/140] getMembershipWitness --- .../execution_data_provider.ts | 11 +----- .../oracle/common.ts | 35 +++++++++++++++++++ .../oracle/utility_execution_oracle.ts | 3 +- .../pxe_oracle_interface.ts | 27 -------------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 44d10ef2be65..daa2f0775b5a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -3,7 +3,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2Block } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; +import { type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { NodeStats } from '@aztec/stdlib/tx'; import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js'; @@ -54,15 +54,6 @@ export interface ExecutionDataProvider { */ getNullifierMembershipWitnessAtLatestBlock(nullifier: Fr): Promise; - /** - * Fetches the index and sibling path of a leaf at a given block from a given tree. - * @param blockNumber - The block number at which to get the membership witness. - * @param treeId - Id of the tree to get the sibling path from. - * @param leafValue - The leaf value - * @returns The index and sibling path concatenated [index, sibling_path] - */ - getMembershipWitness(blockNumber: BlockNumber, treeId: MerkleTreeId, leafValue: Fr): Promise; - /** * Returns a nullifier membership witness for a given nullifier at a given block. * @param blockNumber - The block number at which to get the index. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 98b085283a56..d182d267ea7d 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -4,12 +4,14 @@ import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { KeyStore } from '@aztec/key-store'; import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import type { BlockParameter } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { computeAddressSecret } from '@aztec/stdlib/keys'; import { DirectionalAppTaggingSecret, deriveEcdhSharedSecret } from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; +import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; @@ -137,3 +139,36 @@ export async function getL1ToL2MembershipWitness( // Assuming messageIndex is what you intended to use for the index in MessageLoadOracleInputs return new MessageLoadOracleInputs(messageIndex, siblingPath); } + +export async function getMembershipWitness( + blockNumber: BlockParameter, + treeId: MerkleTreeId, + leafValue: Fr, + aztecNode: AztecNode, +): Promise { + const witness = await tryGetMembershipWitness(blockNumber, treeId, leafValue, aztecNode); + if (!witness) { + throw new Error(`Leaf value ${leafValue} not found in tree ${MerkleTreeId[treeId]} at block ${blockNumber}`); + } + return witness; +} + +async function tryGetMembershipWitness( + blockNumber: BlockParameter, + treeId: MerkleTreeId, + value: Fr, + aztecNode: AztecNode, +): Promise { + switch (treeId) { + case MerkleTreeId.NULLIFIER_TREE: + return (await aztecNode.getNullifierMembershipWitness(blockNumber, value))?.withoutPreimage().toFields(); + case MerkleTreeId.NOTE_HASH_TREE: + return (await aztecNode.getNoteHashMembershipWitness(blockNumber, value))?.toFields(); + case MerkleTreeId.PUBLIC_DATA_TREE: + return (await aztecNode.getPublicDataWitness(blockNumber, value))?.withoutPreimage().toFields(); + case MerkleTreeId.ARCHIVE: + return (await aztecNode.getArchiveMembershipWitness(blockNumber, value))?.toFields(); + default: + throw new Error('Not implemented'); + } +} 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 a6930e92d342..ef6ac0e210dc 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 @@ -22,6 +22,7 @@ import { getCompleteAddress, getContractInstance, getL1ToL2MembershipWitness, + getMembershipWitness, getNotes, getSharedSecret, } from './common.js'; @@ -88,7 +89,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns The index and sibling path concatenated [index, sibling_path] */ public utilityGetMembershipWitness(blockNumber: BlockNumber, treeId: MerkleTreeId, leafValue: Fr): Promise { - return this.executionDataProvider.getMembershipWitness(blockNumber, treeId, leafValue); + return getMembershipWitness(blockNumber, treeId, leafValue, this.aztecNode); } /** diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 56266986fa2e..88ac94e04bea 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -73,33 +73,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return leafIndex?.data; } - public async getMembershipWitness(blockNumber: BlockParameter, treeId: MerkleTreeId, leafValue: Fr): Promise { - const witness = await this.#tryGetMembershipWitness(blockNumber, treeId, leafValue); - if (!witness) { - throw new Error(`Leaf value ${leafValue} not found in tree ${MerkleTreeId[treeId]} at block ${blockNumber}`); - } - return witness; - } - - async #tryGetMembershipWitness( - blockNumber: BlockParameter, - treeId: MerkleTreeId, - value: Fr, - ): Promise { - switch (treeId) { - case MerkleTreeId.NULLIFIER_TREE: - return (await this.aztecNode.getNullifierMembershipWitness(blockNumber, value))?.withoutPreimage().toFields(); - case MerkleTreeId.NOTE_HASH_TREE: - return (await this.aztecNode.getNoteHashMembershipWitness(blockNumber, value))?.toFields(); - case MerkleTreeId.PUBLIC_DATA_TREE: - return (await this.aztecNode.getPublicDataWitness(blockNumber, value))?.withoutPreimage().toFields(); - case MerkleTreeId.ARCHIVE: - return (await this.aztecNode.getArchiveMembershipWitness(blockNumber, value))?.toFields(); - default: - throw new Error('Not implemented'); - } - } - public async getNullifierMembershipWitnessAtLatestBlock(nullifier: Fr) { const blockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); return this.getNullifierMembershipWitness(blockNumber, nullifier); From 8e790919e9873393f4e7d7f4dd54bbfeac76f26a Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 13:00:49 +0000 Subject: [PATCH 012/140] getLowNullifierMembershipWitness --- .../contract_function_simulator.ts | 10 ++- .../execution_data_provider.ts | 14 ---- .../oracle/common.test.ts | 77 +++++++++++++++++++ .../oracle/common.ts | 22 +++++- .../oracle/oracle_version_is_checked.test.ts | 10 ++- .../oracle/private_execution.test.ts | 10 ++- .../oracle/private_execution_oracle.ts | 10 ++- .../oracle/utility_execution.test.ts | 10 ++- .../oracle/utility_execution_oracle.ts | 11 ++- .../pxe_oracle_interface.test.ts | 8 -- .../pxe_oracle_interface.ts | 11 --- yarn-project/pxe/src/pxe.ts | 1 + .../oracle/txe_oracle_top_level_context.ts | 2 + yarn-project/txe/src/txe_session.ts | 2 + 14 files changed, 156 insertions(+), 42 deletions(-) create mode 100644 yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 13033631f237..bc0467056ff3 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -71,7 +71,12 @@ import { getFinalMinRevertibleSideEffectCounter, } from '@aztec/stdlib/tx'; -import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../storage/index.js'; +import type { + AddressDataProvider, + AnchorBlockDataProvider, + ContractDataProvider, + NoteDataProvider, +} from '../storage/index.js'; import type { ExecutionDataProvider } from './execution_data_provider.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; @@ -95,6 +100,7 @@ export class ContractFunctionSimulator { private keyStore: KeyStore, private addressDataProvider: AddressDataProvider, private aztecNode: AztecNode, + private anchorBlockDataProvider: AnchorBlockDataProvider, private simulator: CircuitSimulator, ) { this.log = createLogger('simulator'); @@ -173,6 +179,7 @@ export class ContractFunctionSimulator { this.keyStore, this.addressDataProvider, this.aztecNode, + this.anchorBlockDataProvider, 0, // totalPublicArgsCount startSideEffectCounter, undefined, // log @@ -263,6 +270,7 @@ export class ContractFunctionSimulator { this.keyStore, this.addressDataProvider, this.aztecNode, + this.anchorBlockDataProvider, undefined, scopes, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index daa2f0775b5a..93561b587f17 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -65,20 +65,6 @@ export interface ExecutionDataProvider { nullifier: Fr, ): Promise; - /** - * Returns a low nullifier membership witness for a given nullifier at a given block. - * @param blockNumber - The block number at which to get the index. - * @param nullifier - Nullifier we try to find the low nullifier witness for. - * @returns The low nullifier membership witness (if found). - * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked - * 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. - */ - getLowNullifierMembershipWitness( - blockNumber: BlockNumber, - nullifier: Fr, - ): Promise; - /** * Returns a witness for a given slot of the public data tree at a given block. * @param blockNumber - The block number at which to get the witness. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts new file mode 100644 index 000000000000..097b8a5679f8 --- /dev/null +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -0,0 +1,77 @@ +import { BlockNumber } from '@aztec/foundation/branded-types'; +import { Fr } from '@aztec/foundation/curves/bn254'; +import { KeyStore } from '@aztec/key-store'; +import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; +import { CompleteAddress } from '@aztec/stdlib/contract'; +import type { AztecNode } from '@aztec/stdlib/interfaces/client'; +import { BlockHeader, GlobalVariables } from '@aztec/stdlib/tx'; + +import { jest } from '@jest/globals'; +import { type MockProxy, mock } from 'jest-mock-extended'; + +import { AddressDataProvider, AnchorBlockDataProvider, ContractDataProvider } from '../../storage/index.js'; +import { getLowNullifierMembershipWitness } from './common.js'; + +jest.setTimeout(30_000); + +describe('Common oracle functions', () => { + let aztecNode: MockProxy; + + let addressDataProvider: AddressDataProvider; + let contractDataProvider: ContractDataProvider; + let anchorBlockDataProvider: AnchorBlockDataProvider; + let keyStore: KeyStore; + + let recipient: CompleteAddress; + + // The block number of the last log to be emitted. + const MAX_BLOCK_NUMBER_OF_A_LOG = BlockNumber(3); + + beforeEach(async () => { + const store = await openTmpStore('test'); + aztecNode = mock(); + contractDataProvider = new ContractDataProvider(store); + jest.spyOn(contractDataProvider, 'getDebugContractName').mockImplementation(() => Promise.resolve('TestContract')); + + addressDataProvider = new AddressDataProvider(store); + anchorBlockDataProvider = new AnchorBlockDataProvider(store); + keyStore = new KeyStore(store); + + // Set up recipient account + recipient = await keyStore.addAccount(new Fr(69), Fr.random()); + await addressDataProvider.addCompleteAddress(recipient); + + // PXEOracleInterface.syncTaggedLogs(...) function syncs logs up to the block number up to which PXE synced. We set + // the synced block number to that of the last emitted log to receive all the logs by default. + await setSyncedBlockNumber(MAX_BLOCK_NUMBER_OF_A_LOG); + }); + + describe('Respects synced block number', () => { + const syncedBlockNumber = 100; + let nullifier: Fr; + + beforeEach(async () => { + nullifier = Fr.random(); + await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); + }); + + it('throws when getting low nullifier membership witness for future block', async () => { + await expect( + getLowNullifierMembershipWitness( + BlockNumber(syncedBlockNumber + 1), + nullifier, + anchorBlockDataProvider, + aztecNode, + ), + ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); + }); + }); + + const setSyncedBlockNumber = (blockNumber: BlockNumber) => { + return anchorBlockDataProvider.setHeader( + BlockHeader.empty({ + globalVariables: GlobalVariables.empty({ blockNumber }), + }), + ); + }; +}); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index d182d267ea7d..b8cd91813810 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -11,9 +11,14 @@ import { computeAddressSecret } from '@aztec/stdlib/keys'; import { DirectionalAppTaggingSecret, deriveEcdhSharedSecret } from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; -import { MerkleTreeId } from '@aztec/stdlib/trees'; +import { MerkleTreeId, NullifierMembershipWitness } from '@aztec/stdlib/trees'; -import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import type { + AddressDataProvider, + AnchorBlockDataProvider, + ContractDataProvider, + NoteDataProvider, +} from '../../storage/index.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; // TODO: this might not be the final home for these functions, @@ -172,3 +177,16 @@ async function tryGetMembershipWitness( throw new Error('Not implemented'); } } + +export async function getLowNullifierMembershipWitness( + blockNumber: BlockParameter, + nullifier: Fr, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, +): Promise { + const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 3ef9528483a2..0deb1d089fd8 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -11,7 +11,12 @@ import { BlockHeader, HashedValues, TxContext, TxExecutionRequest } from '@aztec import { mock } from 'jest-mock-extended'; -import { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import { + AddressDataProvider, + AnchorBlockDataProvider, + ContractDataProvider, + NoteDataProvider, +} from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -24,6 +29,7 @@ describe('Oracle Version Check test suite', () => { let keyStore: ReturnType>; let addressDataProvider: ReturnType>; let aztecNode: ReturnType>; + let anchorBlockDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; @@ -34,6 +40,7 @@ describe('Oracle Version Check test suite', () => { keyStore = mock(); addressDataProvider = mock(); aztecNode = mock(); + anchorBlockDataProvider = mock(); // Mock basic oracle responses executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); @@ -54,6 +61,7 @@ describe('Oracle Version Check test suite', () => { keyStore, addressDataProvider, aztecNode, + anchorBlockDataProvider, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index ca685553dc05..f6951fad8094 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -63,7 +63,12 @@ import { jest } from '@jest/globals'; import { Matcher, type MatcherCreator, type MockProxy, mock } from 'jest-mock-extended'; import { toFunctionSelector } from 'viem'; -import { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import { + AddressDataProvider, + AnchorBlockDataProvider, + ContractDataProvider, + NoteDataProvider, +} from '../../storage/index.js'; import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -111,6 +116,7 @@ describe('Private Execution test suite', () => { let keyStore: MockProxy; let senderTaggingDataProvider: MockProxy; let aztecNode: MockProxy; + let anchorBlockDataProvider: MockProxy; let acirSimulator: ContractFunctionSimulator; let anchorBlockHeader = BlockHeader.empty(); @@ -282,6 +288,7 @@ describe('Private Execution test suite', () => { senderTaggingDataProvider = mock(); aztecNode = mock(); keyStore = mock(); + anchorBlockDataProvider = mock(); contracts = {}; // Mock the senderTaggingDataProvider getter @@ -366,6 +373,7 @@ describe('Private Execution test suite', () => { keyStore, addressDataProvider, aztecNode, + anchorBlockDataProvider, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 5f8c3d1d2509..2b92af4f2f21 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -28,7 +28,12 @@ import { type TxContext, } from '@aztec/stdlib/tx'; -import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import type { + AddressDataProvider, + AnchorBlockDataProvider, + ContractDataProvider, + NoteDataProvider, +} from '../../storage/index.js'; import { syncSenderTaggingIndexes } from '../../tagging/sync/sync_sender_tagging_indexes.js'; import { Tag } from '../../tagging/tag.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -89,6 +94,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP keyStore: KeyStore, addressDataProvider: AddressDataProvider, aztecNode: AztecNode, + anchorBlockDataProvider: AnchorBlockDataProvider, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, log = createLogger('simulator:client_execution_context'), @@ -107,6 +113,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP keyStore, addressDataProvider, aztecNode, + anchorBlockDataProvider, log, scopes, ); @@ -558,6 +565,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.keyStore, this.addressDataProvider, this.aztecNode, + this.anchorBlockDataProvider, this.totalPublicCalldataCount, sideEffectCounter, this.log, 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 a2591735a9c3..70e74d8f6e8b 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 @@ -13,7 +13,12 @@ import { BlockHeader, TxHash } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; -import { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import { + AddressDataProvider, + AnchorBlockDataProvider, + ContractDataProvider, + NoteDataProvider, +} from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -26,6 +31,7 @@ describe('Utility Execution test suite', () => { let keyStore: ReturnType>; let addressDataProvider: ReturnType>; let aztecNode: ReturnType>; + let anchorBlockDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; const ownerSecretKey = Fr.fromHexString('2dcc5485a58316776299be08c78fa3788a1a7961ae30dc747fb1be17692a8d32'); @@ -41,6 +47,7 @@ describe('Utility Execution test suite', () => { keyStore = mock(); addressDataProvider = mock(); aztecNode = mock(); + anchorBlockDataProvider = mock(); acirSimulator = new ContractFunctionSimulator( executionDataProvider, contractDataProvider, @@ -48,6 +55,7 @@ describe('Utility Execution test suite', () => { keyStore, addressDataProvider, aztecNode, + anchorBlockDataProvider, simulator, ); 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 ef6ac0e210dc..b5a875ae3e9c 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 @@ -14,7 +14,12 @@ import type { NoteStatus } from '@aztec/stdlib/note'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; -import type { AddressDataProvider, ContractDataProvider, NoteDataProvider } from '../../storage/index.js'; +import type { + AddressDataProvider, + AnchorBlockDataProvider, + ContractDataProvider, + NoteDataProvider, +} from '../../storage/index.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; @@ -22,6 +27,7 @@ import { getCompleteAddress, getContractInstance, getL1ToL2MembershipWitness, + getLowNullifierMembershipWitness, getMembershipWitness, getNotes, getSharedSecret, @@ -49,6 +55,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly keyStore: KeyStore, protected readonly addressDataProvider: AddressDataProvider, protected readonly aztecNode: AztecNode, + protected readonly anchorBlockDataProvider: AnchorBlockDataProvider, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], ) {} @@ -118,7 +125,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, nullifier: Fr, ): Promise { - return await this.executionDataProvider.getLowNullifierMembershipWitness(blockNumber, nullifier); + return await getLowNullifierMembershipWitness(blockNumber, nullifier, this.anchorBlockDataProvider, this.aztecNode); } /** diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts index 3daccb6823b0..5f38a9a422db 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts @@ -1080,22 +1080,14 @@ describe('PXEOracleInterface', () => { describe('Respects synced block number', () => { const syncedBlockNumber = 100; let contractAddress: AztecAddress; - let nullifier: Fr; let leafSlot: Fr; beforeEach(async () => { contractAddress = await AztecAddress.random(); - nullifier = Fr.random(); leafSlot = Fr.random(); await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); }); - it('throws when getting low nullifier membership witness for future block', async () => { - await expect( - pxeOracleInterface.getLowNullifierMembershipWitness(BlockNumber(syncedBlockNumber + 1), nullifier), - ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); - }); - it('throws when getting block for future block number', async () => { await expect(pxeOracleInterface.getBlock(BlockNumber(syncedBlockNumber + 1))).rejects.toThrow( `Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`, diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 88ac94e04bea..954dd26e6c46 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -85,17 +85,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return this.aztecNode.getNullifierMembershipWitness(blockNumber, nullifier); } - public async getLowNullifierMembershipWitness( - blockNumber: BlockParameter, - nullifier: Fr, - ): Promise { - const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return this.aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier); - } - public async getBlock(blockNumber: BlockParameter): Promise { const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index fb32e84f5b8e..14146a594313 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -215,6 +215,7 @@ export class PXE { this.keyStore, this.addressDataProvider, this.node, + this.anchorBlockDataProvider, this.simulator, ); } 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 a5bede299e77..6deb6689b1c3 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 @@ -310,6 +310,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.keyStore, this.addressDataProvider, this.stateMachine.node, + this.stateMachine.anchorBlockDataProvider, 0, 1, undefined, // log @@ -629,6 +630,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.keyStore, this.addressDataProvider, this.stateMachine.node, + this.stateMachine.anchorBlockDataProvider, ); const acirExecutionResult = await new WASMSimulator() .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 17d7c4139ccc..d26364684e69 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -318,6 +318,7 @@ export class TXESession implements TXESessionStateHandler { this.keyStore, this.addressDataProvider, this.stateMachine.node, + this.stateMachine.anchorBlockDataProvider, ); // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary @@ -378,6 +379,7 @@ export class TXESession implements TXESessionStateHandler { this.keyStore, this.addressDataProvider, this.stateMachine.node, + this.stateMachine.anchorBlockDataProvider, ); this.state = { name: 'UTILITY' }; From 6b9cd9582d3ad92e3c34e4973c0f6e1c967f9021 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 15:10:33 +0000 Subject: [PATCH 013/140] fix some regressions --- .../oracle/private_execution.test.ts | 4 ++-- .../oracle/utility_execution.test.ts | 2 +- yarn-project/stdlib/src/messaging/l1_to_l2_message.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index f6951fad8094..d8d56547195f 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -673,8 +673,8 @@ describe('Private Execution test suite', () => { aztecNode.getL1ToL2MessageMembershipWitness.mockImplementation(async () => { return Promise.resolve([0n, await tree.getSiblingPath(0n, true)]); }); - aztecNode.findLeavesIndexes.mockImplementation(async () => { - return [await wrapDataInBlock(0n, await L2Block.random(BlockNumber(Fr.random().toNumber())))]; + aztecNode.findLeavesIndexes.mockImplementation(() => { + return Promise.resolve([]); }); }; 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 70e74d8f6e8b..55e59a3aea44 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 @@ -99,7 +99,7 @@ describe('Utility Execution test suite', () => { Fr.random(), Fr.random(), TxHash.random(), - BlockNumber(Fr.random().toNumber()), + BlockNumber(42), L2BlockHash.random().toString(), BigInt(index), ), diff --git a/yarn-project/stdlib/src/messaging/l1_to_l2_message.ts b/yarn-project/stdlib/src/messaging/l1_to_l2_message.ts index 8ef2fa7756df..a7e7392e4084 100644 --- a/yarn-project/stdlib/src/messaging/l1_to_l2_message.ts +++ b/yarn-project/stdlib/src/messaging/l1_to_l2_message.ts @@ -84,6 +84,7 @@ export async function getNonNullifiedL1ToL2MessageWitness( if (!response) { throw new Error(`No L1 to L2 message found for message hash ${messageHash.toString()}`); } + const [messageIndex, siblingPath] = response; const messageNullifier = await computeL1ToL2MessageNullifier(contractAddress, messageHash, secret); From 6844a44d7f01bf54f5a446d549570b8e493b1823 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 16:17:36 +0000 Subject: [PATCH 014/140] fix regression --- .../oracle/private_execution.test.ts | 62 ++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index d8d56547195f..cebfbc01db4b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -33,7 +33,7 @@ import { getFunctionArtifactByName, } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { type BlockParameter, L2Block, L2BlockHash, wrapDataInBlock } from '@aztec/stdlib/block'; +import { type BlockParameter, L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress, type ContractInstanceWithAddress, @@ -125,13 +125,20 @@ describe('Private Execution test suite', () => { let defaultContractAddress: AztecAddress; const ownerSk = Fr.fromHexString('2dcc5485a58316776299be08c78fa3788a1a7961ae30dc747fb1be17692a8d32'); const recipientSk = Fr.fromHexString('0c9ed344548e8f9ba8aa3c9f8651eaa2853130f6c1e9c050ccf198f7ea18a7ec'); + const senderForTagsSk = Fr.fromHexString('2f0e5a8f3ba9c0738d6f3a9e0c2e13f7b2d4207f36efda729a2c6e2a5a9f8b1d'); let owner: AztecAddress; let recipient: AztecAddress; + let senderForTags: AztecAddress; let ownerCompleteAddress: CompleteAddress; let recipientCompleteAddress: CompleteAddress; + let senderForTagsCompleteAddress: CompleteAddress; let ownerNskM: GrumpkinScalar; let recipientNskM: GrumpkinScalar; + let senderForTagsNskM: GrumpkinScalar; + let ownerIvskM: GrumpkinScalar; + let recipientIvskM: GrumpkinScalar; + let senderForTagsIvskM: GrumpkinScalar; const treeHeights: { [name: string]: number } = { noteHash: NOTE_HASH_TREE_HEIGHT, @@ -200,9 +207,6 @@ describe('Private Execution test suite', () => { salt: Fr.random(), }); - // We don't care about the `senderForTags` in this test. We just need it to be populated in order for the private - // log emission to not revert. - const senderForTags = await AztecAddress.random(); return acirSimulator.run(txRequest, contractAddress, selector, msgSender, anchorBlockHeader, senderForTags); }; @@ -265,17 +269,28 @@ describe('Private Execution test suite', () => { const ownerPartialAddress = Fr.random(); ownerCompleteAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(ownerSk, ownerPartialAddress); - ({ masterNullifierSecretKey: ownerNskM } = await deriveKeys(ownerSk)); + ({ masterNullifierSecretKey: ownerNskM, masterIncomingViewingSecretKey: ownerIvskM } = await deriveKeys(ownerSk)); const recipientPartialAddress = Fr.random(); recipientCompleteAddress = await CompleteAddress.fromSecretKeyAndPartialAddress( recipientSk, recipientPartialAddress, ); - ({ masterNullifierSecretKey: recipientNskM } = await deriveKeys(recipientSk)); + ({ masterNullifierSecretKey: recipientNskM, masterIncomingViewingSecretKey: recipientIvskM } = + await deriveKeys(recipientSk)); + + const senderForTagsPartialAddress = Fr.random(); + senderForTagsCompleteAddress = await CompleteAddress.fromSecretKeyAndPartialAddress( + senderForTagsSk, + senderForTagsPartialAddress, + ); + ({ masterNullifierSecretKey: senderForTagsNskM, masterIncomingViewingSecretKey: senderForTagsIvskM } = + await deriveKeys(senderForTagsSk)); owner = ownerCompleteAddress.address; recipient = recipientCompleteAddress.address; + senderForTags = senderForTagsCompleteAddress.address; + defaultContractAddress = await AztecAddress.random(); }); @@ -311,6 +326,23 @@ describe('Private Execution test suite', () => { // on the input. aztecNode.getLogsByTags.mockImplementation((tags: Fr[]) => Promise.resolve(tags.map(() => []))); + // TODO: refactor. Maybe it's worth stubbing a key store + // and cleaning up the mess that is setting up keys. + // Also: having owner, recipient, and sender for tags + // in the same key store is maybe too weak of a set up to test? + keyStore.getMasterIncomingViewingSecretKey.mockImplementation(async (address: AztecAddress) => { + if (address.equals(owner)) { + return ownerIvskM; + } + if (address.equals(recipient)) { + return recipientIvskM; + } + if (address.equals(senderForTags)) { + return senderForTagsIvskM; + } + return ownerIvskM; + }); + keyStore.getKeyValidationRequest.mockImplementation(async (pkMHash: Fr, contractAddress: AztecAddress) => { if (pkMHash.equals(await ownerCompleteAddress.publicKeys.masterNullifierPublicKey.hash())) { return Promise.resolve( @@ -328,6 +360,15 @@ describe('Private Execution test suite', () => { ), ); } + if (pkMHash.equals(await senderForTagsCompleteAddress.publicKeys.masterNullifierPublicKey.hash())) { + return Promise.resolve( + new KeyValidationRequest( + senderForTagsCompleteAddress.publicKeys.masterNullifierPublicKey, + await computeAppNullifierSecretKey(senderForTagsNskM, contractAddress), + ), + ); + } + throw new Error(`Unknown master public key hash: ${pkMHash}`); }); @@ -342,7 +383,14 @@ describe('Private Execution test suite', () => { if (address.equals(recipient)) { return Promise.resolve(recipientCompleteAddress); } - throw new Error(`Unknown address: ${address}. Recipient: ${recipient}, Owner: ${owner}`); + + if (address.equals(senderForTags)) { + return Promise.resolve(senderForTagsCompleteAddress); + } + + throw new Error( + `Unknown address: ${address}. Recipient: ${recipient}, Owner: ${owner}, Sender for tags: ${senderForTags}`, + ); }); contractDataProvider.getFunctionArtifact.mockImplementation(async (address, selector) => { From 31b5cead5a8580e1fd058ffd8ce9dfe21af03271 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 16:26:51 +0000 Subject: [PATCH 015/140] getBlock --- .../execution_data_provider.ts | 8 -------- .../oracle/common.test.ts | 8 +++++++- .../contract_function_simulator/oracle/common.ts | 14 +++++++++++++- .../oracle/private_execution.test.ts | 10 +++++----- .../oracle/utility_execution_oracle.ts | 3 ++- .../pxe_oracle_interface.test.ts | 6 ------ .../pxe_oracle_interface.ts | 10 +--------- 7 files changed, 28 insertions(+), 31 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 93561b587f17..0514ae290c7a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -1,7 +1,6 @@ import type { BlockNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { L2Block } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { NodeStats } from '@aztec/stdlib/tx'; @@ -86,13 +85,6 @@ export interface ExecutionDataProvider { */ getPublicStorageAt(blockNumber: BlockNumber, contract: AztecAddress, slot: Fr): Promise; - /** - * Fetch a block corresponding to the given block number. - * @param blockNumber - The block number of a block to fetch. - * @returns - The block corresponding to the given block number. Undefined if it does not exist. - */ - getBlock(blockNumber: BlockNumber): Promise; - /** * Assert that the oracle version is compatible with the expected version. * @param version - The expected version. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index 097b8a5679f8..dc51e50dc303 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -10,7 +10,7 @@ import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; import { AddressDataProvider, AnchorBlockDataProvider, ContractDataProvider } from '../../storage/index.js'; -import { getLowNullifierMembershipWitness } from './common.js'; +import { getBlock, getLowNullifierMembershipWitness } from './common.js'; jest.setTimeout(30_000); @@ -65,6 +65,12 @@ describe('Common oracle functions', () => { ), ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); }); + + it('throws when getting block for future block number', async () => { + await expect(getBlock(BlockNumber(syncedBlockNumber + 1), anchorBlockDataProvider, aztecNode)).rejects.toThrow( + `Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`, + ); + }); }); const setSyncedBlockNumber = (blockNumber: BlockNumber) => { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index b8cd91813810..d7808e626154 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -4,7 +4,7 @@ import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { KeyStore } from '@aztec/key-store'; import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter } from '@aztec/stdlib/block'; +import type { BlockParameter, L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { computeAddressSecret } from '@aztec/stdlib/keys'; @@ -190,3 +190,15 @@ export async function getLowNullifierMembershipWitness( } return aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier); } + +export async function getBlock( + blockNumber: BlockParameter, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, +): Promise { + const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return await aztecNode.getBlock(blockNumber); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index cebfbc01db4b..30757487ede0 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -330,17 +330,17 @@ describe('Private Execution test suite', () => { // and cleaning up the mess that is setting up keys. // Also: having owner, recipient, and sender for tags // in the same key store is maybe too weak of a set up to test? - keyStore.getMasterIncomingViewingSecretKey.mockImplementation(async (address: AztecAddress) => { + keyStore.getMasterIncomingViewingSecretKey.mockImplementation((address: AztecAddress) => { if (address.equals(owner)) { - return ownerIvskM; + return Promise.resolve(ownerIvskM); } if (address.equals(recipient)) { - return recipientIvskM; + return Promise.resolve(recipientIvskM); } if (address.equals(senderForTags)) { - return senderForTagsIvskM; + return Promise.resolve(senderForTagsIvskM); } - return ownerIvskM; + return Promise.resolve(ownerIvskM); }); keyStore.getKeyValidationRequest.mockImplementation(async (pkMHash: Fr, contractAddress: AztecAddress) => { 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 b5a875ae3e9c..f8d14a6ad731 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 @@ -24,6 +24,7 @@ import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; import { + getBlock, getCompleteAddress, getContractInstance, getL1ToL2MembershipWitness, @@ -147,7 +148,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns Block extracted from a block with block number `blockNumber`. */ public async utilityGetBlockHeader(blockNumber: BlockNumber): Promise { - const block = await this.executionDataProvider.getBlock(blockNumber); + const block = await getBlock(blockNumber, this.anchorBlockDataProvider, this.aztecNode); if (!block) { return undefined; } diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts index 5f38a9a422db..d7e2554e3994 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts @@ -1088,12 +1088,6 @@ describe('PXEOracleInterface', () => { await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); }); - it('throws when getting block for future block number', async () => { - await expect(pxeOracleInterface.getBlock(BlockNumber(syncedBlockNumber + 1))).rejects.toThrow( - `Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`, - ); - }); - it('throws when getting public data witness for future block', async () => { await expect( pxeOracleInterface.getPublicDataWitness(BlockNumber(syncedBlockNumber + 1), leafSlot), diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 954dd26e6c46..12c8f627390c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -3,7 +3,7 @@ import { createLogger } from '@aztec/foundation/log'; import type { KeyStore } from '@aztec/key-store'; import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; +import type { BlockParameter, DataInBlock } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/client'; import { @@ -85,14 +85,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return this.aztecNode.getNullifierMembershipWitness(blockNumber, nullifier); } - public async getBlock(blockNumber: BlockParameter): Promise { - const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return await this.aztecNode.getBlock(blockNumber); - } - public async getPublicDataWitness(blockNumber: BlockParameter, leafSlot: Fr): Promise { const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { From b8f8de391ce03ed50e9127e4122c16ec453874eb Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 16:36:04 +0000 Subject: [PATCH 016/140] getNulliferMembershipWitness and getNullifierMembershipWitnessAtLatestBlock --- .../execution_data_provider.ts | 20 +------------------ .../oracle/common.ts | 17 ++++++++++++++++ .../oracle/utility_execution_oracle.ts | 3 ++- .../pxe_oracle_interface.ts | 14 +------------ 4 files changed, 21 insertions(+), 33 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 0514ae290c7a..b2cfdb0f097a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -2,7 +2,7 @@ import type { BlockNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; +import { PublicDataWitness } from '@aztec/stdlib/trees'; import type { NodeStats } from '@aztec/stdlib/tx'; import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js'; @@ -46,24 +46,6 @@ export interface ExecutionDataProvider { */ getNullifierIndex(nullifier: Fr): Promise; - /** - * Returns a nullifier membership witness for the given nullifier or undefined if not found. - * REFACTOR: Same as getL1ToL2MembershipWitness, can be combined with aztec-node method that does almost the same thing. - * @param nullifier - Nullifier we're looking for. - */ - getNullifierMembershipWitnessAtLatestBlock(nullifier: Fr): Promise; - - /** - * Returns a nullifier membership witness for a given nullifier at a given block. - * @param blockNumber - The block number at which to get the index. - * @param nullifier - Nullifier we try to find witness for. - * @returns The nullifier membership witness (if found). - */ - getNullifierMembershipWitness( - blockNumber: BlockNumber, - nullifier: Fr, - ): Promise; - /** * Returns a witness for a given slot of the public data tree at a given block. * @param blockNumber - The block number at which to get the witness. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index d7808e626154..f4ba92729d6b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -202,3 +202,20 @@ export async function getBlock( } return await aztecNode.getBlock(blockNumber); } + +export function getNullifierMembershipWitness( + blockNumber: BlockParameter, + nullifier: Fr, + aztecNode: AztecNode, +): Promise { + return aztecNode.getNullifierMembershipWitness(blockNumber, nullifier); +} + +export async function getNullifierMembershipWitnessAtLatestBlock( + nullifier: Fr, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, +) { + const blockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + return getNullifierMembershipWitness(blockNumber, nullifier, aztecNode); +} 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 f8d14a6ad731..7a56864106d5 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 @@ -31,6 +31,7 @@ import { getLowNullifierMembershipWitness, getMembershipWitness, getNotes, + getNullifierMembershipWitness, getSharedSecret, } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; @@ -110,7 +111,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, nullifier: Fr, ): Promise { - return await this.executionDataProvider.getNullifierMembershipWitness(blockNumber, nullifier); + return await getNullifierMembershipWitness(blockNumber, nullifier, this.aztecNode); } /** diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 12c8f627390c..a26b91248083 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -14,7 +14,7 @@ import { TxScopedL2Log, } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; -import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; +import { MerkleTreeId, PublicDataWitness } from '@aztec/stdlib/trees'; import { TxHash } from '@aztec/stdlib/tx'; import type { ExecutionDataProvider, ExecutionStats } from '../contract_function_simulator/execution_data_provider.js'; @@ -73,18 +73,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return leafIndex?.data; } - public async getNullifierMembershipWitnessAtLatestBlock(nullifier: Fr) { - const blockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - return this.getNullifierMembershipWitness(blockNumber, nullifier); - } - - public getNullifierMembershipWitness( - blockNumber: BlockParameter, - nullifier: Fr, - ): Promise { - return this.aztecNode.getNullifierMembershipWitness(blockNumber, nullifier); - } - public async getPublicDataWitness(blockNumber: BlockParameter, leafSlot: Fr): Promise { const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { From 7514360ed390237d85397bea100a5f6fd5e93e84 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 16:44:58 +0000 Subject: [PATCH 017/140] getPublicDataWitness --- .../execution_data_provider.ts | 8 -------- .../oracle/common.test.ts | 10 +++++++++- .../contract_function_simulator/oracle/common.ts | 15 ++++++++++++++- .../oracle/utility_execution_oracle.ts | 3 ++- .../pxe_oracle_interface.test.ts | 6 ------ .../pxe_oracle_interface.ts | 10 +--------- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index b2cfdb0f097a..fe22a5eface0 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -2,7 +2,6 @@ import type { BlockNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { PublicDataWitness } from '@aztec/stdlib/trees'; import type { NodeStats } from '@aztec/stdlib/tx'; import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js'; @@ -46,13 +45,6 @@ export interface ExecutionDataProvider { */ getNullifierIndex(nullifier: Fr): Promise; - /** - * Returns a witness for a given slot of the public data tree at a given block. - * @param blockNumber - The block number at which to get the witness. - * @param leafSlot - The slot of the public data in the public data tree. - */ - getPublicDataWitness(blockNumber: BlockNumber, leafSlot: Fr): Promise; - /** * Gets the storage value at the given contract storage slot. * diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index dc51e50dc303..a3814a4c574e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -10,7 +10,7 @@ import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; import { AddressDataProvider, AnchorBlockDataProvider, ContractDataProvider } from '../../storage/index.js'; -import { getBlock, getLowNullifierMembershipWitness } from './common.js'; +import { getBlock, getLowNullifierMembershipWitness, getPublicDataWitness } from './common.js'; jest.setTimeout(30_000); @@ -49,8 +49,10 @@ describe('Common oracle functions', () => { describe('Respects synced block number', () => { const syncedBlockNumber = 100; let nullifier: Fr; + let leafSlot: Fr; beforeEach(async () => { + leafSlot = Fr.random(); nullifier = Fr.random(); await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); }); @@ -71,6 +73,12 @@ describe('Common oracle functions', () => { `Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`, ); }); + + it('throws when getting public data witness for future block', async () => { + await expect( + getPublicDataWitness(BlockNumber(syncedBlockNumber + 1), leafSlot, anchorBlockDataProvider, aztecNode), + ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); + }); }); const setSyncedBlockNumber = (blockNumber: BlockNumber) => { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index f4ba92729d6b..b3a536243daa 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -11,7 +11,7 @@ import { computeAddressSecret } from '@aztec/stdlib/keys'; import { DirectionalAppTaggingSecret, deriveEcdhSharedSecret } from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; -import { MerkleTreeId, NullifierMembershipWitness } from '@aztec/stdlib/trees'; +import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { AddressDataProvider, @@ -219,3 +219,16 @@ export async function getNullifierMembershipWitnessAtLatestBlock( const blockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); return getNullifierMembershipWitness(blockNumber, nullifier, aztecNode); } + +export async function getPublicDataWitness( + blockNumber: BlockParameter, + leafSlot: Fr, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, +): Promise { + const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return await aztecNode.getPublicDataWitness(blockNumber, leafSlot); +} 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 7a56864106d5..80362d0c7d17 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 @@ -32,6 +32,7 @@ import { getMembershipWitness, getNotes, getNullifierMembershipWitness, + getPublicDataWitness, getSharedSecret, } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; @@ -140,7 +141,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, leafSlot: Fr, ): Promise { - return await this.executionDataProvider.getPublicDataWitness(blockNumber, leafSlot); + return await getPublicDataWitness(blockNumber, leafSlot, this.anchorBlockDataProvider, this.aztecNode); } /** diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts index d7e2554e3994..7d9788bc99be 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts @@ -1088,12 +1088,6 @@ describe('PXEOracleInterface', () => { await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); }); - it('throws when getting public data witness for future block', async () => { - await expect( - pxeOracleInterface.getPublicDataWitness(BlockNumber(syncedBlockNumber + 1), leafSlot), - ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); - }); - it('throws when getting public storage for future block', async () => { await expect( pxeOracleInterface.getPublicStorageAt(BlockNumber(syncedBlockNumber + 1), contractAddress, leafSlot), diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index a26b91248083..c45b62683845 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -14,7 +14,7 @@ import { TxScopedL2Log, } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; -import { MerkleTreeId, PublicDataWitness } from '@aztec/stdlib/trees'; +import { MerkleTreeId } from '@aztec/stdlib/trees'; import { TxHash } from '@aztec/stdlib/tx'; import type { ExecutionDataProvider, ExecutionStats } from '../contract_function_simulator/execution_data_provider.js'; @@ -73,14 +73,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return leafIndex?.data; } - public async getPublicDataWitness(blockNumber: BlockParameter, leafSlot: Fr): Promise { - const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return await this.aztecNode.getPublicDataWitness(blockNumber, leafSlot); - } - public async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise { const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { From 19c6fd731f6a97deeb16ad4cc777b28428317ee8 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Wed, 17 Dec 2025 17:27:39 +0000 Subject: [PATCH 018/140] getPublicStorageAt --- .../contract_function_simulator.ts | 11 +++- .../execution_data_provider.ts | 15 ------ .../oracle/common.test.ts | 17 +++++- .../oracle/common.ts | 14 +++++ .../oracle/oracle_version_is_checked.test.ts | 2 +- .../oracle/private_execution.test.ts | 2 +- .../oracle/private_execution.ts | 54 +++++++++++++++---- .../oracle/private_execution_oracle.ts | 3 +- .../oracle/utility_execution.test.ts | 2 +- .../oracle/utility_execution_oracle.ts | 9 +++- .../pxe_oracle_interface.test.ts | 18 ------- .../pxe_oracle_interface.ts | 8 --- yarn-project/pxe/src/pxe.ts | 4 +- 13 files changed, 99 insertions(+), 60 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index bc0467056ff3..92966031c5a9 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -132,7 +132,8 @@ export class ContractFunctionSimulator { await verifyCurrentClassId( contractAddress, - this.executionDataProvider, + this.anchorBlockDataProvider, + this.aztecNode, this.contractDataProvider, anchorBlockHeader, ); @@ -251,7 +252,13 @@ export class ContractFunctionSimulator { anchorBlockHeader: BlockHeader, scopes?: AztecAddress[], ): Promise { - await verifyCurrentClassId(call.to, this.executionDataProvider, this.contractDataProvider, anchorBlockHeader); + await verifyCurrentClassId( + call.to, + this.anchorBlockDataProvider, + this.aztecNode, + this.contractDataProvider, + anchorBlockHeader, + ); const entryPointArtifact = await getFunctionArtifact(call.to, call.selector, this.contractDataProvider); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index fe22a5eface0..4af961b238bc 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -1,4 +1,3 @@ -import type { BlockNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; @@ -45,20 +44,6 @@ export interface ExecutionDataProvider { */ getNullifierIndex(nullifier: Fr): Promise; - /** - * Gets the storage value at the given contract storage slot. - * - * @remarks The storage slot here refers to the slot as it is defined in Noir not the index in the merkle tree. - * Aztec's version of `eth_getStorageAt`. - * - * @param blockNumber - The block number at which to get the data. - * @param contract - Address of the contract to query. - * @param slot - Slot to query. - * @returns Storage value at the given contract slot. - * @throws If the contract is not deployed. - */ - getPublicStorageAt(blockNumber: BlockNumber, contract: AztecAddress, slot: Fr): Promise; - /** * Assert that the oracle version is compatible with the expected version. * @param version - The expected version. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index a3814a4c574e..979b8ffe274b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -2,6 +2,7 @@ import { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { CompleteAddress } from '@aztec/stdlib/contract'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { BlockHeader, GlobalVariables } from '@aztec/stdlib/tx'; @@ -10,7 +11,7 @@ import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; import { AddressDataProvider, AnchorBlockDataProvider, ContractDataProvider } from '../../storage/index.js'; -import { getBlock, getLowNullifierMembershipWitness, getPublicDataWitness } from './common.js'; +import { getBlock, getLowNullifierMembershipWitness, getPublicDataWitness, getPublicStorageAt } from './common.js'; jest.setTimeout(30_000); @@ -49,11 +50,13 @@ describe('Common oracle functions', () => { describe('Respects synced block number', () => { const syncedBlockNumber = 100; let nullifier: Fr; + let contractAddress: AztecAddress; let leafSlot: Fr; beforeEach(async () => { leafSlot = Fr.random(); nullifier = Fr.random(); + contractAddress = await AztecAddress.random(); await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); }); @@ -79,6 +82,18 @@ describe('Common oracle functions', () => { getPublicDataWitness(BlockNumber(syncedBlockNumber + 1), leafSlot, anchorBlockDataProvider, aztecNode), ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); }); + + it('throws when getting public storage for future block', async () => { + await expect( + getPublicStorageAt( + BlockNumber(syncedBlockNumber + 1), + contractAddress, + leafSlot, + anchorBlockDataProvider, + aztecNode, + ), + ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); + }); }); const setSyncedBlockNumber = (blockNumber: BlockNumber) => { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index b3a536243daa..6cf6436409cb 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -232,3 +232,17 @@ export async function getPublicDataWitness( } return await aztecNode.getPublicDataWitness(blockNumber, leafSlot); } + +export async function getPublicStorageAt( + blockNumber: BlockParameter, + contract: AztecAddress, + slot: Fr, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, +): Promise { + const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return await aztecNode.getPublicStorageAt(blockNumber, contract, slot); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 0deb1d089fd8..044c58545057 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -43,7 +43,7 @@ describe('Oracle Version Check test suite', () => { anchorBlockDataProvider = mock(); // Mock basic oracle responses - executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); + aztecNode.getPublicStorageAt.mockResolvedValue(Fr.ZERO); executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); contractAddress = await AztecAddress.random(); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 30757487ede0..cb58f974f402 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -408,7 +408,7 @@ describe('Private Execution test suite', () => { executionDataProvider.syncTaggedLogs.mockImplementation((_, __) => Promise.resolve()); executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); - executionDataProvider.getPublicStorageAt.mockImplementation( + aztecNode.getPublicStorageAt.mockImplementation( (_blockNumber: BlockParameter, _address: AztecAddress, _storageSlot: Fr) => { return Promise.resolve(Fr.ZERO); }, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts index 0c5109d01548..8d1011850178 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts @@ -27,9 +27,8 @@ import type { CircuitWitnessGenerationStats } from '@aztec/stdlib/stats'; import { BlockHeader, PrivateCallExecutionResult } from '@aztec/stdlib/tx'; import type { UInt64 } from '@aztec/stdlib/types'; -import { ContractDataProvider } from '../../storage/index.js'; -import type { ExecutionDataProvider } from '../execution_data_provider.js'; -import { getContractInstance } from './common.js'; +import { AnchorBlockDataProvider, ContractDataProvider } from '../../storage/index.js'; +import { getContractInstance, getPublicStorageAt } from './common.js'; import { Oracle } from './oracle.js'; import type { PrivateExecutionOracle } from './private_execution_oracle.js'; @@ -161,16 +160,51 @@ export function extractPrivateCircuitPublicInputs( * @param timestamp - The timestamp at which to obtain the class id from the DelayedPublicMutable. * @returns The current class id. */ -export async function readCurrentClassId( +export async function readCurrentClassIdFromCurrentBlockAnchor( contractAddress: AztecAddress, instance: ContractInstance, - executionDataProvider: ExecutionDataProvider | AztecNode, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, blockNumber: BlockNumber, timestamp: UInt64, ) { const { delayedPublicMutableSlot } = await DelayedPublicMutableValuesWithHash.getContractUpdateSlots(contractAddress); const delayedPublicMutableValues = await DelayedPublicMutableValues.readFromTree(delayedPublicMutableSlot, slot => - executionDataProvider.getPublicStorageAt(blockNumber, ProtocolContractAddress.ContractInstanceRegistry, slot), + getPublicStorageAt( + blockNumber, + ProtocolContractAddress.ContractInstanceRegistry, + slot, + anchorBlockDataProvider, + aztecNode, + ), + ); + let currentClassId = delayedPublicMutableValues.svc.getCurrentAt(timestamp)[0]; + if (currentClassId.isZero()) { + currentClassId = instance.originalContractClassId; + } + return currentClassId; +} + +/** + * Read the current class id of a contract from the execution data provider or AztecNode. If not found, class id + * from the instance is used. + * @param contractAddress - The address of the contract to read the class id for. + * @param instance - The instance of the contract. + * @param executionDataProvider - The execution data provider. + * @param blockNumber - The block number at which to load the DelayedPublicMutable storing the class id. + * @param timestamp - The timestamp at which to obtain the class id from the DelayedPublicMutable. + * @returns The current class id. + */ +export async function readCurrentClassIdFromNode( + contractAddress: AztecAddress, + instance: ContractInstance, + aztecNode: AztecNode, + blockNumber: BlockNumber, + timestamp: UInt64, +) { + const { delayedPublicMutableSlot } = await DelayedPublicMutableValuesWithHash.getContractUpdateSlots(contractAddress); + const delayedPublicMutableValues = await DelayedPublicMutableValues.readFromTree(delayedPublicMutableSlot, slot => + aztecNode.getPublicStorageAt(blockNumber, ProtocolContractAddress.ContractInstanceRegistry, slot), ); let currentClassId = delayedPublicMutableValues.svc.getCurrentAt(timestamp)[0]; if (currentClassId.isZero()) { @@ -189,15 +223,17 @@ export async function readCurrentClassId( */ export async function verifyCurrentClassId( contractAddress: AztecAddress, - executionDataProvider: ExecutionDataProvider, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, contractDataProvider: ContractDataProvider, header: BlockHeader, ) { const instance = await getContractInstance(contractAddress, contractDataProvider); - const currentClassId = await readCurrentClassId( + const currentClassId = await readCurrentClassIdFromCurrentBlockAnchor( contractAddress, instance, - executionDataProvider, + anchorBlockDataProvider, + aztecNode, header.globalVariables.blockNumber, header.globalVariables.timestamp, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 2b92af4f2f21..567137a47fcc 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -534,7 +534,8 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP await verifyCurrentClassId( targetContractAddress, - this.executionDataProvider, + this.anchorBlockDataProvider, + this.aztecNode, this.contractDataProvider, this.anchorBlockHeader, ); 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 55e59a3aea44..2cc12c04930c 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 @@ -79,7 +79,7 @@ describe('Utility Execution test suite', () => { const notes: Note[] = [...Array(5).fill(buildNote(1n)), ...Array(2).fill(buildNote(2n))]; - executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO); + aztecNode.getPublicStorageAt.mockResolvedValue(Fr.ZERO); contractDataProvider.getFunctionArtifact.mockResolvedValue(artifact); contractDataProvider.getContractInstance.mockResolvedValue({ currentContractClassId: new Fr(42), 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 80362d0c7d17..de0277c96954 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 @@ -33,6 +33,7 @@ import { getNotes, getNullifierMembershipWitness, getPublicDataWitness, + getPublicStorageAt, getSharedSecret, } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; @@ -287,7 +288,13 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra const values = []; for (let i = 0n; i < numberOfElements; i++) { const storageSlot = new Fr(startStorageSlot.value + i); - const value = await this.executionDataProvider.getPublicStorageAt(blockNumber, contractAddress, storageSlot); + const value = await getPublicStorageAt( + blockNumber, + contractAddress, + storageSlot, + this.anchorBlockDataProvider, + this.aztecNode, + ); this.log.debug( `Oracle storage read: slot=${storageSlot.toString()} address-${contractAddress.toString()} value=${value}`, diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts index 7d9788bc99be..7bef43a7eb10 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts @@ -1077,24 +1077,6 @@ describe('PXEOracleInterface', () => { }); }); - describe('Respects synced block number', () => { - const syncedBlockNumber = 100; - let contractAddress: AztecAddress; - let leafSlot: Fr; - - beforeEach(async () => { - contractAddress = await AztecAddress.random(); - leafSlot = Fr.random(); - await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); - }); - - it('throws when getting public storage for future block', async () => { - await expect( - pxeOracleInterface.getPublicStorageAt(BlockNumber(syncedBlockNumber + 1), contractAddress, leafSlot), - ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); - }); - }); - const setSyncedBlockNumber = (blockNumber: BlockNumber) => { return anchorBlockDataProvider.setHeader( BlockHeader.empty({ diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index c45b62683845..e80293919a0e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -73,14 +73,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return leafIndex?.data; } - public async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise { - const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return await this.aztecNode.getPublicStorageAt(blockNumber, contract, slot); - } - public assertCompatibleOracleVersion(version: number): void { if (version !== ORACLE_VERSION) { throw new Error(`Incompatible oracle version. Expected version ${ORACLE_VERSION}, got ${version}.`); diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 14146a594313..194ca3f5f9d3 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -58,7 +58,7 @@ import { ContractFunctionSimulator, generateSimulatedProvingResult, } from './contract_function_simulator/contract_function_simulator.js'; -import { readCurrentClassId } from './contract_function_simulator/oracle/private_execution.js'; +import { readCurrentClassIdFromNode } from './contract_function_simulator/oracle/private_execution.js'; import { ProxiedContractDataProviderFactory } from './contract_function_simulator/proxied_contract_data_source.js'; import { ProxiedNodeFactory } from './contract_function_simulator/proxied_node.js'; import { PXEOracleInterface } from './contract_function_simulator/pxe_oracle_interface.js'; @@ -630,7 +630,7 @@ export class PXE { const header = await this.anchorBlockDataProvider.getBlockHeader(); - const currentClassId = await readCurrentClassId( + const currentClassId = await readCurrentClassIdFromNode( contractAddress, currentInstance, this.node, From 95918da5a69c35d48e57bb9bcfd70af8216df7e3 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 09:40:25 +0000 Subject: [PATCH 019/140] fix regressions --- .../oracle/oracle_version_is_checked.test.ts | 7 +++++-- .../oracle/private_execution.test.ts | 2 ++ .../oracle/utility_execution.test.ts | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 044c58545057..77b5e104d960 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -32,6 +32,7 @@ describe('Oracle Version Check test suite', () => { let anchorBlockDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; + let anchorBlockHeader: BlockHeader; beforeEach(async () => { executionDataProvider = mock(); @@ -44,6 +45,8 @@ describe('Oracle Version Check test suite', () => { // Mock basic oracle responses aztecNode.getPublicStorageAt.mockResolvedValue(Fr.ZERO); + anchorBlockHeader = BlockHeader.random(); + anchorBlockDataProvider.getBlockHeader.mockResolvedValue(anchorBlockHeader); executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); contractAddress = await AztecAddress.random(); @@ -99,7 +102,7 @@ describe('Oracle Version Check test suite', () => { // Call the private function with arbitrary message sender and sender for tags const msgSender = await AztecAddress.random(); const senderForTags = await AztecAddress.random(); - await acirSimulator.run(txRequest, contractAddress, selector, msgSender, BlockHeader.random(), senderForTags); + await acirSimulator.run(txRequest, contractAddress, selector, msgSender, anchorBlockHeader, senderForTags); expect(executionDataProvider.assertCompatibleOracleVersion).toHaveBeenCalledTimes(1); }, 30_000); @@ -128,7 +131,7 @@ describe('Oracle Version Check test suite', () => { }; // Call the utility function - await acirSimulator.runUtility(execRequest, [], BlockHeader.random(), []); + await acirSimulator.runUtility(execRequest, [], anchorBlockHeader, []); expect(executionDataProvider.assertCompatibleOracleVersion).toHaveBeenCalledTimes(1); }, 30_000); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index cb58f974f402..72f3935c541f 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -305,6 +305,8 @@ describe('Private Execution test suite', () => { keyStore = mock(); anchorBlockDataProvider = mock(); contracts = {}; + anchorBlockHeader = makeBlockHeader(); + anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); // Mock the senderTaggingDataProvider getter Object.defineProperty(executionDataProvider, 'senderTaggingDataProvider', { 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 2cc12c04930c..fb4fc22015d3 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 @@ -34,6 +34,7 @@ describe('Utility Execution test suite', () => { let anchorBlockDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; + let anchorBlockHeader: BlockHeader; const ownerSecretKey = Fr.fromHexString('2dcc5485a58316776299be08c78fa3788a1a7961ae30dc747fb1be17692a8d32'); const buildNote = (amount: bigint) => { @@ -48,6 +49,8 @@ describe('Utility Execution test suite', () => { addressDataProvider = mock(); aztecNode = mock(); anchorBlockDataProvider = mock(); + anchorBlockHeader = BlockHeader.random(); + anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); acirSimulator = new ContractFunctionSimulator( executionDataProvider, contractDataProvider, @@ -119,7 +122,7 @@ describe('Utility Execution test suite', () => { returnTypes: artifact.returnTypes, }; - const result = await acirSimulator.runUtility(execRequest, [], BlockHeader.random(), []); + const result = await acirSimulator.runUtility(execRequest, [], anchorBlockHeader, []); expect(result).toEqual([new Fr(9)]); }, 30_000); From 245af9a7a9857ab04be7dce7d330c16abfe1a5cb Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 09:50:05 +0000 Subject: [PATCH 020/140] assertCompatibleOracleVersion --- .../contract_function_simulator/execution_data_provider.ts | 6 ------ .../pxe/src/contract_function_simulator/oracle/common.ts | 7 +++++++ .../oracle/oracle_version_is_checked.test.ts | 5 +++-- .../oracle/utility_execution_oracle.ts | 3 ++- .../contract_function_simulator/pxe_oracle_interface.ts | 7 ------- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 4af961b238bc..7012356ed4c1 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -44,12 +44,6 @@ export interface ExecutionDataProvider { */ getNullifierIndex(nullifier: Fr): Promise; - /** - * Assert that the oracle version is compatible with the expected version. - * @param version - The expected version. - */ - assertCompatibleOracleVersion(version: number): void; - /** * Synchronizes the private logs tagged with scoped addresses and all the senders in the address book. Stores the found * logs in CapsuleArray ready for a later retrieval in Aztec.nr. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 6cf6436409cb..02d45cc37f56 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -13,6 +13,7 @@ import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; +import { ORACLE_VERSION } from '../../oracle_version.js'; import type { AddressDataProvider, AnchorBlockDataProvider, @@ -246,3 +247,9 @@ export async function getPublicStorageAt( } return await aztecNode.getPublicStorageAt(blockNumber, contract, slot); } + +export function assertCompatibleOracleVersion(version: number): void { + if (version !== ORACLE_VERSION) { + throw new Error(`Incompatible oracle version. Expected version ${ORACLE_VERSION}, got ${version}.`); + } +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 77b5e104d960..ab4fdd41aa53 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -19,6 +19,7 @@ import { } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; +import { assertCompatibleOracleVersion } from './common.js'; describe('Oracle Version Check test suite', () => { const simulator = new WASMSimulator(); @@ -104,7 +105,7 @@ describe('Oracle Version Check test suite', () => { const senderForTags = await AztecAddress.random(); await acirSimulator.run(txRequest, contractAddress, selector, msgSender, anchorBlockHeader, senderForTags); - expect(executionDataProvider.assertCompatibleOracleVersion).toHaveBeenCalledTimes(1); + expect(assertCompatibleOracleVersion).toHaveBeenCalledTimes(1); }, 30_000); }); @@ -133,7 +134,7 @@ describe('Oracle Version Check test suite', () => { // Call the utility function await acirSimulator.runUtility(execRequest, [], anchorBlockHeader, []); - expect(executionDataProvider.assertCompatibleOracleVersion).toHaveBeenCalledTimes(1); + expect(assertCompatibleOracleVersion).toHaveBeenCalledTimes(1); }, 30_000); }); }); 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 de0277c96954..30ed4461fcbf 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 @@ -24,6 +24,7 @@ import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; import { + assertCompatibleOracleVersion, getBlock, getCompleteAddress, getContractInstance, @@ -65,7 +66,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra ) {} public utilityAssertCompatibleOracleVersion(version: number): void { - this.executionDataProvider.assertCompatibleOracleVersion(version); + assertCompatibleOracleVersion(version); } public utilityGetRandomField(): Fr { diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index e80293919a0e..c91c486ed60e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -18,7 +18,6 @@ import { MerkleTreeId } from '@aztec/stdlib/trees'; import { TxHash } from '@aztec/stdlib/tx'; import type { ExecutionDataProvider, ExecutionStats } from '../contract_function_simulator/execution_data_provider.js'; -import { ORACLE_VERSION } from '../oracle_version.js'; import type { AddressDataProvider } from '../storage/address_data_provider/address_data_provider.js'; import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; import type { CapsuleDataProvider } from '../storage/capsule_data_provider/capsule_data_provider.js'; @@ -73,12 +72,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return leafIndex?.data; } - public assertCompatibleOracleVersion(version: number): void { - if (version !== ORACLE_VERSION) { - throw new Error(`Incompatible oracle version. Expected version ${ORACLE_VERSION}, got ${version}.`); - } - } - /** * Returns the full contents of your address book. * This is used when calculating tags for incoming notes by deriving the shared secret, the contract-siloed tagging secret, and From cd0313d28c801d1cb9cb176e8f1dfcd102b6e6a7 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 10:32:52 +0000 Subject: [PATCH 021/140] remove getSenders --- .../contract_function_simulator.ts | 4 ++++ .../execution_data_provider.ts | 3 --- .../oracle/oracle_version_is_checked.test.ts | 19 ++++++++++++++++--- .../oracle/private_execution.test.ts | 7 ++++--- .../oracle/private_execution_oracle.ts | 9 ++++++--- .../oracle/utility_execution.test.ts | 4 ++++ .../oracle/utility_execution_oracle.ts | 2 ++ .../pxe_oracle_interface.ts | 10 ---------- yarn-project/pxe/src/pxe.ts | 1 + .../oracle/txe_oracle_top_level_context.ts | 6 +++++- yarn-project/txe/src/txe_session.test.ts | 1 + yarn-project/txe/src/txe_session.ts | 6 ++++++ 12 files changed, 49 insertions(+), 23 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 92966031c5a9..139111d893c3 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -76,6 +76,7 @@ import type { AnchorBlockDataProvider, ContractDataProvider, NoteDataProvider, + SenderTaggingDataProvider, } from '../storage/index.js'; import type { ExecutionDataProvider } from './execution_data_provider.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; @@ -101,6 +102,7 @@ export class ContractFunctionSimulator { private addressDataProvider: AddressDataProvider, private aztecNode: AztecNode, private anchorBlockDataProvider: AnchorBlockDataProvider, + private senderTaggingDataProvider: SenderTaggingDataProvider, private simulator: CircuitSimulator, ) { this.log = createLogger('simulator'); @@ -181,6 +183,7 @@ export class ContractFunctionSimulator { this.addressDataProvider, this.aztecNode, this.anchorBlockDataProvider, + this.senderTaggingDataProvider, 0, // totalPublicArgsCount startSideEffectCounter, undefined, // log @@ -278,6 +281,7 @@ export class ContractFunctionSimulator { this.addressDataProvider, this.aztecNode, this.anchorBlockDataProvider, + this.senderTaggingDataProvider, undefined, scopes, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 7012356ed4c1..090f933cebff 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -3,8 +3,6 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import type { NodeStats } from '@aztec/stdlib/tx'; -import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js'; - /** * Error thrown when a contract is not found in the database. */ @@ -133,5 +131,4 @@ export interface ExecutionDataProvider { // Exposed when moving in the direction of #17776 get aztecNode(): AztecNode; - get senderTaggingDataProvider(): SenderTaggingDataProvider; } diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index ab4fdd41aa53..93f62f57f9b6 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -9,6 +9,7 @@ import { GasFees, GasSettings } from '@aztec/stdlib/gas'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { BlockHeader, HashedValues, TxContext, TxExecutionRequest } from '@aztec/stdlib/tx'; +import { jest } from '@jest/globals'; import { mock } from 'jest-mock-extended'; import { @@ -16,10 +17,11 @@ import { AnchorBlockDataProvider, ContractDataProvider, NoteDataProvider, + SenderTaggingDataProvider, } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; -import { assertCompatibleOracleVersion } from './common.js'; +import { UtilityExecutionOracle } from './utility_execution_oracle.js'; describe('Oracle Version Check test suite', () => { const simulator = new WASMSimulator(); @@ -31,9 +33,13 @@ describe('Oracle Version Check test suite', () => { let addressDataProvider: ReturnType>; let aztecNode: ReturnType>; let anchorBlockDataProvider: ReturnType>; + let senderTaggingDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; let anchorBlockHeader: BlockHeader; + let assertCompatibleOracleVersionSpy: jest.SpiedFunction< + typeof UtilityExecutionOracle.prototype.utilityAssertCompatibleOracleVersion + >; beforeEach(async () => { executionDataProvider = mock(); @@ -43,6 +49,12 @@ describe('Oracle Version Check test suite', () => { addressDataProvider = mock(); aztecNode = mock(); anchorBlockDataProvider = mock(); + senderTaggingDataProvider = mock(); + assertCompatibleOracleVersionSpy = jest.spyOn( + UtilityExecutionOracle.prototype, + 'utilityAssertCompatibleOracleVersion', + ); + assertCompatibleOracleVersionSpy.mockClear(); // Mock basic oracle responses aztecNode.getPublicStorageAt.mockResolvedValue(Fr.ZERO); @@ -66,6 +78,7 @@ describe('Oracle Version Check test suite', () => { addressDataProvider, aztecNode, anchorBlockDataProvider, + senderTaggingDataProvider, simulator, ); }); @@ -105,7 +118,7 @@ describe('Oracle Version Check test suite', () => { const senderForTags = await AztecAddress.random(); await acirSimulator.run(txRequest, contractAddress, selector, msgSender, anchorBlockHeader, senderForTags); - expect(assertCompatibleOracleVersion).toHaveBeenCalledTimes(1); + expect(assertCompatibleOracleVersionSpy).toHaveBeenCalledTimes(1); }, 30_000); }); @@ -134,7 +147,7 @@ describe('Oracle Version Check test suite', () => { // Call the utility function await acirSimulator.runUtility(execRequest, [], anchorBlockHeader, []); - expect(assertCompatibleOracleVersion).toHaveBeenCalledTimes(1); + expect(assertCompatibleOracleVersionSpy).toHaveBeenCalledTimes(1); }, 30_000); }); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 72f3935c541f..19c72aef3a12 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -424,6 +424,7 @@ describe('Private Execution test suite', () => { addressDataProvider, aztecNode, anchorBlockDataProvider, + senderTaggingDataProvider, simulator, ); }); @@ -918,7 +919,7 @@ describe('Private Execution test suite', () => { }); it('should be ok for parent to enqueue calls with <= max total args', async () => { // This function recurses and calls itself, so we need to mock retrieval of its own contract instance (parent) - // Recursions test that total args are enforced accross nested calls + // Recursions test that total args are enforced across nested calls const parentContractArtifact = structuredClone(ParentContractArtifact); const parentFunctionArtifact = parentContractArtifact.functions.find(fn => fn.name === 'public_dispatch')!; expect(parentFunctionArtifact).toBeDefined(); @@ -937,9 +938,9 @@ describe('Private Execution test suite', () => { args, }); }); - it('(prevent footguns) should error if parent enqueues two public calls with too many TOTAL args', async () => { + it('(prevent foot guns) should error if parent enqueues two public calls with too many TOTAL args', async () => { // This function recurses and calls itself, so we need to mock retrieval of its own contract instance (parent) - // Recursions test that total args are enforced accross nested calls + // Recursions test that total args are enforced across nested calls const parentContractArtifact = structuredClone(ParentContractArtifact); const parentFunctionArtifact = parentContractArtifact.functions.find(fn => fn.name === 'public_dispatch')!; expect(parentFunctionArtifact).toBeDefined(); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 567137a47fcc..8f0cd978450f 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -33,6 +33,7 @@ import type { AnchorBlockDataProvider, ContractDataProvider, NoteDataProvider, + SenderTaggingDataProvider, } from '../../storage/index.js'; import { syncSenderTaggingIndexes } from '../../tagging/sync/sync_sender_tagging_indexes.js'; import { Tag } from '../../tagging/tag.js'; @@ -95,6 +96,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP addressDataProvider: AddressDataProvider, aztecNode: AztecNode, anchorBlockDataProvider: AnchorBlockDataProvider, + senderTaggingDataProvider: SenderTaggingDataProvider, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, log = createLogger('simulator:client_execution_context'), @@ -114,6 +116,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP addressDataProvider, aztecNode, anchorBlockDataProvider, + senderTaggingDataProvider, log, scopes, ); @@ -258,14 +261,13 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP } else { // TODO(#17776): Don't access the Aztec node and senderTaggingDataProvider via the executionDataProvider. const aztecNode = this.executionDataProvider.aztecNode; - const senderTaggingDataProvider = this.executionDataProvider.senderTaggingDataProvider; // This is a tagging secret we've not yet used in this tx, so first sync our store to make sure its indices // are up to date. We do this here because this store is not synced as part of the global sync because // that'd be wasteful as most tagging secrets are not used in each tx. - await syncSenderTaggingIndexes(secret, this.contractAddress, aztecNode, senderTaggingDataProvider); + await syncSenderTaggingIndexes(secret, this.contractAddress, aztecNode, this.senderTaggingDataProvider); - const lastUsedIndex = await senderTaggingDataProvider.getLastUsedIndex(secret); + const lastUsedIndex = await this.senderTaggingDataProvider.getLastUsedIndex(secret); // If lastUsedIndex is undefined, we've never used this secret, so start from 0 // Otherwise, the next index to use is one past the last used index return lastUsedIndex === undefined ? 0 : lastUsedIndex + 1; @@ -567,6 +569,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.addressDataProvider, this.aztecNode, this.anchorBlockDataProvider, + this.senderTaggingDataProvider, this.totalPublicCalldataCount, sideEffectCounter, this.log, 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 fb4fc22015d3..233a444507db 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 @@ -18,6 +18,7 @@ import { AnchorBlockDataProvider, ContractDataProvider, NoteDataProvider, + SenderTaggingDataProvider, } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -32,6 +33,7 @@ describe('Utility Execution test suite', () => { let addressDataProvider: ReturnType>; let aztecNode: ReturnType>; let anchorBlockDataProvider: ReturnType>; + let senderTaggingDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; let anchorBlockHeader: BlockHeader; @@ -49,6 +51,7 @@ describe('Utility Execution test suite', () => { addressDataProvider = mock(); aztecNode = mock(); anchorBlockDataProvider = mock(); + senderTaggingDataProvider = mock(); anchorBlockHeader = BlockHeader.random(); anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); acirSimulator = new ContractFunctionSimulator( @@ -59,6 +62,7 @@ describe('Utility Execution test suite', () => { addressDataProvider, aztecNode, anchorBlockDataProvider, + senderTaggingDataProvider, simulator, ); 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 30ed4461fcbf..70ebc194d848 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 @@ -19,6 +19,7 @@ import type { AnchorBlockDataProvider, ContractDataProvider, NoteDataProvider, + SenderTaggingDataProvider, } from '../../storage/index.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; @@ -61,6 +62,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly addressDataProvider: AddressDataProvider, protected readonly aztecNode: AztecNode, protected readonly anchorBlockDataProvider: AnchorBlockDataProvider, + protected readonly senderTaggingDataProvider: SenderTaggingDataProvider, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], ) {} diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index c91c486ed60e..df8e13cc7468 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -72,16 +72,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return leafIndex?.data; } - /** - * Returns the full contents of your address book. - * This is used when calculating tags for incoming notes by deriving the shared secret, the contract-siloed tagging secret, and - * finally the index specified tag. We will then query the node with this tag for each address in the address book. - * @returns The full list of the users contact addresses. - */ - public getSenders(): Promise { - return this.recipientTaggingDataProvider.getSenderAddresses(); - } - /** * Returns the last used tagging indexes along with the directional app tagging secrets for a given recipient and all * the senders in the address book. diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 194ca3f5f9d3..00a0c1b61197 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -216,6 +216,7 @@ export class PXE { this.addressDataProvider, this.node, this.anchorBlockDataProvider, + this.senderTaggingDataProvider, this.simulator, ); } 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 6deb6689b1c3..8022a06c557a 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 @@ -17,6 +17,7 @@ import { NoteDataProvider, ORACLE_VERSION, PXEOracleInterface, + SenderTaggingDataProvider, enrichPublicSimulationError, getFunctionArtifact, } from '@aztec/pxe/server'; @@ -97,6 +98,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl private keyStore: KeyStore, private addressDataProvider: AddressDataProvider, private accountDataProvider: TXEAccountDataProvider, + private senderTaggingDataProvider: SenderTaggingDataProvider, private pxeOracleInterface: PXEOracleInterface, private nextBlockTimestamp: bigint, private version: Fr, @@ -205,7 +207,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl } async txeCreateAccount(secret: Fr) { - // This is a footgun ! + // This is a foot gun ! const completeAddress = await this.keyStore.addAccount(secret, secret); await this.accountDataProvider.setAccount(completeAddress.address, completeAddress); await this.addressDataProvider.addCompleteAddress(completeAddress); @@ -311,6 +313,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.addressDataProvider, this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, + this.senderTaggingDataProvider, 0, 1, undefined, // log @@ -631,6 +634,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.addressDataProvider, this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, + this.senderTaggingDataProvider, ); const acirExecutionResult = await new WASMSimulator() .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) diff --git a/yarn-project/txe/src/txe_session.test.ts b/yarn-project/txe/src/txe_session.test.ts index 05cfb418f822..7bbbae68bd24 100644 --- a/yarn-project/txe/src/txe_session.test.ts +++ b/yarn-project/txe/src/txe_session.test.ts @@ -16,6 +16,7 @@ describe('TXESession.processFunction', () => { {} as any, // keyStore {} as any, // addressDataProvider {} as any, // accountDataProvider + {} as any, // senderTaggingDataProvider new Fr(1), // chainId new Fr(1), // version 0n, // nextBlockTimestamp diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index d26364684e69..c4843062b39b 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -125,6 +125,7 @@ export class TXESession implements TXESessionStateHandler { private keyStore: KeyStore, private addressDataProvider: AddressDataProvider, private accountDataProvider: TXEAccountDataProvider, + private senderTaggingDataProvider: SenderTaggingDataProvider, private chainId: Fr, private version: Fr, private nextBlockTimestamp: bigint, @@ -176,6 +177,7 @@ export class TXESession implements TXESessionStateHandler { keyStore, addressDataProvider, accountDataProvider, + senderTaggingDataProvider, pxeOracleInterface, nextBlockTimestamp, version, @@ -193,6 +195,7 @@ export class TXESession implements TXESessionStateHandler { keyStore, addressDataProvider, accountDataProvider, + senderTaggingDataProvider, version, chainId, nextBlockTimestamp, @@ -260,6 +263,7 @@ export class TXESession implements TXESessionStateHandler { this.keyStore, this.addressDataProvider, this.accountDataProvider, + this.senderTaggingDataProvider, this.pxeOracleInterface, this.nextBlockTimestamp, this.version, @@ -319,6 +323,7 @@ export class TXESession implements TXESessionStateHandler { this.addressDataProvider, this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, + this.senderTaggingDataProvider, ); // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary @@ -380,6 +385,7 @@ export class TXESession implements TXESessionStateHandler { this.addressDataProvider, this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, + this.senderTaggingDataProvider, ); this.state = { name: 'UTILITY' }; From 3bf3ac290bdda19c5991ff8697534559a8317238 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 10:48:22 +0000 Subject: [PATCH 022/140] storeCapsule --- .../contract_function_simulator.ts | 4 ++++ .../execution_data_provider.ts | 12 ------------ .../oracle/oracle_version_is_checked.test.ts | 4 ++++ .../oracle/private_execution.test.ts | 4 ++++ .../oracle/private_execution_oracle.ts | 4 ++++ .../oracle/utility_execution.test.ts | 4 ++++ .../oracle/utility_execution_oracle.ts | 4 +++- .../pxe_oracle_interface.ts | 4 ---- yarn-project/pxe/src/pxe.ts | 1 + .../txe/src/oracle/txe_oracle_top_level_context.ts | 4 ++++ yarn-project/txe/src/txe_session.test.ts | 1 + yarn-project/txe/src/txe_session.ts | 6 ++++++ 12 files changed, 35 insertions(+), 17 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 139111d893c3..577104350419 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -74,6 +74,7 @@ import { import type { AddressDataProvider, AnchorBlockDataProvider, + CapsuleDataProvider, ContractDataProvider, NoteDataProvider, SenderTaggingDataProvider, @@ -103,6 +104,7 @@ export class ContractFunctionSimulator { private aztecNode: AztecNode, private anchorBlockDataProvider: AnchorBlockDataProvider, private senderTaggingDataProvider: SenderTaggingDataProvider, + private capsuleDataProvider: CapsuleDataProvider, private simulator: CircuitSimulator, ) { this.log = createLogger('simulator'); @@ -184,6 +186,7 @@ export class ContractFunctionSimulator { this.aztecNode, this.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.capsuleDataProvider, 0, // totalPublicArgsCount startSideEffectCounter, undefined, // log @@ -282,6 +285,7 @@ export class ContractFunctionSimulator { this.aztecNode, this.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.capsuleDataProvider, undefined, scopes, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 090f933cebff..eea37fe623d1 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -83,18 +83,6 @@ export interface ExecutionDataProvider { */ syncNoteNullifiers(contractAddress: AztecAddress): Promise; - /** - * Stores arbitrary information in a per-contract non-volatile database, which can later be retrieved with `loadCapsule`. - * * If data was already stored at this slot, it is overwritten. - * @param contractAddress - The contract address to scope the data under. - * @param slot - The slot in the database in which to store the value. Slots need not be contiguous. - * @param capsule - An array of field elements representing the capsule. - * @remarks A capsule is a "blob" of data that is passed to the contract through an oracle. It works similarly - * to public contract storage in that it's indexed by the contract address and storage slot but instead of the global - * network state it's backed by local PXE db. - */ - storeCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[]): Promise; - /** * Returns data previously stored via `storeCapsule` in the per-contract non-volatile database. * @param contractAddress - The contract address under which the data is scoped. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 93f62f57f9b6..11ae8977ac8f 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -15,6 +15,7 @@ import { mock } from 'jest-mock-extended'; import { AddressDataProvider, AnchorBlockDataProvider, + CapsuleDataProvider, ContractDataProvider, NoteDataProvider, SenderTaggingDataProvider, @@ -34,6 +35,7 @@ describe('Oracle Version Check test suite', () => { let aztecNode: ReturnType>; let anchorBlockDataProvider: ReturnType>; let senderTaggingDataProvider: ReturnType>; + let capsuleDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; let anchorBlockHeader: BlockHeader; @@ -50,6 +52,7 @@ describe('Oracle Version Check test suite', () => { aztecNode = mock(); anchorBlockDataProvider = mock(); senderTaggingDataProvider = mock(); + capsuleDataProvider = mock(); assertCompatibleOracleVersionSpy = jest.spyOn( UtilityExecutionOracle.prototype, 'utilityAssertCompatibleOracleVersion', @@ -79,6 +82,7 @@ describe('Oracle Version Check test suite', () => { aztecNode, anchorBlockDataProvider, senderTaggingDataProvider, + capsuleDataProvider, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 19c72aef3a12..e5b40201fe85 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -66,6 +66,7 @@ import { toFunctionSelector } from 'viem'; import { AddressDataProvider, AnchorBlockDataProvider, + CapsuleDataProvider, ContractDataProvider, NoteDataProvider, } from '../../storage/index.js'; @@ -117,6 +118,7 @@ describe('Private Execution test suite', () => { let senderTaggingDataProvider: MockProxy; let aztecNode: MockProxy; let anchorBlockDataProvider: MockProxy; + let capsuleDataProvider: MockProxy; let acirSimulator: ContractFunctionSimulator; let anchorBlockHeader = BlockHeader.empty(); @@ -304,6 +306,7 @@ describe('Private Execution test suite', () => { aztecNode = mock(); keyStore = mock(); anchorBlockDataProvider = mock(); + capsuleDataProvider = mock(); contracts = {}; anchorBlockHeader = makeBlockHeader(); anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); @@ -425,6 +428,7 @@ describe('Private Execution test suite', () => { aztecNode, anchorBlockDataProvider, senderTaggingDataProvider, + capsuleDataProvider, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 8f0cd978450f..26113e60726c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -31,6 +31,7 @@ import { import type { AddressDataProvider, AnchorBlockDataProvider, + CapsuleDataProvider, ContractDataProvider, NoteDataProvider, SenderTaggingDataProvider, @@ -97,6 +98,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP aztecNode: AztecNode, anchorBlockDataProvider: AnchorBlockDataProvider, senderTaggingDataProvider: SenderTaggingDataProvider, + capsuleDataProvider: CapsuleDataProvider, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, log = createLogger('simulator:client_execution_context'), @@ -117,6 +119,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP aztecNode, anchorBlockDataProvider, senderTaggingDataProvider, + capsuleDataProvider, log, scopes, ); @@ -570,6 +573,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.aztecNode, this.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.capsuleDataProvider, this.totalPublicCalldataCount, sideEffectCounter, this.log, 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 233a444507db..17d00aa3b993 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 @@ -16,6 +16,7 @@ import { mock } from 'jest-mock-extended'; import { AddressDataProvider, AnchorBlockDataProvider, + CapsuleDataProvider, ContractDataProvider, NoteDataProvider, SenderTaggingDataProvider, @@ -34,6 +35,7 @@ describe('Utility Execution test suite', () => { let aztecNode: ReturnType>; let anchorBlockDataProvider: ReturnType>; let senderTaggingDataProvider: ReturnType>; + let capsuleDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; let anchorBlockHeader: BlockHeader; @@ -52,6 +54,7 @@ describe('Utility Execution test suite', () => { aztecNode = mock(); anchorBlockDataProvider = mock(); senderTaggingDataProvider = mock(); + capsuleDataProvider = mock(); anchorBlockHeader = BlockHeader.random(); anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); acirSimulator = new ContractFunctionSimulator( @@ -63,6 +66,7 @@ describe('Utility Execution test suite', () => { aztecNode, anchorBlockDataProvider, senderTaggingDataProvider, + capsuleDataProvider, simulator, ); 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 70ebc194d848..8f0d25c94eea 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 @@ -17,6 +17,7 @@ import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; import type { AddressDataProvider, AnchorBlockDataProvider, + CapsuleDataProvider, ContractDataProvider, NoteDataProvider, SenderTaggingDataProvider, @@ -63,6 +64,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly aztecNode: AztecNode, protected readonly anchorBlockDataProvider: AnchorBlockDataProvider, protected readonly senderTaggingDataProvider: SenderTaggingDataProvider, + protected readonly capsuleDataProvider: CapsuleDataProvider, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], ) {} @@ -360,7 +362,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB throw new Error(`Contract ${contractAddress} is not allowed to access ${this.contractAddress}'s PXE DB`); } - return this.executionDataProvider.storeCapsule(this.contractAddress, slot, capsule); + return this.capsuleDataProvider.storeCapsule(this.contractAddress, slot, capsule); } public async utilityLoadCapsule(contractAddress: AztecAddress, slot: Fr): Promise { diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index df8e13cc7468..8228b671b4cf 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -704,10 +704,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { }); } - storeCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[]): Promise { - return this.capsuleDataProvider.storeCapsule(contractAddress, slot, capsule); - } - loadCapsule(contractAddress: AztecAddress, slot: Fr): Promise { return this.capsuleDataProvider.loadCapsule(contractAddress, slot); } diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 00a0c1b61197..addcc8aabf91 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -217,6 +217,7 @@ export class PXE { this.node, this.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.capsuleDataProvider, this.simulator, ); } 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 8022a06c557a..cd92c2456dd5 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 @@ -14,6 +14,7 @@ import { TestDateProvider } from '@aztec/foundation/timer'; import type { KeyStore } from '@aztec/key-store'; import { AddressDataProvider, + CapsuleDataProvider, NoteDataProvider, ORACLE_VERSION, PXEOracleInterface, @@ -99,6 +100,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl private addressDataProvider: AddressDataProvider, private accountDataProvider: TXEAccountDataProvider, private senderTaggingDataProvider: SenderTaggingDataProvider, + private capsuleDataProvider: CapsuleDataProvider, private pxeOracleInterface: PXEOracleInterface, private nextBlockTimestamp: bigint, private version: Fr, @@ -314,6 +316,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.capsuleDataProvider, 0, 1, undefined, // log @@ -635,6 +638,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.capsuleDataProvider, ); const acirExecutionResult = await new WASMSimulator() .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) diff --git a/yarn-project/txe/src/txe_session.test.ts b/yarn-project/txe/src/txe_session.test.ts index 7bbbae68bd24..c3e9424471a7 100644 --- a/yarn-project/txe/src/txe_session.test.ts +++ b/yarn-project/txe/src/txe_session.test.ts @@ -17,6 +17,7 @@ describe('TXESession.processFunction', () => { {} as any, // addressDataProvider {} as any, // accountDataProvider {} as any, // senderTaggingDataProvider + {} as any, // capsuleDataProvider new Fr(1), // chainId new Fr(1), // version 0n, // nextBlockTimestamp diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index c4843062b39b..6c857a6daa8a 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -126,6 +126,7 @@ export class TXESession implements TXESessionStateHandler { private addressDataProvider: AddressDataProvider, private accountDataProvider: TXEAccountDataProvider, private senderTaggingDataProvider: SenderTaggingDataProvider, + private capsuleDataProvider: CapsuleDataProvider, private chainId: Fr, private version: Fr, private nextBlockTimestamp: bigint, @@ -178,6 +179,7 @@ export class TXESession implements TXESessionStateHandler { addressDataProvider, accountDataProvider, senderTaggingDataProvider, + capsuleDataProvider, pxeOracleInterface, nextBlockTimestamp, version, @@ -196,6 +198,7 @@ export class TXESession implements TXESessionStateHandler { addressDataProvider, accountDataProvider, senderTaggingDataProvider, + capsuleDataProvider, version, chainId, nextBlockTimestamp, @@ -264,6 +267,7 @@ export class TXESession implements TXESessionStateHandler { this.addressDataProvider, this.accountDataProvider, this.senderTaggingDataProvider, + this.capsuleDataProvider, this.pxeOracleInterface, this.nextBlockTimestamp, this.version, @@ -324,6 +328,7 @@ export class TXESession implements TXESessionStateHandler { this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.capsuleDataProvider, ); // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary @@ -386,6 +391,7 @@ export class TXESession implements TXESessionStateHandler { this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.capsuleDataProvider, ); this.state = { name: 'UTILITY' }; From a022b02b0ca3adc30ffc6976e8183ca298b2c479 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 10:55:41 +0000 Subject: [PATCH 023/140] rest of capsule delegates --- .../execution_data_provider.ts | 29 ------------------- .../oracle/oracle_version_is_checked.test.ts | 2 +- .../oracle/private_execution.test.ts | 2 +- .../oracle/utility_execution.test.ts | 2 +- .../oracle/utility_execution_oracle.ts | 6 ++-- .../pxe_oracle_interface.ts | 12 -------- 6 files changed, 6 insertions(+), 47 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index eea37fe623d1..2f460fe35b37 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -77,40 +77,11 @@ export interface ExecutionDataProvider { logRetrievalRequestsArrayBaseSlot: Fr, logRetrievalResponsesArrayBaseSlot: Fr, ): Promise; - /** * Looks for nullifiers of active contract notes and marks them as nullified in the db if a nullifier is found. */ syncNoteNullifiers(contractAddress: AztecAddress): Promise; - /** - * Returns data previously stored via `storeCapsule` in the per-contract non-volatile database. - * @param contractAddress - The contract address under which the data is scoped. - * @param slot - The slot in the database to read. - * @returns The stored data or `null` if no data is stored under the slot. - */ - loadCapsule(contractAddress: AztecAddress, slot: Fr): Promise; - - /** - * Deletes data in the per-contract non-volatile database. Does nothing if no data was present. - * @param contractAddress - The contract address under which the data is scoped. - * @param slot - The slot in the database to delete. - */ - deleteCapsule(contractAddress: AztecAddress, slot: Fr): Promise; - - /** - * Copies a number of contiguous entries in the per-contract non-volatile database. This allows for efficient data - * structures by avoiding repeated calls to `loadCapsule` and `storeCapsule`. - * Supports overlapping source and destination regions (which will result in the overlapped source values being - * overwritten). All copied slots must exist in the database (i.e. have been stored and not deleted) - * - * @param contractAddress - The contract address under which the data is scoped. - * @param srcSlot - The first slot to copy from. - * @param dstSlot - The first slot to copy to. - * @param numEntries - The number of entries to copy. - */ - copyCapsule(contractAddress: AztecAddress, srcSlot: Fr, dstSlot: Fr, numEntries: number): Promise; - /** * Returns the execution statistics collected during the simulator run. * @returns The execution statistics. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 11ae8977ac8f..139887e14b2e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -63,7 +63,7 @@ describe('Oracle Version Check test suite', () => { aztecNode.getPublicStorageAt.mockResolvedValue(Fr.ZERO); anchorBlockHeader = BlockHeader.random(); anchorBlockDataProvider.getBlockHeader.mockResolvedValue(anchorBlockHeader); - executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); + capsuleDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); contractAddress = await AztecAddress.random(); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index e5b40201fe85..8ea6270e5fbb 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -411,7 +411,7 @@ describe('Private Execution test suite', () => { }); executionDataProvider.syncTaggedLogs.mockImplementation((_, __) => Promise.resolve()); - executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); + capsuleDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); aztecNode.getPublicStorageAt.mockImplementation( (_blockNumber: BlockParameter, _address: AztecAddress, _storageSlot: Fr) => { 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 17d00aa3b993..0002e6703a9d 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 @@ -117,7 +117,7 @@ describe('Utility Execution test suite', () => { ), ); - executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); + capsuleDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); const execRequest: FunctionCall = { name: artifact.name, 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 8f0d25c94eea..610a0deaf690 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 @@ -373,7 +373,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra return ( // TODO(#12425): On the following line, the pertinent capsule gets overshadowed by the transient one. Tackle this. this.capsules.find(c => c.contractAddress.equals(contractAddress) && c.storageSlot.equals(slot))?.data ?? - (await this.executionDataProvider.loadCapsule(this.contractAddress, slot)) + (await this.capsuleDataProvider.loadCapsule(this.contractAddress, slot)) ); } @@ -382,7 +382,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB throw new Error(`Contract ${contractAddress} is not allowed to access ${this.contractAddress}'s PXE DB`); } - return this.executionDataProvider.deleteCapsule(this.contractAddress, slot); + return this.capsuleDataProvider.deleteCapsule(this.contractAddress, slot); } public utilityCopyCapsule( @@ -395,7 +395,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB throw new Error(`Contract ${contractAddress} is not allowed to access ${this.contractAddress}'s PXE DB`); } - return this.executionDataProvider.copyCapsule(this.contractAddress, srcSlot, dstSlot, numEntries); + return this.capsuleDataProvider.copyCapsule(this.contractAddress, srcSlot, dstSlot, numEntries); } // TODO(#11849): consider replacing this oracle with a pure Noir implementation of aes decryption. diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 8228b671b4cf..de3245598862 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -704,18 +704,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { }); } - loadCapsule(contractAddress: AztecAddress, slot: Fr): Promise { - return this.capsuleDataProvider.loadCapsule(contractAddress, slot); - } - - deleteCapsule(contractAddress: AztecAddress, slot: Fr): Promise { - return this.capsuleDataProvider.deleteCapsule(contractAddress, slot); - } - - copyCapsule(contractAddress: AztecAddress, srcSlot: Fr, dstSlot: Fr, numEntries: number): Promise { - return this.capsuleDataProvider.copyCapsule(contractAddress, srcSlot, dstSlot, numEntries); - } - // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. async #getPrivateLogsByTags(tags: Fr[]): Promise { From a94eb8ec29fa14f51c0b334add80ff4cdb198e1b Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 10:59:54 +0000 Subject: [PATCH 024/140] getStats --- .../contract_function_simulator.ts | 4 ++-- .../execution_data_provider.ts | 6 ------ .../pxe/src/contract_function_simulator/oracle/common.ts | 9 +++++++++ .../contract_function_simulator/pxe_oracle_interface.ts | 9 +-------- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 577104350419..dfadf5a7c432 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -83,7 +83,7 @@ import type { ExecutionDataProvider } from './execution_data_provider.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; import { HashedValuesCache } from './hashed_values_cache.js'; -import { getFunctionArtifact } from './oracle/common.js'; +import { getFunctionArtifact, getStats } from './oracle/common.js'; import { Oracle } from './oracle/oracle.js'; import { executePrivateFunction, verifyCurrentClassId } from './oracle/private_execution.js'; import { PrivateExecutionOracle } from './oracle/private_execution_oracle.js'; @@ -321,7 +321,7 @@ export class ContractFunctionSimulator { // docs:end:execute_utility_function getStats() { - return this.executionDataProvider.getStats(); + return getStats(this.aztecNode); } } diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 2f460fe35b37..62a9f0f9853d 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -82,12 +82,6 @@ export interface ExecutionDataProvider { */ syncNoteNullifiers(contractAddress: AztecAddress): Promise; - /** - * Returns the execution statistics collected during the simulator run. - * @returns The execution statistics. - */ - getStats(): ExecutionStats; - // Exposed when moving in the direction of #17776 get aztecNode(): AztecNode; } diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 02d45cc37f56..f1fa1afe9e79 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -20,6 +20,8 @@ import type { ContractDataProvider, NoteDataProvider, } from '../../storage/index.js'; +import type { ExecutionStats } from '../execution_data_provider.js'; +import type { ProxiedNode } from '../proxied_node.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; // TODO: this might not be the final home for these functions, @@ -253,3 +255,10 @@ export function assertCompatibleOracleVersion(version: number): void { throw new Error(`Incompatible oracle version. Expected version ${ORACLE_VERSION}, got ${version}.`); } } + +export function getStats(aztecNode: AztecNode): ExecutionStats { + const nodeRPCCalls = + typeof (aztecNode as ProxiedNode).getStats === 'function' ? (aztecNode as ProxiedNode).getStats() : {}; + + return { nodeRPCCalls }; +} diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index de3245598862..ba1efbf985a4 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -17,7 +17,7 @@ import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import { TxHash } from '@aztec/stdlib/tx'; -import type { ExecutionDataProvider, ExecutionStats } from '../contract_function_simulator/execution_data_provider.js'; +import type { ExecutionDataProvider } from '../contract_function_simulator/execution_data_provider.js'; import type { AddressDataProvider } from '../storage/address_data_provider/address_data_provider.js'; import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; import type { CapsuleDataProvider } from '../storage/capsule_data_provider/capsule_data_provider.js'; @@ -720,11 +720,4 @@ export class PXEOracleInterface implements ExecutionDataProvider { logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress)), ); } - - getStats(): ExecutionStats { - const nodeRPCCalls = - typeof (this.aztecNode as ProxiedNode).getStats === 'function' ? (this.aztecNode as ProxiedNode).getStats() : {}; - - return { nodeRPCCalls }; - } } From ee2465306a2d2c27d81059d19214e400c12776bd Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 12:00:55 +0000 Subject: [PATCH 025/140] almost the rest of the frigging owl --- .../contract_function_simulator.ts | 4 + .../execution_data_provider.ts | 19 - .../oracle/common.test.ts | 234 ++++++- .../oracle/common.ts | 402 +++++++++++- .../oracle/oracle_version_is_checked.test.ts | 14 + .../oracle/private_execution.test.ts | 18 +- .../oracle/private_execution_oracle.ts | 4 + .../oracle/utility_execution.test.ts | 14 + .../oracle/utility_execution_oracle.ts | 21 +- .../pxe_oracle_interface.test.ts | 590 +----------------- .../pxe_oracle_interface.ts | 407 +----------- yarn-project/pxe/src/pxe.ts | 6 +- .../oracle/txe_oracle_top_level_context.ts | 4 + yarn-project/txe/src/txe_session.test.ts | 1 + yarn-project/txe/src/txe_session.ts | 11 +- 15 files changed, 714 insertions(+), 1035 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index dfadf5a7c432..a7817c6f756c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -77,6 +77,7 @@ import type { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../storage/index.js'; import type { ExecutionDataProvider } from './execution_data_provider.js'; @@ -104,6 +105,7 @@ export class ContractFunctionSimulator { private aztecNode: AztecNode, private anchorBlockDataProvider: AnchorBlockDataProvider, private senderTaggingDataProvider: SenderTaggingDataProvider, + private recipientTaggingDataProvider: RecipientTaggingDataProvider, private capsuleDataProvider: CapsuleDataProvider, private simulator: CircuitSimulator, ) { @@ -186,6 +188,7 @@ export class ContractFunctionSimulator { this.aztecNode, this.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.recipientTaggingDataProvider, this.capsuleDataProvider, 0, // totalPublicArgsCount startSideEffectCounter, @@ -285,6 +288,7 @@ export class ContractFunctionSimulator { this.aztecNode, this.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.recipientTaggingDataProvider, this.capsuleDataProvider, undefined, scopes, diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 62a9f0f9853d..ed6581197033 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -42,20 +42,6 @@ export interface ExecutionDataProvider { */ getNullifierIndex(nullifier: Fr): Promise; - /** - * Synchronizes the private logs tagged with scoped addresses and all the senders in the address book. Stores the found - * logs in CapsuleArray ready for a later retrieval in Aztec.nr. - * @param contractAddress - The address of the contract that the logs are tagged for. - * @param pendingTaggedLogArrayBaseSlot - The base slot of the pending tagged log capsule array in which found logs will be stored. - * @param scopes - The scoped addresses to sync logs for. If not provided, all accounts in the address book will be - * synced. - */ - syncTaggedLogs( - contractAddress: AztecAddress, - pendingTaggedLogArrayBaseSlot: Fr, - scopes?: AztecAddress[], - ): Promise; - /** * Validates all note and event validation requests enqueued via `enqueue_note_for_validation` and * `enqueue_event_for_validation`, inserting them into the note database and event store respectively, making them @@ -72,11 +58,6 @@ export interface ExecutionDataProvider { eventValidationRequestsArrayBaseSlot: Fr, ): Promise; - bulkRetrieveLogs( - contractAddress: AztecAddress, - logRetrievalRequestsArrayBaseSlot: Fr, - logRetrievalResponsesArrayBaseSlot: Fr, - ): Promise; /** * Looks for nullifiers of active contract notes and marks them as nullified in the db if a nullifier is found. */ diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index 979b8ffe274b..ae49707c936f 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -3,15 +3,33 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; +import { siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { BlockHeader, GlobalVariables } from '@aztec/stdlib/tx'; +import { PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; +import { BlockHeader, GlobalVariables, TxHash, randomIndexedTxEffect } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; +import { randomInt } from 'crypto'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { AddressDataProvider, AnchorBlockDataProvider, ContractDataProvider } from '../../storage/index.js'; -import { getBlock, getLowNullifierMembershipWitness, getPublicDataWitness, getPublicStorageAt } from './common.js'; +import { + AddressDataProvider, + AnchorBlockDataProvider, + CapsuleDataProvider, + ContractDataProvider, +} from '../../storage/index.js'; +import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; +import { + bulkRetrieveLogs, + getBlock, + getLowNullifierMembershipWitness, + getPrivateLogByTag, + getPublicDataWitness, + getPublicLogByTag, + getPublicStorageAt, +} from './common.js'; jest.setTimeout(30_000); @@ -22,8 +40,10 @@ describe('Common oracle functions', () => { let contractDataProvider: ContractDataProvider; let anchorBlockDataProvider: AnchorBlockDataProvider; let keyStore: KeyStore; + let capsuleDataProvider: CapsuleDataProvider; let recipient: CompleteAddress; + let contractAddress: AztecAddress; // The block number of the last log to be emitted. const MAX_BLOCK_NUMBER_OF_A_LOG = BlockNumber(3); @@ -37,6 +57,7 @@ describe('Common oracle functions', () => { addressDataProvider = new AddressDataProvider(store); anchorBlockDataProvider = new AnchorBlockDataProvider(store); keyStore = new KeyStore(store); + capsuleDataProvider = new CapsuleDataProvider(store); // Set up recipient account recipient = await keyStore.addAccount(new Fr(69), Fr.random()); @@ -45,6 +66,8 @@ describe('Common oracle functions', () => { // PXEOracleInterface.syncTaggedLogs(...) function syncs logs up to the block number up to which PXE synced. We set // the synced block number to that of the last emitted log to receive all the logs by default. await setSyncedBlockNumber(MAX_BLOCK_NUMBER_OF_A_LOG); + + contractAddress = await AztecAddress.random(); }); describe('Respects synced block number', () => { @@ -96,6 +119,211 @@ describe('Common oracle functions', () => { }); }); + describe('utilityBulkRetrieveLogs', () => { + const unsiloedTag = Fr.random(); + const REQUEST_SLOT = Fr.random(); + const RESPONSE_SLOT = Fr.random(); + + beforeEach(() => { + aztecNode.getLogsByTags.mockReset(); + aztecNode.getTxEffect.mockReset(); + }); + + it('returns no logs if none are found', async () => { + aztecNode.getLogsByTags.mockResolvedValue([[]]); + + const request = new LogRetrievalRequest(contractAddress, unsiloedTag); + + await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); + await bulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT, capsuleDataProvider, aztecNode); + + expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); + + const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); + expect(responses.length).toEqual(1); + + // Check Option::none + expect(responses[0][0]).toEqual(new Fr(0)); // TODO: deserialize into option and check properly + }); + + it('returns a public log if one is found', async () => { + const scopedLog = await TxScopedL2Log.random(true); + (scopedLog.log as PublicLog).contractAddress = contractAddress; + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + const indexedTxEffect = await randomIndexedTxEffect(); + + aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => + txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), + ); + + const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); + + await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); + await bulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT, capsuleDataProvider, aztecNode); + + expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); + + const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); + expect(responses.length).toEqual(1); + + // Check Option::some + expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly + }); + + it('returns a private log if one is found', async () => { + const scopedLog = await TxScopedL2Log.random(false); + scopedLog.log.fields[0] = await siloPrivateLog(contractAddress, Fr.random()); + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + const indexedTxEffect = await randomIndexedTxEffect(); + aztecNode.getTxEffect.mockResolvedValue(indexedTxEffect); + + aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => + txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), + ); + + const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); + + await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); + await bulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT, capsuleDataProvider, aztecNode); + + expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); + + const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); + expect(responses.length).toEqual(1); + + // Check Option::some + expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly + }); + }); + + describe('getPublicLogByTag', () => { + const tag = Fr.random(); + + beforeEach(() => { + aztecNode.getLogsByTags.mockReset(); + aztecNode.getTxEffect.mockReset(); + }); + + it('returns null if no logs found for tag', async () => { + aztecNode.getLogsByTags.mockResolvedValue([[]]); + + const result = await getPublicLogByTag(tag, contractAddress, aztecNode); + expect(result).toBeNull(); + }); + + it('returns log data when single log found', async () => { + const scopedLog = await TxScopedL2Log.random(true); + const logContractAddress = (scopedLog.log as PublicLog).contractAddress; + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + const indexedTxEffect = await randomIndexedTxEffect(); + aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => + txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), + ); + + const result = (await getPublicLogByTag(tag, logContractAddress, aztecNode))!; + + expect(result.logPayload).toEqual(scopedLog.log.getEmittedFieldsWithoutTag()); + expect(result.uniqueNoteHashesInTx).toEqual(indexedTxEffect.data.noteHashes); + expect(result.txHash).toEqual(scopedLog.txHash); + expect(result.firstNullifierInTx).toEqual(indexedTxEffect.data.nullifiers[0]); + + expect(aztecNode.getLogsByTags).toHaveBeenCalledWith([tag]); + expect(aztecNode.getTxEffect).toHaveBeenCalledWith(scopedLog.txHash); + }); + + it('throws if multiple logs found for tag', async () => { + const scopedLog = await TxScopedL2Log.random(true); + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog, scopedLog]]); + const logContractAddress = (scopedLog.log as PublicLog).contractAddress; + + await expect(getPublicLogByTag(tag, logContractAddress, aztecNode)).rejects.toThrow(/Got 2 logs for tag/); + }); + + it('throws if tx effect not found', async () => { + const scopedLog = await TxScopedL2Log.random(true); + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + aztecNode.getTxEffect.mockResolvedValue(undefined); + const logContractAddress = (scopedLog.log as PublicLog).contractAddress; + + await expect(getPublicLogByTag(tag, logContractAddress, aztecNode)).rejects.toThrow( + /failed to retrieve tx effects/, + ); + }); + + it('returns log fields that are actually emitted', async () => { + const logContractAddress = await AztecAddress.random(); + const logPlaintext = [Fr.random()]; + const logContent = [tag, ...logPlaintext]; + + const log = PublicLog.from({ + contractAddress: logContractAddress, + fields: logContent, + }); + const scopedLogWithPadding = new TxScopedL2Log( + TxHash.random(), + randomInt(100), + randomInt(100), + BlockNumber(randomInt(100)), + L2BlockHash.random(), + log, + ); + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLogWithPadding]]); + aztecNode.getTxEffect.mockResolvedValue(await randomIndexedTxEffect()); + + const result = await getPublicLogByTag(tag, logContractAddress, aztecNode); + + expect(result?.logPayload).toEqual(logPlaintext); + }); + }); + + describe('getPrivateLogByTag', () => { + let tag: Fr; + + beforeEach(() => { + tag = Fr.random(); + }); + + it('returns null if no logs found', async () => { + aztecNode.getLogsByTags.mockResolvedValue([[]]); + const result = await getPrivateLogByTag(tag, aztecNode); + expect(result).toBeNull(); + }); + + it('returns log and tx effect if single log found', async () => { + const scopedLog = await TxScopedL2Log.random(false); + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + const indexedTxEffect = await randomIndexedTxEffect(); + aztecNode.getTxEffect.mockResolvedValue(indexedTxEffect); + + const result = await getPrivateLogByTag(tag, aztecNode); + + expect(result?.logPayload).toEqual(scopedLog.log.getEmittedFieldsWithoutTag()); + expect(result?.uniqueNoteHashesInTx).toEqual(indexedTxEffect.data.noteHashes); + expect(result?.txHash).toEqual(scopedLog.txHash); + expect(result?.firstNullifierInTx).toEqual(indexedTxEffect.data.nullifiers[0]); + expect(aztecNode.getTxEffect).toHaveBeenCalledWith(scopedLog.txHash); + }); + + it('throws if multiple logs found for tag', async () => { + const scopedLog = await TxScopedL2Log.random(false); + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog, scopedLog]]); + + await expect(getPrivateLogByTag(tag, aztecNode)).rejects.toThrow(/Got 2 logs for tag/); + }); + + it('throws if tx effect not found', async () => { + const scopedLog = await TxScopedL2Log.random(false); + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + aztecNode.getTxEffect.mockResolvedValue(undefined); + + await expect(getPrivateLogByTag(tag, aztecNode)).rejects.toThrow(/failed to retrieve tx effects/); + }); + }); + const setSyncedBlockNumber = (blockNumber: BlockNumber) => { return anchorBlockDataProvider.setHeader( BlockHeader.empty({ diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index f1fa1afe9e79..003c55cbac91 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -6,9 +6,18 @@ import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; +import { siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { computeAddressSecret } from '@aztec/stdlib/keys'; -import { DirectionalAppTaggingSecret, deriveEcdhSharedSecret } from '@aztec/stdlib/logs'; +import { + DirectionalAppTaggingSecret, + PendingTaggedLog, + PrivateLogWithTxData, + PublicLog, + PublicLogWithTxData, + TxScopedL2Log, + deriveEcdhSharedSecret, +} from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; @@ -17,10 +26,15 @@ import { ORACLE_VERSION } from '../../oracle_version.js'; import type { AddressDataProvider, AnchorBlockDataProvider, + CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + RecipientTaggingDataProvider, } from '../../storage/index.js'; +import { SiloedTag, Tag, WINDOW_HALF_SIZE, getInitialIndexesMap, getPreTagsForTheWindow } from '../../tagging/index.js'; import type { ExecutionStats } from '../execution_data_provider.js'; +import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; +import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; import type { ProxiedNode } from '../proxied_node.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; @@ -262,3 +276,389 @@ export function getStats(aztecNode: AztecNode): ExecutionStats { return { nodeRPCCalls }; } + +// TODO(#17775): Replace this implementation of this function with one implementing an approach similar +// to syncSenderTaggingIndexes. Not done yet due to re-prioritization to devex and this doesn't directly affect +// devex. +export async function syncTaggedLogs( + contractAddress: AztecAddress, + pendingTaggedLogArrayBaseSlot: Fr, + anchorBlockDataProvider: AnchorBlockDataProvider, + keyStore: KeyStore, + contractDataProvider: ContractDataProvider, + capsuleDataProvider: CapsuleDataProvider, + addressDataProvider: AddressDataProvider, + recipientTaggingDataProvider: RecipientTaggingDataProvider, + aztecNode: AztecNode, + scopes?: AztecAddress[], +) { + const maxBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + + // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles. + // However it is impossible at the moment due to the language not supporting nested slices. + // This nesting is necessary because for a given set of tags we don't + // know how many logs we will get back. Furthermore, these logs are of undetermined + // length, since we don't really know the note they correspond to until we decrypt them. + const recipients = scopes ? scopes : await keyStore.getAccounts(); + for (const recipient of recipients) { + // Get all the secrets for the recipient and sender pairs (#9365) + const indexedSecrets = await getLastUsedTaggingIndexesForSenders( + contractAddress, + recipient, + addressDataProvider, + keyStore, + recipientTaggingDataProvider, + ); + + // We fetch logs for a window of indexes in a range: + // . + // + // We use this window approach because it could happen that a sender might have messed up and inadvertently + // incremented their index without us getting any logs (for example, in case of a revert). If we stopped looking + // for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again. + // Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed + // some logs. For these reasons, we have to look both back and ahead of the stored index. + let secretsAndWindows = indexedSecrets.map(indexedSecret => { + if (indexedSecret.index === undefined) { + return { + secret: indexedSecret.secret, + leftMostIndex: 0, + rightMostIndex: WINDOW_HALF_SIZE, + }; + } else { + return { + secret: indexedSecret.secret, + leftMostIndex: Math.max(0, indexedSecret.index - WINDOW_HALF_SIZE), + rightMostIndex: indexedSecret.index + WINDOW_HALF_SIZE, + }; + } + }); + + // As we iterate we store the largest index we have seen for a given secret to later on store it in the db. + const newLargestIndexMapToStore: { [k: string]: number } = {}; + + // The initial/unmodified indexes of the secrets stored in a key-value map where key is the directional app + // tagging secret. + const initialIndexesMap = getInitialIndexesMap(indexedSecrets); + + while (secretsAndWindows.length > 0) { + const preTagsForTheWholeWindow = getPreTagsForTheWindow(secretsAndWindows); + const tagsForTheWholeWindow = await Promise.all( + preTagsForTheWholeWindow.map(async preTag => { + return SiloedTag.compute(await Tag.compute(preTag), contractAddress); + }), + ); + + // We store the new largest indexes we find in the iteration in the following map to later on construct + // a new set of secrets and windows to fetch logs for. + const newLargestIndexMapForIteration: { [k: string]: number } = {}; + + // Fetch the private logs for the tags and iterate over them + // TODO: The following conversion is unfortunate and we should most likely just type the #getPrivateLogsByTags + // to accept SiloedTag[] instead of Fr[]. That would result in a large change so I didn't do it yet. + const tagsForTheWholeWindowAsFr = tagsForTheWholeWindow.map(tag => tag.value); + const logsByTags = await internalGetPrivateLogsByTags(tagsForTheWholeWindowAsFr, aztecNode); + + for (let logIndex = 0; logIndex < logsByTags.length; logIndex++) { + const logsByTag = logsByTags[logIndex]; + if (logsByTag.length > 0) { + // We filter out the logs that are newer than the anchor block number of the tx currently being constructed + const filteredLogsByBlockNumber = logsByTag.filter(l => l.blockNumber <= maxBlockNumber); + + // We store the logs in capsules (to later be obtained in Noir) + await storePendingTaggedLogs( + contractAddress, + pendingTaggedLogArrayBaseSlot, + recipient, + filteredLogsByBlockNumber, + aztecNode, + capsuleDataProvider, + ); + + // We retrieve the pre-tag corresponding to the log as I need that to evaluate whether + // a new largest index have been found. + const preTagCorrespondingToLog = preTagsForTheWholeWindow[logIndex]; + const initialIndex = initialIndexesMap[preTagCorrespondingToLog.secret.toString()]; + + if ( + preTagCorrespondingToLog.index >= initialIndex && + (newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] === undefined || + preTagCorrespondingToLog.index >= + newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()]) + ) { + // We have found a new largest index so we store it for later processing (storing it in the db + fetching + // the difference of the window sets of current and the next iteration) + newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] = + preTagCorrespondingToLog.index + 1; + } + } + } + + // Now based on the new largest indexes we found, we will construct a new secrets and windows set to fetch logs + // for. Note that it's very unlikely that a new log from the current window would appear between the iterations + // so we fetch the logs only for the difference of the window sets. + const newSecretsAndWindows = []; + for (const [directionalAppTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration)) { + const maybeIndexedSecret = indexedSecrets.find( + indexedSecret => indexedSecret.secret.toString() === directionalAppTaggingSecret, + ); + if (maybeIndexedSecret) { + newSecretsAndWindows.push({ + secret: maybeIndexedSecret.secret, + // We set the left most index to the new index to avoid fetching the same logs again + leftMostIndex: newIndex, + rightMostIndex: newIndex + WINDOW_HALF_SIZE, + }); + + // We store the new largest index in the map to later store it in the db. + newLargestIndexMapToStore[directionalAppTaggingSecret] = newIndex; + } else { + throw new Error( + `Secret not found for directionalAppTaggingSecret ${directionalAppTaggingSecret}. This is a bug as it should never happen!`, + ); + } + } + + // Now we set the new secrets and windows and proceed to the next iteration. + secretsAndWindows = newSecretsAndWindows; + } + + // At this point we have processed all the logs for the recipient so we store the last used indexes in the db. + // newLargestIndexMapToStore contains "next" indexes to look for (one past the last found), so subtract 1 to get + // last used. + await recipientTaggingDataProvider.setLastUsedIndexes( + Object.entries(newLargestIndexMapToStore).map(([directionalAppTaggingSecret, index]) => ({ + secret: DirectionalAppTaggingSecret.fromString(directionalAppTaggingSecret), + index: index - 1, + })), + ); + } +} + +async function storePendingTaggedLogs( + contractAddress: AztecAddress, + capsuleArrayBaseSlot: Fr, + recipient: AztecAddress, + privateLogs: TxScopedL2Log[], + aztecNode: AztecNode, + capsuleDataProvider: CapsuleDataProvider, +) { + // Build all pending tagged logs upfront with their tx effects + const pendingTaggedLogs = await Promise.all( + privateLogs.map(async scopedLog => { + // TODO(#9789): get these effects along with the log + const txEffect = await aztecNode.getTxEffect(scopedLog.txHash); + if (!txEffect) { + throw new Error(`Could not find tx effect for tx hash ${scopedLog.txHash}`); + } + + const pendingTaggedLog = new PendingTaggedLog( + scopedLog.log.fields, + scopedLog.txHash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + recipient, + ); + + return pendingTaggedLog.toFields(); + }), + ); + + return capsuleDataProvider.appendToCapsuleArray(contractAddress, capsuleArrayBaseSlot, pendingTaggedLogs); +} + +/** + * Returns the last used tagging indexes along with the directional app tagging secrets for a given recipient and all + * the senders in the address book. + * This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration + * of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment, + * so we're keeping it private for now. + * @param contractAddress - The contract address to silo the secret for + * @param recipient - The address receiving the notes + * @returns A list of directional app tagging secrets along with the last used tagging indexes. If the corresponding + * secret was never used, the index is undefined. + * TODO(#17775): The naming here is broken as the function name does not reflect the return type. Make sure this gets + * fixed when implementing the linked issue. + */ +async function getLastUsedTaggingIndexesForSenders( + contractAddress: AztecAddress, + recipient: AztecAddress, + addressDataProvider: AddressDataProvider, + keyStore: KeyStore, + recipientTaggingDataProvider: RecipientTaggingDataProvider, +): Promise<{ secret: DirectionalAppTaggingSecret; index: number | undefined }[]> { + const recipientCompleteAddress = await getCompleteAddress(recipient, addressDataProvider); + const recipientIvsk = await keyStore.getMasterIncomingViewingSecretKey(recipient); + + // We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves + // (recipient = us, sender = us) + const senders = [ + ...(await recipientTaggingDataProvider.getSenderAddresses()), + ...(await keyStore.getAccounts()), + ].filter((address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address))); + const secrets = await Promise.all( + senders.map(contact => { + return DirectionalAppTaggingSecret.compute( + recipientCompleteAddress, + recipientIvsk, + contact, + contractAddress, + recipient, + ); + }), + ); + const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); + if (indexes.length !== secrets.length) { + throw new Error('Indexes and directional app tagging secrets have different lengths'); + } + + return secrets.map((secret, i) => ({ + secret, + index: indexes[i], + })); +} + +// TODO(#14555): delete this function and implement this behavior in the node instead +export async function getPublicLogByTag( + tag: Fr, + contractAddress: AztecAddress, + aztecNode: AztecNode, +): Promise { + const logs = await internalGetPublicLogsByTagsFromContract([tag], contractAddress, aztecNode); + const logsForTag = logs[0]; + + if (logsForTag.length == 0) { + return null; + } else if (logsForTag.length > 1) { + // TODO(#11627): handle this case + throw new Error( + `Got ${logsForTag.length} logs for tag ${tag} and contract ${contractAddress.toString()}. getPublicLogByTag currently only supports a single log per tag`, + ); + } + + const scopedLog = logsForTag[0]; + + // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so + // we need to make a second call to the node for `getTxEffect`. + // TODO(#9789): bundle this information in the `getLogsByTag` call. + const txEffect = await aztecNode.getTxEffect(scopedLog.txHash); + if (txEffect == undefined) { + throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); + } + + return new PublicLogWithTxData( + scopedLog.log.getEmittedFieldsWithoutTag(), + scopedLog.txHash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + ); +} + +// TODO(#14555): delete this function and implement this behavior in the node instead +export async function getPrivateLogByTag(siloedTag: Fr, aztecNode: AztecNode): Promise { + const logs = await internalGetPrivateLogsByTags([siloedTag], aztecNode); + const logsForTag = logs[0]; + + if (logsForTag.length == 0) { + return null; + } else if (logsForTag.length > 1) { + // TODO(#11627): handle this case + throw new Error( + `Got ${logsForTag.length} logs for tag ${siloedTag}. getPrivateLogByTag currently only supports a single log per tag`, + ); + } + + const scopedLog = logsForTag[0]; + + // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so + // we need to make a second call to the node for `getTxEffect`. + // TODO(#9789): bundle this information in the `getLogsByTag` call. + const txEffect = await aztecNode.getTxEffect(scopedLog.txHash); + if (txEffect == undefined) { + throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); + } + + return new PrivateLogWithTxData( + scopedLog.log.getEmittedFieldsWithoutTag(), + scopedLog.txHash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + ); +} + +// TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This +// was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. +async function internalGetPrivateLogsByTags(tags: Fr[], aztecNode: AztecNode): Promise { + const allLogs = await aztecNode.getLogsByTags(tags); + return allLogs.map(logs => logs.filter(log => !log.isFromPublic)); +} + +// TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This +// was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. +async function internalGetPublicLogsByTagsFromContract( + tags: Fr[], + contractAddress: AztecAddress, + aztecNode: AztecNode, +): Promise { + const allLogs = await aztecNode.getLogsByTags(tags); + const allPublicLogs = allLogs.map(logs => logs.filter(log => log.isFromPublic)); + return allPublicLogs.map(logs => logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress))); +} + +export async function bulkRetrieveLogs( + contractAddress: AztecAddress, + logRetrievalRequestsArrayBaseSlot: Fr, + logRetrievalResponsesArrayBaseSlot: Fr, + capsuleDataProvider: CapsuleDataProvider, + aztecNode: AztecNode, +) { + // We read all log retrieval requests and process them all concurrently. This makes the process much faster as we + // don't need to wait for the network round-trip. + const logRetrievalRequests = ( + await capsuleDataProvider.readCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot) + ).map(LogRetrievalRequest.fromFields); + + const maybeLogRetrievalResponses = await Promise.all( + logRetrievalRequests.map(async request => { + // TODO(#14555): remove these internal functions and have node endpoints that do this instead + const [publicLog, privateLog] = await Promise.all([ + getPublicLogByTag(request.unsiloedTag, request.contractAddress, aztecNode), + getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag), aztecNode), + ]); + + if (publicLog !== null) { + if (privateLog !== null) { + throw new Error( + `Found both a public and private log when searching for tag ${request.unsiloedTag} from contract ${request.contractAddress}`, + ); + } + + return new LogRetrievalResponse( + publicLog.logPayload, + publicLog.txHash, + publicLog.uniqueNoteHashesInTx, + publicLog.firstNullifierInTx, + ); + } else if (privateLog !== null) { + return new LogRetrievalResponse( + privateLog.logPayload, + privateLog.txHash, + privateLog.uniqueNoteHashesInTx, + privateLog.firstNullifierInTx, + ); + } else { + return null; + } + }), + ); + + // Requests are cleared once we're done. + await capsuleDataProvider.setCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot, []); + + // The responses are stored as Option in a second CapsuleArray. + await capsuleDataProvider.setCapsuleArray( + contractAddress, + logRetrievalResponsesArrayBaseSlot, + maybeLogRetrievalResponses.map(LogRetrievalResponse.toSerializedOption), + ); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 139887e14b2e..7b3296054b2a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -18,6 +18,7 @@ import { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; @@ -35,6 +36,7 @@ describe('Oracle Version Check test suite', () => { let aztecNode: ReturnType>; let anchorBlockDataProvider: ReturnType>; let senderTaggingDataProvider: ReturnType>; + let recipientTaggingDataProvider: ReturnType>; let capsuleDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; @@ -52,6 +54,7 @@ describe('Oracle Version Check test suite', () => { aztecNode = mock(); anchorBlockDataProvider = mock(); senderTaggingDataProvider = mock(); + recipientTaggingDataProvider = mock(); capsuleDataProvider = mock(); assertCompatibleOracleVersionSpy = jest.spyOn( UtilityExecutionOracle.prototype, @@ -64,6 +67,16 @@ describe('Oracle Version Check test suite', () => { anchorBlockHeader = BlockHeader.random(); anchorBlockDataProvider.getBlockHeader.mockResolvedValue(anchorBlockHeader); capsuleDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); + capsuleDataProvider.readCapsuleArray.mockResolvedValue([]); + senderTaggingDataProvider.getLastFinalizedIndex.mockResolvedValue(undefined); + senderTaggingDataProvider.getLastUsedIndex.mockResolvedValue(undefined); + senderTaggingDataProvider.getTxHashesOfPendingIndexes.mockResolvedValue([]); + senderTaggingDataProvider.storePendingIndexes.mockResolvedValue(); + recipientTaggingDataProvider.getSenderAddresses.mockResolvedValue([]); + recipientTaggingDataProvider.getLastUsedIndexes.mockImplementation(secrets => + Promise.resolve(secrets.map(() => undefined)), + ); + keyStore.getAccounts.mockResolvedValue([]); contractAddress = await AztecAddress.random(); @@ -82,6 +95,7 @@ describe('Oracle Version Check test suite', () => { aztecNode, anchorBlockDataProvider, senderTaggingDataProvider, + recipientTaggingDataProvider, capsuleDataProvider, simulator, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 8ea6270e5fbb..f3a0c19cb466 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -69,6 +69,7 @@ import { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + RecipientTaggingDataProvider, } from '../../storage/index.js'; import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; @@ -116,6 +117,7 @@ describe('Private Execution test suite', () => { let addressDataProvider: MockProxy; let keyStore: MockProxy; let senderTaggingDataProvider: MockProxy; + let recipientTaggingDataProvider: MockProxy; let aztecNode: MockProxy; let anchorBlockDataProvider: MockProxy; let capsuleDataProvider: MockProxy; @@ -303,6 +305,7 @@ describe('Private Execution test suite', () => { noteDataProvider = mock(); addressDataProvider = mock(); senderTaggingDataProvider = mock(); + recipientTaggingDataProvider = mock(); aztecNode = mock(); keyStore = mock(); anchorBlockDataProvider = mock(); @@ -310,6 +313,7 @@ describe('Private Execution test suite', () => { contracts = {}; anchorBlockHeader = makeBlockHeader(); anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); + capsuleDataProvider.readCapsuleArray.mockResolvedValue([]); // Mock the senderTaggingDataProvider getter Object.defineProperty(executionDataProvider, 'senderTaggingDataProvider', { @@ -326,6 +330,10 @@ describe('Private Execution test suite', () => { senderTaggingDataProvider.getLastUsedIndex.mockResolvedValue(undefined); senderTaggingDataProvider.getTxHashesOfPendingIndexes.mockResolvedValue([]); senderTaggingDataProvider.storePendingIndexes.mockResolvedValue(); + recipientTaggingDataProvider.getSenderAddresses.mockResolvedValue([]); + recipientTaggingDataProvider.getLastUsedIndexes.mockImplementation(secrets => + Promise.resolve(secrets.map(() => undefined)), + ); // Mock aztec node methods - the return array needs to have the same length as the number of tags // on the input. @@ -348,6 +356,8 @@ describe('Private Execution test suite', () => { return Promise.resolve(ownerIvskM); }); + keyStore.getAccounts.mockResolvedValue([owner, recipient, senderForTags]); + keyStore.getKeyValidationRequest.mockImplementation(async (pkMHash: Fr, contractAddress: AztecAddress) => { if (pkMHash.equals(await ownerCompleteAddress.publicKeys.masterNullifierPublicKey.hash())) { return Promise.resolve( @@ -410,7 +420,6 @@ describe('Private Execution test suite', () => { return Promise.resolve(artifact); }); - executionDataProvider.syncTaggedLogs.mockImplementation((_, __) => Promise.resolve()); capsuleDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); aztecNode.getPublicStorageAt.mockImplementation( @@ -428,6 +437,7 @@ describe('Private Execution test suite', () => { aztecNode, anchorBlockDataProvider, senderTaggingDataProvider, + recipientTaggingDataProvider, capsuleDataProvider, simulator, ); @@ -552,7 +562,6 @@ describe('Private Execution test suite', () => { buildNote(60n, ownerCompleteAddress.address, storageSlot), buildNote(80n, ownerCompleteAddress.address, storageSlot), ]); - executionDataProvider.syncTaggedLogs.mockResolvedValue(); noteDataProvider.getNotes.mockResolvedValue(notes); @@ -601,7 +610,6 @@ describe('Private Execution test suite', () => { const storageSlot = StatefulTestContractArtifact.storageLayout['notes'].slot; const notes = await Promise.all([buildNote(balance, ownerCompleteAddress.address, storageSlot)]); - executionDataProvider.syncTaggedLogs.mockResolvedValue(); noteDataProvider.getNotes.mockResolvedValue(notes); const consumedNotes = await asyncMap(notes, async ({ note, noteNonce, randomness }) => { @@ -1035,7 +1043,6 @@ describe('Private Execution test suite', () => { }); it('should be able to insert, read, and nullify pending note hashes in one call', async () => { - executionDataProvider.syncTaggedLogs.mockResolvedValue(); noteDataProvider.getNotes.mockResolvedValue([]); const amountToTransfer = 100n; @@ -1085,7 +1092,6 @@ describe('Private Execution test suite', () => { }); it('should be able to insert, read, and nullify pending note hashes in nested calls', async () => { - executionDataProvider.syncTaggedLogs.mockResolvedValue(); noteDataProvider.getNotes.mockResolvedValue([]); const amountToTransfer = 100n; @@ -1153,7 +1159,6 @@ describe('Private Execution test suite', () => { }); it('cant read a commitment that is inserted later in same call', async () => { - executionDataProvider.syncTaggedLogs.mockResolvedValue(); noteDataProvider.getNotes.mockResolvedValue([]); const amountToTransfer = 100n; @@ -1194,7 +1199,6 @@ describe('Private Execution test suite', () => { it('fails if returning no notes', async () => { // call_get_notes(owner: AztecAddress, storage_slot: Field, active_or_nullified: bool) const args = [owner, 2n, true]; - executionDataProvider.syncTaggedLogs.mockResolvedValue(); noteDataProvider.getNotes.mockResolvedValue([]); await expect(() => diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 26113e60726c..f8cd5ebf5e4e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -34,6 +34,7 @@ import type { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; import { syncSenderTaggingIndexes } from '../../tagging/sync/sync_sender_tagging_indexes.js'; @@ -98,6 +99,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP aztecNode: AztecNode, anchorBlockDataProvider: AnchorBlockDataProvider, senderTaggingDataProvider: SenderTaggingDataProvider, + recipientTaggingDataProvider: RecipientTaggingDataProvider, capsuleDataProvider: CapsuleDataProvider, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, @@ -119,6 +121,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP aztecNode, anchorBlockDataProvider, senderTaggingDataProvider, + recipientTaggingDataProvider, capsuleDataProvider, log, scopes, @@ -573,6 +576,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.aztecNode, this.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.recipientTaggingDataProvider, this.capsuleDataProvider, this.totalPublicCalldataCount, sideEffectCounter, 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 0002e6703a9d..25a6abf3eb10 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 @@ -19,6 +19,7 @@ import { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; @@ -35,6 +36,7 @@ describe('Utility Execution test suite', () => { let aztecNode: ReturnType>; let anchorBlockDataProvider: ReturnType>; let senderTaggingDataProvider: ReturnType>; + let recipientTaggingDataProvider: ReturnType>; let capsuleDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; @@ -54,9 +56,19 @@ describe('Utility Execution test suite', () => { aztecNode = mock(); anchorBlockDataProvider = mock(); senderTaggingDataProvider = mock(); + recipientTaggingDataProvider = mock(); capsuleDataProvider = mock(); anchorBlockHeader = BlockHeader.random(); anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); + senderTaggingDataProvider.getLastFinalizedIndex.mockResolvedValue(undefined); + senderTaggingDataProvider.getLastUsedIndex.mockResolvedValue(undefined); + senderTaggingDataProvider.getTxHashesOfPendingIndexes.mockResolvedValue([]); + senderTaggingDataProvider.storePendingIndexes.mockResolvedValue(); + recipientTaggingDataProvider.getSenderAddresses.mockResolvedValue([]); + recipientTaggingDataProvider.getLastUsedIndexes.mockImplementation(secrets => + Promise.resolve(secrets.map(() => undefined)), + ); + capsuleDataProvider.readCapsuleArray.mockResolvedValue([]); acirSimulator = new ContractFunctionSimulator( executionDataProvider, contractDataProvider, @@ -66,12 +78,14 @@ describe('Utility Execution test suite', () => { aztecNode, anchorBlockDataProvider, senderTaggingDataProvider, + recipientTaggingDataProvider, capsuleDataProvider, simulator, ); const ownerCompleteAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(ownerSecretKey, Fr.random()); owner = ownerCompleteAddress.address; + keyStore.getAccounts.mockResolvedValue([owner]); addressDataProvider.getCompleteAddress.mockImplementation((account: AztecAddress) => { if (account.equals(owner)) { 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 610a0deaf690..cd1ba289fe9e 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 @@ -20,6 +20,7 @@ import type { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; import type { ExecutionDataProvider } from '../execution_data_provider.js'; @@ -27,6 +28,7 @@ import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; import { assertCompatibleOracleVersion, + bulkRetrieveLogs, getBlock, getCompleteAddress, getContractInstance, @@ -38,6 +40,7 @@ import { getPublicDataWitness, getPublicStorageAt, getSharedSecret, + syncTaggedLogs, } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; @@ -64,6 +67,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly aztecNode: AztecNode, protected readonly anchorBlockDataProvider: AnchorBlockDataProvider, protected readonly senderTaggingDataProvider: SenderTaggingDataProvider, + protected readonly recipientTaggingDataProvider: RecipientTaggingDataProvider, protected readonly capsuleDataProvider: CapsuleDataProvider, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], @@ -318,7 +322,18 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } public async utilityFetchTaggedLogs(pendingTaggedLogArrayBaseSlot: Fr) { - await this.executionDataProvider.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes); + await syncTaggedLogs( + this.contractAddress, + pendingTaggedLogArrayBaseSlot, + this.anchorBlockDataProvider, + this.keyStore, + this.contractDataProvider, + this.capsuleDataProvider, + this.addressDataProvider, + this.recipientTaggingDataProvider, + this.aztecNode, + this.scopes, + ); await this.executionDataProvider.syncNoteNullifiers(this.contractAddress); } @@ -350,10 +365,12 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra throw new Error(`Got a note validation request from ${contractAddress}, expected ${this.contractAddress}`); } - await this.executionDataProvider.bulkRetrieveLogs( + await bulkRetrieveLogs( contractAddress, logRetrievalRequestsArrayBaseSlot, logRetrievalResponsesArrayBaseSlot, + this.capsuleDataProvider, + this.aztecNode, ); } diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts index 7bef43a7eb10..5d9aebed5516 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts @@ -1,27 +1,16 @@ import { BlockNumber } from '@aztec/foundation/branded-types'; -import { timesParallel } from '@aztec/foundation/collection'; -import { randomInt } from '@aztec/foundation/crypto/random'; -import { Fq, Fr } from '@aztec/foundation/curves/bn254'; +import { Fr } from '@aztec/foundation/curves/bn254'; import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; -import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; +import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { computeAddress, deriveKeys } from '@aztec/stdlib/keys'; -import { DirectionalAppTaggingSecret, PrivateLog, PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; import { NoteDao, NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; -import { - BlockHeader, - GlobalVariables, - type IndexedTxEffect, - TxEffect, - TxHash, - randomIndexedTxEffect, -} from '@aztec/stdlib/tx'; +import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect, TxHash } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -32,33 +21,10 @@ import { CapsuleDataProvider } from '../storage/capsule_data_provider/capsule_da import { ContractDataProvider } from '../storage/contract_data_provider/contract_data_provider.js'; import { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; import { PrivateEventDataProvider } from '../storage/private_event_data_provider/private_event_data_provider.js'; -import { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js'; -import { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js'; -import { WINDOW_HALF_SIZE } from '../tagging/constants.js'; -import { SiloedTag } from '../tagging/siloed_tag.js'; -import { Tag } from '../tagging/tag.js'; -import { LogRetrievalRequest } from './noir-structs/log_retrieval_request.js'; import { PXEOracleInterface } from './pxe_oracle_interface.js'; jest.setTimeout(30_000); -async function computeSiloedTagForIndex( - sender: { completeAddress: CompleteAddress; ivsk: Fq }, - recipient: AztecAddress, - contractAddress: AztecAddress, - index: number, -) { - const secret = await DirectionalAppTaggingSecret.compute( - sender.completeAddress, - sender.ivsk, - recipient, - contractAddress, - recipient, - ); - const tag = await Tag.compute({ secret, index }); - return SiloedTag.compute(tag, contractAddress); -} - describe('PXEOracleInterface', () => { let aztecNode: MockProxy; @@ -66,8 +32,6 @@ describe('PXEOracleInterface', () => { let privateEventDataProvider: PrivateEventDataProvider; let contractDataProvider: ContractDataProvider; let noteDataProvider: NoteDataProvider; - let senderTaggingDataProvider: SenderTaggingDataProvider; - let recipientTaggingDataProvider: RecipientTaggingDataProvider; let anchorBlockDataProvider: AnchorBlockDataProvider; let capsuleDataProvider: CapsuleDataProvider; let keyStore: KeyStore; @@ -77,8 +41,6 @@ describe('PXEOracleInterface', () => { let pxeOracleInterface: PXEOracleInterface; - // The block number of the first log to be emitted. - const MIN_BLOCK_NUMBER_OF_A_LOG = BlockNumber(1); // The block number of the last log to be emitted. const MAX_BLOCK_NUMBER_OF_A_LOG = BlockNumber(3); @@ -92,20 +54,13 @@ describe('PXEOracleInterface', () => { privateEventDataProvider = new PrivateEventDataProvider(store); noteDataProvider = await NoteDataProvider.create(store); anchorBlockDataProvider = new AnchorBlockDataProvider(store); - senderTaggingDataProvider = new SenderTaggingDataProvider(store); - recipientTaggingDataProvider = new RecipientTaggingDataProvider(store); capsuleDataProvider = new CapsuleDataProvider(store); keyStore = new KeyStore(store); pxeOracleInterface = new PXEOracleInterface( aztecNode, - keyStore, - contractDataProvider, noteDataProvider, capsuleDataProvider, anchorBlockDataProvider, - senderTaggingDataProvider, - recipientTaggingDataProvider, - addressDataProvider, privateEventDataProvider, ); // Set up contract address contractAddress = await AztecAddress.random(); @@ -118,340 +73,6 @@ describe('PXEOracleInterface', () => { await setSyncedBlockNumber(MAX_BLOCK_NUMBER_OF_A_LOG); }); - describe('sync tagged logs', () => { - const NUM_SENDERS = 10; - - let senders: { completeAddress: CompleteAddress; ivsk: Fq; secretKey: Fr }[]; - - async function generateMockLogs(tagIndex: number) { - const logs: { [k: string]: TxScopedL2Log[] } = {}; - - // Add a random note from every address in the address book for our account with index tagIndex - // Compute the tag as sender (knowledge of preaddress and ivsk) - for (const sender of senders) { - const tag = await computeSiloedTagForIndex(sender, recipient.address, contractAddress, tagIndex); - const log = new TxScopedL2Log( - TxHash.random(), - 0, - 0, - MIN_BLOCK_NUMBER_OF_A_LOG, - L2BlockHash.random(), - PrivateLog.random(tag.value), - ); - logs[tag.toString()] = [log]; - } - // Accumulated logs intended for recipient: NUM_SENDERS - - // Add a random note from the first sender in the address book, repeating the tag - // Compute the tag as sender (knowledge of preaddress and ivsk) - const firstSender = senders[0]; - const tag = await computeSiloedTagForIndex(firstSender, recipient.address, contractAddress, tagIndex); - const log = new TxScopedL2Log( - TxHash.random(), - 1, - 0, - BlockNumber.ZERO, - L2BlockHash.random(), - PrivateLog.random(tag.value), - ); - logs[tag.toString()].push(log); - // Accumulated logs intended for recipient: NUM_SENDERS + 1 - - // Add a random note from half the address book for our account with index tagIndex + 1 - // Compute the tag as sender (knowledge of preaddress and ivsk) - for (let i = NUM_SENDERS / 2; i < NUM_SENDERS; i++) { - const sender = senders[i]; - const tag = await computeSiloedTagForIndex(sender, recipient.address, contractAddress, tagIndex + 1); - const blockNumber = BlockNumber(2); - const log = new TxScopedL2Log( - TxHash.random(), - 0, - 0, - blockNumber, - L2BlockHash.random(), - PrivateLog.random(tag.value), - ); - logs[tag.toString()] = [log]; - } - // Accumulated logs intended for recipient: NUM_SENDERS + 1 + NUM_SENDERS / 2 - - // Add a random note from every address in the address book for a random recipient with index tagIndex - // Compute the tag as sender (knowledge of preaddress and ivsk) - for (const sender of senders) { - const keys = await deriveKeys(Fr.random()); - const partialAddress = Fr.random(); - const randomRecipient = await computeAddress(keys.publicKeys, partialAddress); - const tag = await computeSiloedTagForIndex(sender, randomRecipient, contractAddress, tagIndex); - const log = new TxScopedL2Log( - TxHash.random(), - 0, - 0, - MAX_BLOCK_NUMBER_OF_A_LOG, - L2BlockHash.random(), - PrivateLog.random(tag.value), - ); - logs[tag.toString()] = [log]; - } - // Accumulated logs intended for recipient: NUM_SENDERS + 1 + NUM_SENDERS / 2 - - // Set up the getPrivateLogsByTags mock - aztecNode.getLogsByTags.mockImplementation(tags => { - return Promise.resolve(tags.map(tag => logs[tag.toString()] ?? [])); - }); - } - - // Set to a random value in this test we don't care about Noir loading the logs from the capsule array. - const PENDING_TAGGED_LOG_ARRAY_BASE_SLOT = Fr.random(); - - beforeEach(async () => { - // Set up the address book - senders = await timesParallel(NUM_SENDERS, async index => { - const keys = await deriveKeys(new Fr(index)); - const partialAddress = Fr.random(); - const address = await computeAddress(keys.publicKeys, partialAddress); - const completeAddress = await CompleteAddress.create(address, keys.publicKeys, partialAddress); - return { completeAddress, ivsk: keys.masterIncomingViewingSecretKey, secretKey: new Fr(index) }; - }); - for (const sender of senders) { - await recipientTaggingDataProvider.addSenderAddress(sender.completeAddress.address); - } - aztecNode.getLogsByTags.mockReset(); - aztecNode.getTxEffect.mockResolvedValue({ - ...randomDataInBlock(await TxEffect.random({ numNullifiers: 1 })), - txIndexInBlock: 0, - }); - }); - - it('should sync tagged logs', async () => { - const tagIndex = 0; - await generateMockLogs(tagIndex); - await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - - // We expect to have all logs intended for the recipient synced (and hence stored in the capsule for later - // processing), one per sender + 1 with a duplicated tag for the first sender + half of the logs for the second - // index - await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1 + NUM_SENDERS / 2); - - // Recompute the secrets (as recipient) to ensure indexes are updated - const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); - const secrets = await Promise.all( - senders.map(sender => - DirectionalAppTaggingSecret.compute( - recipient, - ivsk, - sender.completeAddress.address, - contractAddress, - recipient.address, - ), - ), - ); - - // First sender should have 2 logs, but keep index 0 since they were built using the same tag - // Next 4 senders should also have index 0 = offset + 0 - // Last 5 senders should have index 1 = offset + 1 - const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); - - expect(indexes).toHaveLength(NUM_SENDERS); - expect(indexes).toEqual([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]); - - // We should have called the node 2 times: - // 2 times: first time during initial request, second time after pushing the edge of the window once - expect(aztecNode.getLogsByTags.mock.calls.length).toBe(2); - }); - - it('should sync tagged logs with a sender index offset', async () => { - const tagIndex = 5; - await generateMockLogs(tagIndex); - await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - - // We expect to have all logs intended for the recipient, one per sender + 1 with a duplicated tag for the first - // one + half of the logs for the second index - await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1 + NUM_SENDERS / 2); - - // Recompute the secrets (as recipient) to ensure indexes are updated - const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); - const secrets = await Promise.all( - senders.map(sender => - DirectionalAppTaggingSecret.compute( - recipient, - ivsk, - sender.completeAddress.address, - contractAddress, - recipient.address, - ), - ), - ); - - // First sender should have 2 logs, but keep index 5 since they were built using the same tag - // Next 4 senders should also have index 5 = offset - // Last 5 senders should have index 6 = offset + 1 - const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); - - expect(indexes).toHaveLength(NUM_SENDERS); - expect(indexes).toEqual([5, 5, 5, 5, 5, 6, 6, 6, 6, 6]); - - // We should have called the node 2 times: - // 2 times: first time during initial request, second time after pushing the edge of the window once - expect(aztecNode.getLogsByTags.mock.calls.length).toBe(2); - }); - - it("should sync tagged logs for which indexes are not updated if they're inside the window", async () => { - const tagIndex = 1; - await generateMockLogs(tagIndex); - - // Recompute the secrets (as recipient) to update indexes - const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); - const secrets = await Promise.all( - senders.map(sender => - DirectionalAppTaggingSecret.compute( - recipient, - ivsk, - sender.completeAddress.address, - contractAddress, - recipient.address, - ), - ), - ); - - // Set last used indexes to 1 (so next scan starts at 2) - await recipientTaggingDataProvider.setLastUsedIndexes(secrets.map(secret => ({ secret, index: 1 }))); - - await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - - // Even if our index as recipient is higher than what the sender sent, we should be able to find the logs - // since the window starts at Math.max(0, 2 - window_size) = 0 - await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1 + NUM_SENDERS / 2); - - // First sender should have 2 logs, but keep index 1 since they were built using the same tag - // Next 4 senders should also have index 1 = tagIndex - // Last 5 senders should have index 2 = tagIndex + 1 - const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); - - expect(indexes).toHaveLength(NUM_SENDERS); - expect(indexes).toEqual([1, 1, 1, 1, 1, 2, 2, 2, 2, 2]); - - // We should have called the node 2 times: - // first time during initial request, second time after pushing the edge of the window once - expect(aztecNode.getLogsByTags.mock.calls.length).toBe(2); - }); - - it("should not sync tagged logs for which indexes are not updated if they're outside the window", async () => { - const tagIndex = 0; - await generateMockLogs(tagIndex); - - // Recompute the secrets (as recipient) to update indexes - const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); - const secrets = await Promise.all( - senders.map(sender => - DirectionalAppTaggingSecret.compute( - recipient, - ivsk, - sender.completeAddress.address, - contractAddress, - recipient.address, - ), - ), - ); - - // We set the last used indexes to WINDOW_HALF_SIZE so that next scan starts at WINDOW_HALF_SIZE + 1, - // which is outside the window, and for this reason no updates should be triggered. - const index = WINDOW_HALF_SIZE + 1; - await recipientTaggingDataProvider.setLastUsedIndexes(secrets.map(secret => ({ secret, index }))); - - await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - - // Only half of the logs should be synced since we start from index 1 = (11 - window_size), the other half should - // be skipped - await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS / 2); - - // Indexes should remain where we set them (window_size) - const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); - - expect(indexes).toHaveLength(NUM_SENDERS); - expect(indexes).toEqual([index, index, index, index, index, index, index, index, index, index]); - - // We should have called the node once and that is only for the first window - expect(aztecNode.getLogsByTags.mock.calls.length).toBe(1); - }); - - it('should sync tagged logs from scratch after a DB wipe', async () => { - const tagIndex = 0; - await generateMockLogs(tagIndex); - - // Recompute the secrets (as recipient) to update indexes - const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); - const secrets = await Promise.all( - senders.map(sender => - DirectionalAppTaggingSecret.compute( - recipient, - ivsk, - sender.completeAddress.address, - contractAddress, - recipient.address, - ), - ), - ); - - await recipientTaggingDataProvider.setLastUsedIndexes( - secrets.map(secret => ({ secret, index: WINDOW_HALF_SIZE + 2 })), - ); - - await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - - // No logs should be synced (and hence no capsules stored) since we start from index 2 = 12 - window_size - await expectPendingTaggedLogArrayLengthToBe(contractAddress, 0); - - // Since no logs were synced, window edge hash not been pushed and for this reason we should have called - // the node only once for the initial window - expect(aztecNode.getLogsByTags.mock.calls.length).toBe(1); - - aztecNode.getLogsByTags.mockClear(); - - // Wipe the database - await recipientTaggingDataProvider.resetNoteSyncData(); - - await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - - // First sender should have 2 logs, but keep index 0 since they were built using the same tag - // Next 4 senders should also have index 0 = offset - // Last 5 senders should have index 1 = offset + 1 - const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); - - expect(indexes).toHaveLength(NUM_SENDERS); - expect(indexes).toEqual([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]); - - // We should have called the node 2 times: - // first time during initial request, second time after pushing the edge of the window once - expect(aztecNode.getLogsByTags.mock.calls.length).toBe(2); - }); - - it('should not sync tagged logs with a blockNumber larger than the block number to which PXE is synced', async () => { - // We set the block number to which PXE is synced to a block number in which only the first batch of logs was - // emitted and then we check that we receive logs only from this batch. - await setSyncedBlockNumber(MIN_BLOCK_NUMBER_OF_A_LOG); - - const tagIndex = 0; - await generateMockLogs(tagIndex); - await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - - // Only NUM_SENDERS + 1 logs should be synched, since the rest have blockNumber > 1 - await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1); - }); - - const expectPendingTaggedLogArrayLengthToBe = async (contractAddress: AztecAddress, expectedLength: number) => { - // Capsule array length is stored in the array base slot. - const capsule = await capsuleDataProvider.loadCapsule(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - if (expectedLength === 0 && capsule === null) { - // If expected length is 0 we are fine with the capsule not existing since the array might not have been - // initialized yet. - return; - } - expect(capsule).toBeDefined(); - expect(capsule!.length).toBe(1); - expect(capsule![0].toNumber()).toBe(expectedLength); - }; - }); - describe('deliverEvent', () => { let blockNumber: BlockNumber; let eventSelector: EventSelector; @@ -750,211 +371,6 @@ describe('PXEOracleInterface', () => { }); }); - describe('utilityBulkRetrieveLogs', () => { - const unsiloedTag = Fr.random(); - const REQUEST_SLOT = Fr.random(); - const RESPONSE_SLOT = Fr.random(); - - beforeEach(() => { - aztecNode.getLogsByTags.mockReset(); - aztecNode.getTxEffect.mockReset(); - }); - - it('returns no logs if none are found', async () => { - aztecNode.getLogsByTags.mockResolvedValue([[]]); - - const request = new LogRetrievalRequest(contractAddress, unsiloedTag); - - await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); - await pxeOracleInterface.bulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT); - - expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); - - const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); - expect(responses.length).toEqual(1); - - // Check Option::none - expect(responses[0][0]).toEqual(new Fr(0)); // TODO: deserialize into option and check properly - }); - - it('returns a public log if one is found', async () => { - const scopedLog = await TxScopedL2Log.random(true); - (scopedLog.log as PublicLog).contractAddress = contractAddress; - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - - aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => - txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), - ); - - const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); - - await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); - await pxeOracleInterface.bulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT); - - expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); - - const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); - expect(responses.length).toEqual(1); - - // Check Option::some - expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly - }); - - it('returns a private log if one is found', async () => { - const scopedLog = await TxScopedL2Log.random(false); - scopedLog.log.fields[0] = await siloPrivateLog(contractAddress, Fr.random()); - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - aztecNode.getTxEffect.mockResolvedValue(indexedTxEffect); - - aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => - txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), - ); - - const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); - - await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); - await pxeOracleInterface.bulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT); - - expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); - - const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); - expect(responses.length).toEqual(1); - - // Check Option::some - expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly - }); - }); - - describe('getPublicLogByTag', () => { - const tag = Fr.random(); - - beforeEach(() => { - aztecNode.getLogsByTags.mockReset(); - aztecNode.getTxEffect.mockReset(); - }); - - it('returns null if no logs found for tag', async () => { - aztecNode.getLogsByTags.mockResolvedValue([[]]); - - const result = await pxeOracleInterface.getPublicLogByTag(tag, contractAddress); - expect(result).toBeNull(); - }); - - it('returns log data when single log found', async () => { - const scopedLog = await TxScopedL2Log.random(true); - const logContractAddress = (scopedLog.log as PublicLog).contractAddress; - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => - txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), - ); - - const result = (await pxeOracleInterface.getPublicLogByTag(tag, logContractAddress))!; - - expect(result.logPayload).toEqual(scopedLog.log.getEmittedFieldsWithoutTag()); - expect(result.uniqueNoteHashesInTx).toEqual(indexedTxEffect.data.noteHashes); - expect(result.txHash).toEqual(scopedLog.txHash); - expect(result.firstNullifierInTx).toEqual(indexedTxEffect.data.nullifiers[0]); - - expect(aztecNode.getLogsByTags).toHaveBeenCalledWith([tag]); - expect(aztecNode.getTxEffect).toHaveBeenCalledWith(scopedLog.txHash); - }); - - it('throws if multiple logs found for tag', async () => { - const scopedLog = await TxScopedL2Log.random(true); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog, scopedLog]]); - const logContractAddress = (scopedLog.log as PublicLog).contractAddress; - - await expect(pxeOracleInterface.getPublicLogByTag(tag, logContractAddress)).rejects.toThrow(/Got 2 logs for tag/); - }); - - it('throws if tx effect not found', async () => { - const scopedLog = await TxScopedL2Log.random(true); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - aztecNode.getTxEffect.mockResolvedValue(undefined); - const logContractAddress = (scopedLog.log as PublicLog).contractAddress; - - await expect(pxeOracleInterface.getPublicLogByTag(tag, logContractAddress)).rejects.toThrow( - /failed to retrieve tx effects/, - ); - }); - - it('returns log fields that are actually emitted', async () => { - const logContractAddress = await AztecAddress.random(); - const logPlaintext = [Fr.random()]; - const logContent = [tag, ...logPlaintext]; - - const log = PublicLog.from({ - contractAddress: logContractAddress, - fields: logContent, - }); - const scopedLogWithPadding = new TxScopedL2Log( - TxHash.random(), - randomInt(100), - randomInt(100), - BlockNumber(randomInt(100)), - L2BlockHash.random(), - log, - ); - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLogWithPadding]]); - aztecNode.getTxEffect.mockResolvedValue(await randomIndexedTxEffect()); - - const result = await pxeOracleInterface.getPublicLogByTag(tag, logContractAddress); - - expect(result?.logPayload).toEqual(logPlaintext); - }); - }); - - describe('getPrivateLogByTag', () => { - let tag: Fr; - - beforeEach(() => { - tag = Fr.random(); - }); - - it('returns null if no logs found', async () => { - aztecNode.getLogsByTags.mockResolvedValue([[]]); - const result = await pxeOracleInterface.getPrivateLogByTag(tag); - expect(result).toBeNull(); - }); - - it('returns log and tx effect if single log found', async () => { - const scopedLog = await TxScopedL2Log.random(false); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - aztecNode.getTxEffect.mockResolvedValue(indexedTxEffect); - - const result = await pxeOracleInterface.getPrivateLogByTag(tag); - - expect(result?.logPayload).toEqual(scopedLog.log.getEmittedFieldsWithoutTag()); - expect(result?.uniqueNoteHashesInTx).toEqual(indexedTxEffect.data.noteHashes); - expect(result?.txHash).toEqual(scopedLog.txHash); - expect(result?.firstNullifierInTx).toEqual(indexedTxEffect.data.nullifiers[0]); - expect(aztecNode.getTxEffect).toHaveBeenCalledWith(scopedLog.txHash); - }); - - it('throws if multiple logs found for tag', async () => { - const scopedLog = await TxScopedL2Log.random(false); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog, scopedLog]]); - - await expect(pxeOracleInterface.getPrivateLogByTag(tag)).rejects.toThrow(/Got 2 logs for tag/); - }); - - it('throws if tx effect not found', async () => { - const scopedLog = await TxScopedL2Log.random(false); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - aztecNode.getTxEffect.mockResolvedValue(undefined); - - await expect(pxeOracleInterface.getPrivateLogByTag(tag)).rejects.toThrow(/failed to retrieve tx effects/); - }); - }); - describe('syncNoteNullifiers', () => { let recipient: AztecAddress; diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index ba1efbf985a4..e830773c5b81 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -1,44 +1,21 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { createLogger } from '@aztec/foundation/log'; -import type { KeyStore } from '@aztec/key-store'; import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock } from '@aztec/stdlib/block'; -import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; +import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/client'; -import { - PendingTaggedLog, - PrivateLogWithTxData, - PublicLog, - PublicLogWithTxData, - TxScopedL2Log, -} from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import { TxHash } from '@aztec/stdlib/tx'; import type { ExecutionDataProvider } from '../contract_function_simulator/execution_data_provider.js'; -import type { AddressDataProvider } from '../storage/address_data_provider/address_data_provider.js'; import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; import type { CapsuleDataProvider } from '../storage/capsule_data_provider/capsule_data_provider.js'; -import type { ContractDataProvider } from '../storage/contract_data_provider/contract_data_provider.js'; import type { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; import type { PrivateEventDataProvider } from '../storage/private_event_data_provider/private_event_data_provider.js'; -import type { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js'; -import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js'; -import { - DirectionalAppTaggingSecret, - SiloedTag, - Tag, - WINDOW_HALF_SIZE, - getInitialIndexesMap, - getPreTagsForTheWindow, -} from '../tagging/index.js'; import { EventValidationRequest } from './noir-structs/event_validation_request.js'; -import { LogRetrievalRequest } from './noir-structs/log_retrieval_request.js'; -import { LogRetrievalResponse } from './noir-structs/log_retrieval_response.js'; import { NoteValidationRequest } from './noir-structs/note_validation_request.js'; -import { getCompleteAddress } from './oracle/common.js'; import type { ProxiedNode } from './proxied_node.js'; /** @@ -51,14 +28,9 @@ export class PXEOracleInterface implements ExecutionDataProvider { // no alternative way to access them in the PrivateExecutionOracle. constructor( public readonly aztecNode: AztecNode | ProxiedNode, - private keyStore: KeyStore, - private contractDataProvider: ContractDataProvider, private noteDataProvider: NoteDataProvider, private capsuleDataProvider: CapsuleDataProvider, private anchorBlockDataProvider: AnchorBlockDataProvider, - public readonly senderTaggingDataProvider: SenderTaggingDataProvider, - private recipientTaggingDataProvider: RecipientTaggingDataProvider, - private addressDataProvider: AddressDataProvider, private privateEventDataProvider: PrivateEventDataProvider, private log = createLogger('pxe:pxe_oracle_interface'), ) {} @@ -72,242 +44,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { return leafIndex?.data; } - /** - * Returns the last used tagging indexes along with the directional app tagging secrets for a given recipient and all - * the senders in the address book. - * This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration - * of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment, - * so we're keeping it private for now. - * @param contractAddress - The contract address to silo the secret for - * @param recipient - The address receiving the notes - * @returns A list of directional app tagging secrets along with the last used tagging indexes. If the corresponding - * secret was never used, the index is undefined. - * TODO(#17775): The naming here is broken as the function name does not reflect the return type. Make sure this gets - * fixed when implementing the linked issue. - */ - async #getLastUsedTaggingIndexesForSenders( - contractAddress: AztecAddress, - recipient: AztecAddress, - ): Promise<{ secret: DirectionalAppTaggingSecret; index: number | undefined }[]> { - const recipientCompleteAddress = await getCompleteAddress(recipient, this.addressDataProvider); - const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); - - // We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves - // (recipient = us, sender = us) - const senders = [ - ...(await this.recipientTaggingDataProvider.getSenderAddresses()), - ...(await this.keyStore.getAccounts()), - ].filter((address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address))); - const secrets = await Promise.all( - senders.map(contact => { - return DirectionalAppTaggingSecret.compute( - recipientCompleteAddress, - recipientIvsk, - contact, - contractAddress, - recipient, - ); - }), - ); - const indexes = await this.recipientTaggingDataProvider.getLastUsedIndexes(secrets); - if (indexes.length !== secrets.length) { - throw new Error('Indexes and directional app tagging secrets have different lengths'); - } - - return secrets.map((secret, i) => ({ - secret, - index: indexes[i], - })); - } - - // TODO(#17775): Replace this implementation of this function with one implementing an approach similar - // to syncSenderTaggingIndexes. Not done yet due to re-prioritization to devex and this doesn't directly affect - // devex. - public async syncTaggedLogs( - contractAddress: AztecAddress, - pendingTaggedLogArrayBaseSlot: Fr, - scopes?: AztecAddress[], - ) { - this.log.verbose('Searching for tagged logs', { contract: contractAddress }); - - const maxBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - - // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles. - // However it is impossible at the moment due to the language not supporting nested slices. - // This nesting is necessary because for a given set of tags we don't - // know how many logs we will get back. Furthermore, these logs are of undetermined - // length, since we don't really know the note they correspond to until we decrypt them. - - const recipients = scopes ? scopes : await this.keyStore.getAccounts(); - const contractName = await this.contractDataProvider.getDebugContractName(contractAddress); - for (const recipient of recipients) { - // Get all the secrets for the recipient and sender pairs (#9365) - const indexedSecrets = await this.#getLastUsedTaggingIndexesForSenders(contractAddress, recipient); - - // We fetch logs for a window of indexes in a range: - // . - // - // We use this window approach because it could happen that a sender might have messed up and inadvertently - // incremented their index without us getting any logs (for example, in case of a revert). If we stopped looking - // for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again. - // Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed - // some logs. For these reasons, we have to look both back and ahead of the stored index. - let secretsAndWindows = indexedSecrets.map(indexedSecret => { - if (indexedSecret.index === undefined) { - return { - secret: indexedSecret.secret, - leftMostIndex: 0, - rightMostIndex: WINDOW_HALF_SIZE, - }; - } else { - return { - secret: indexedSecret.secret, - leftMostIndex: Math.max(0, indexedSecret.index - WINDOW_HALF_SIZE), - rightMostIndex: indexedSecret.index + WINDOW_HALF_SIZE, - }; - } - }); - - // As we iterate we store the largest index we have seen for a given secret to later on store it in the db. - const newLargestIndexMapToStore: { [k: string]: number } = {}; - - // The initial/unmodified indexes of the secrets stored in a key-value map where key is the directional app - // tagging secret. - const initialIndexesMap = getInitialIndexesMap(indexedSecrets); - - while (secretsAndWindows.length > 0) { - const preTagsForTheWholeWindow = getPreTagsForTheWindow(secretsAndWindows); - const tagsForTheWholeWindow = await Promise.all( - preTagsForTheWholeWindow.map(async preTag => { - return SiloedTag.compute(await Tag.compute(preTag), contractAddress); - }), - ); - - // We store the new largest indexes we find in the iteration in the following map to later on construct - // a new set of secrets and windows to fetch logs for. - const newLargestIndexMapForIteration: { [k: string]: number } = {}; - - // Fetch the private logs for the tags and iterate over them - // TODO: The following conversion is unfortunate and we should most likely just type the #getPrivateLogsByTags - // to accept SiloedTag[] instead of Fr[]. That would result in a large change so I didn't do it yet. - const tagsForTheWholeWindowAsFr = tagsForTheWholeWindow.map(tag => tag.value); - const logsByTags = await this.#getPrivateLogsByTags(tagsForTheWholeWindowAsFr); - this.log.debug(`Found ${logsByTags.filter(logs => logs.length > 0).length} logs as recipient ${recipient}`, { - recipient, - contractName, - contractAddress, - }); - - for (let logIndex = 0; logIndex < logsByTags.length; logIndex++) { - const logsByTag = logsByTags[logIndex]; - if (logsByTag.length > 0) { - // We filter out the logs that are newer than the anchor block number of the tx currently being constructed - const filteredLogsByBlockNumber = logsByTag.filter(l => l.blockNumber <= maxBlockNumber); - - // We store the logs in capsules (to later be obtained in Noir) - await this.#storePendingTaggedLogs( - contractAddress, - pendingTaggedLogArrayBaseSlot, - recipient, - filteredLogsByBlockNumber, - ); - - // We retrieve the pre-tag corresponding to the log as I need that to evaluate whether - // a new largest index have been found. - const preTagCorrespondingToLog = preTagsForTheWholeWindow[logIndex]; - const initialIndex = initialIndexesMap[preTagCorrespondingToLog.secret.toString()]; - - if ( - preTagCorrespondingToLog.index >= initialIndex && - (newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] === undefined || - preTagCorrespondingToLog.index >= - newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()]) - ) { - // We have found a new largest index so we store it for later processing (storing it in the db + fetching - // the difference of the window sets of current and the next iteration) - newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] = - preTagCorrespondingToLog.index + 1; - - this.log.debug( - `Incrementing index to ${ - preTagCorrespondingToLog.index + 1 - } at contract ${contractName}(${contractAddress})`, - ); - } - } - } - - // Now based on the new largest indexes we found, we will construct a new secrets and windows set to fetch logs - // for. Note that it's very unlikely that a new log from the current window would appear between the iterations - // so we fetch the logs only for the difference of the window sets. - const newSecretsAndWindows = []; - for (const [directionalAppTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration)) { - const maybeIndexedSecret = indexedSecrets.find( - indexedSecret => indexedSecret.secret.toString() === directionalAppTaggingSecret, - ); - if (maybeIndexedSecret) { - newSecretsAndWindows.push({ - secret: maybeIndexedSecret.secret, - // We set the left most index to the new index to avoid fetching the same logs again - leftMostIndex: newIndex, - rightMostIndex: newIndex + WINDOW_HALF_SIZE, - }); - - // We store the new largest index in the map to later store it in the db. - newLargestIndexMapToStore[directionalAppTaggingSecret] = newIndex; - } else { - throw new Error( - `Secret not found for directionalAppTaggingSecret ${directionalAppTaggingSecret}. This is a bug as it should never happen!`, - ); - } - } - - // Now we set the new secrets and windows and proceed to the next iteration. - secretsAndWindows = newSecretsAndWindows; - } - - // At this point we have processed all the logs for the recipient so we store the last used indexes in the db. - // newLargestIndexMapToStore contains "next" indexes to look for (one past the last found), so subtract 1 to get - // last used. - await this.recipientTaggingDataProvider.setLastUsedIndexes( - Object.entries(newLargestIndexMapToStore).map(([directionalAppTaggingSecret, index]) => ({ - secret: DirectionalAppTaggingSecret.fromString(directionalAppTaggingSecret), - index: index - 1, - })), - ); - } - } - - async #storePendingTaggedLogs( - contractAddress: AztecAddress, - capsuleArrayBaseSlot: Fr, - recipient: AztecAddress, - privateLogs: TxScopedL2Log[], - ) { - // Build all pending tagged logs upfront with their tx effects - const pendingTaggedLogs = await Promise.all( - privateLogs.map(async scopedLog => { - // TODO(#9789): get these effects along with the log - const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); - if (!txEffect) { - throw new Error(`Could not find tx effect for tx hash ${scopedLog.txHash}`); - } - - const pendingTaggedLog = new PendingTaggedLog( - scopedLog.log.fields, - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - recipient, - ); - - return pendingTaggedLog.toFields(); - }), - ); - - return this.capsuleDataProvider.appendToCapsuleArray(contractAddress, capsuleArrayBaseSlot, pendingTaggedLogs); - } - public async validateEnqueuedNotesAndEvents( contractAddress: AztecAddress, noteValidationRequestsArrayBaseSlot: Fr, @@ -460,62 +196,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { } } - public async bulkRetrieveLogs( - contractAddress: AztecAddress, - logRetrievalRequestsArrayBaseSlot: Fr, - logRetrievalResponsesArrayBaseSlot: Fr, - ) { - // We read all log retrieval requests and process them all concurrently. This makes the process much faster as we - // don't need to wait for the network round-trip. - const logRetrievalRequests = ( - await this.capsuleDataProvider.readCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot) - ).map(LogRetrievalRequest.fromFields); - - const maybeLogRetrievalResponses = await Promise.all( - logRetrievalRequests.map(async request => { - // TODO(#14555): remove these internal functions and have node endpoints that do this instead - const [publicLog, privateLog] = await Promise.all([ - this.getPublicLogByTag(request.unsiloedTag, request.contractAddress), - this.getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag)), - ]); - - if (publicLog !== null) { - if (privateLog !== null) { - throw new Error( - `Found both a public and private log when searching for tag ${request.unsiloedTag} from contract ${request.contractAddress}`, - ); - } - - return new LogRetrievalResponse( - publicLog.logPayload, - publicLog.txHash, - publicLog.uniqueNoteHashesInTx, - publicLog.firstNullifierInTx, - ); - } else if (privateLog !== null) { - return new LogRetrievalResponse( - privateLog.logPayload, - privateLog.txHash, - privateLog.uniqueNoteHashesInTx, - privateLog.firstNullifierInTx, - ); - } else { - return null; - } - }), - ); - - // Requests are cleared once we're done. - await this.capsuleDataProvider.setCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot, []); - - // The responses are stored as Option in a second CapsuleArray. - await this.capsuleDataProvider.setCapsuleArray( - contractAddress, - logRetrievalResponsesArrayBaseSlot, - maybeLogRetrievalResponses.map(LogRetrievalResponse.toSerializedOption), - ); - } - async deliverEvent( contractAddress: AztecAddress, selector: EventSelector, @@ -575,74 +255,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { ); } - // TODO(#14555): delete this function and implement this behavior in the node instead - async getPublicLogByTag(tag: Fr, contractAddress: AztecAddress): Promise { - const logs = await this.#getPublicLogsByTagsFromContract([tag], contractAddress); - const logsForTag = logs[0]; - - this.log.debug(`Got ${logsForTag.length} public logs for tag ${tag}`); - - if (logsForTag.length == 0) { - return null; - } else if (logsForTag.length > 1) { - // TODO(#11627): handle this case - throw new Error( - `Got ${logsForTag.length} logs for tag ${tag} and contract ${contractAddress.toString()}. getPublicLogByTag currently only supports a single log per tag`, - ); - } - - const scopedLog = logsForTag[0]; - - // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so - // we need to make a second call to the node for `getTxEffect`. - // TODO(#9789): bundle this information in the `getLogsByTag` call. - const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); - if (txEffect == undefined) { - throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); - } - - return new PublicLogWithTxData( - scopedLog.log.getEmittedFieldsWithoutTag(), - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - ); - } - - // TODO(#14555): delete this function and implement this behavior in the node instead - async getPrivateLogByTag(siloedTag: Fr): Promise { - const logs = await this.#getPrivateLogsByTags([siloedTag]); - const logsForTag = logs[0]; - - this.log.debug(`Got ${logsForTag.length} private logs for tag ${siloedTag}`); - - if (logsForTag.length == 0) { - return null; - } else if (logsForTag.length > 1) { - // TODO(#11627): handle this case - throw new Error( - `Got ${logsForTag.length} logs for tag ${siloedTag}. getPrivateLogByTag currently only supports a single log per tag`, - ); - } - - const scopedLog = logsForTag[0]; - - // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so - // we need to make a second call to the node for `getTxEffect`. - // TODO(#9789): bundle this information in the `getLogsByTag` call. - const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); - if (txEffect == undefined) { - throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); - } - - return new PrivateLogWithTxData( - scopedLog.log.getEmittedFieldsWithoutTag(), - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - ); - } - /** * Looks for nullifiers of active contract notes and marks them as nullified if a nullifier is found. * @@ -703,21 +315,4 @@ export class PXEOracleInterface implements ExecutionDataProvider { }); }); } - - // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This - // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. - async #getPrivateLogsByTags(tags: Fr[]): Promise { - const allLogs = await this.aztecNode.getLogsByTags(tags); - return allLogs.map(logs => logs.filter(log => !log.isFromPublic)); - } - - // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This - // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. - async #getPublicLogsByTagsFromContract(tags: Fr[], contractAddress: AztecAddress): Promise { - const allLogs = await this.aztecNode.getLogsByTags(tags); - const allPublicLogs = allLogs.map(logs => logs.filter(log => log.isFromPublic)); - return allPublicLogs.map(logs => - logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress)), - ); - } } diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index addcc8aabf91..16bec5eea235 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -197,14 +197,9 @@ export class PXE { const pxeOracleInterface = new PXEOracleInterface( ProxiedNodeFactory.create(this.node), - this.keyStore, - proxyContractDataProvider, this.noteDataProvider, this.capsuleDataProvider, this.anchorBlockDataProvider, - this.senderTaggingDataProvider, - this.recipientTaggingDataProvider, - this.addressDataProvider, this.privateEventDataProvider, this.log, ); @@ -217,6 +212,7 @@ export class PXE { this.node, this.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.recipientTaggingDataProvider, this.capsuleDataProvider, this.simulator, ); 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 cd92c2456dd5..dab34423839e 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 @@ -18,6 +18,7 @@ import { NoteDataProvider, ORACLE_VERSION, PXEOracleInterface, + RecipientTaggingDataProvider, SenderTaggingDataProvider, enrichPublicSimulationError, getFunctionArtifact, @@ -100,6 +101,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl private addressDataProvider: AddressDataProvider, private accountDataProvider: TXEAccountDataProvider, private senderTaggingDataProvider: SenderTaggingDataProvider, + private recipientTaggingDataProvider: RecipientTaggingDataProvider, private capsuleDataProvider: CapsuleDataProvider, private pxeOracleInterface: PXEOracleInterface, private nextBlockTimestamp: bigint, @@ -316,6 +318,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.recipientTaggingDataProvider, this.capsuleDataProvider, 0, 1, @@ -638,6 +641,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.recipientTaggingDataProvider, this.capsuleDataProvider, ); const acirExecutionResult = await new WASMSimulator() diff --git a/yarn-project/txe/src/txe_session.test.ts b/yarn-project/txe/src/txe_session.test.ts index c3e9424471a7..878663bb0304 100644 --- a/yarn-project/txe/src/txe_session.test.ts +++ b/yarn-project/txe/src/txe_session.test.ts @@ -17,6 +17,7 @@ describe('TXESession.processFunction', () => { {} as any, // addressDataProvider {} as any, // accountDataProvider {} as any, // senderTaggingDataProvider + {} as any, // recipientTaggingDataProvider {} as any, // capsuleDataProvider new Fr(1), // chainId new Fr(1), // version diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 6c857a6daa8a..bb439bda5812 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -126,6 +126,7 @@ export class TXESession implements TXESessionStateHandler { private addressDataProvider: AddressDataProvider, private accountDataProvider: TXEAccountDataProvider, private senderTaggingDataProvider: SenderTaggingDataProvider, + private recipientTaggingDataProvider: RecipientTaggingDataProvider, private capsuleDataProvider: CapsuleDataProvider, private chainId: Fr, private version: Fr, @@ -160,14 +161,9 @@ export class TXESession implements TXESessionStateHandler { const pxeOracleInterface = new PXEOracleInterface( stateMachine.node, - keyStore, - contractDataProvider, noteDataProvider, capsuleDataProvider, stateMachine.anchorBlockDataProvider, - senderTaggingDataProvider, - recipientTaggingDataProvider, - addressDataProvider, privateEventDataProvider, ); @@ -179,6 +175,7 @@ export class TXESession implements TXESessionStateHandler { addressDataProvider, accountDataProvider, senderTaggingDataProvider, + recipientTaggingDataProvider, capsuleDataProvider, pxeOracleInterface, nextBlockTimestamp, @@ -198,6 +195,7 @@ export class TXESession implements TXESessionStateHandler { addressDataProvider, accountDataProvider, senderTaggingDataProvider, + recipientTaggingDataProvider, capsuleDataProvider, version, chainId, @@ -267,6 +265,7 @@ export class TXESession implements TXESessionStateHandler { this.addressDataProvider, this.accountDataProvider, this.senderTaggingDataProvider, + this.recipientTaggingDataProvider, this.capsuleDataProvider, this.pxeOracleInterface, this.nextBlockTimestamp, @@ -328,6 +327,7 @@ export class TXESession implements TXESessionStateHandler { this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.recipientTaggingDataProvider, this.capsuleDataProvider, ); @@ -391,6 +391,7 @@ export class TXESession implements TXESessionStateHandler { this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, this.senderTaggingDataProvider, + this.recipientTaggingDataProvider, this.capsuleDataProvider, ); From c99cdf60ec603a37a0ea2006a2e54a2810b9d767 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 12:05:13 +0000 Subject: [PATCH 026/140] getNullifierIndex --- .../execution_data_provider.ts | 7 ------- .../contract_function_simulator/oracle/common.ts | 14 ++++++++++++++ .../oracle/utility_execution_oracle.ts | 3 ++- .../pxe_oracle_interface.ts | 11 +---------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index ed6581197033..b10484702e67 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -35,13 +35,6 @@ export type ExecutionStats = { * The interface for the data layer required to perform private and utility execution. */ export interface ExecutionDataProvider { - /** - * Gets the index of a nullifier in the nullifier tree. - * @param nullifier - The nullifier. - * @returns - The index of the nullifier. Undefined if it does not exist in the tree. - */ - getNullifierIndex(nullifier: Fr): Promise; - /** * Validates all note and event validation requests enqueued via `enqueue_note_for_validation` and * `enqueue_event_for_validation`, inserting them into the note database and event store respectively, making them diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 003c55cbac91..073e2333ed4e 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -662,3 +662,17 @@ export async function bulkRetrieveLogs( maybeLogRetrievalResponses.map(LogRetrievalResponse.toSerializedOption), ); } + +export async function getNullifierIndex(nullifier: Fr, aztecNode: AztecNode) { + return await findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier, aztecNode); +} + +async function findLeafIndex( + blockNumber: BlockParameter, + treeId: MerkleTreeId, + leafValue: Fr, + aztecNode: AztecNode, +): Promise { + const [leafIndex] = await aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]); + return leafIndex?.data; +} 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 cd1ba289fe9e..cedde0b53556 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 @@ -36,6 +36,7 @@ import { getLowNullifierMembershipWitness, getMembershipWitness, getNotes, + getNullifierIndex, getNullifierMembershipWitness, getPublicDataWitness, getPublicStorageAt, @@ -265,7 +266,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra */ public async utilityCheckNullifierExists(innerNullifier: Fr) { const nullifier = await siloNullifier(this.contractAddress, innerNullifier!); - const index = await this.executionDataProvider.getNullifierIndex(nullifier); + const index = await getNullifierIndex(nullifier, this.aztecNode); return index !== undefined; } diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index e830773c5b81..35919e0cfc8c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -2,7 +2,7 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { createLogger } from '@aztec/foundation/log'; import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter, DataInBlock } from '@aztec/stdlib/block'; +import type { DataInBlock } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/client'; import { Note, NoteDao } from '@aztec/stdlib/note'; @@ -35,15 +35,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { private log = createLogger('pxe:pxe_oracle_interface'), ) {} - async getNullifierIndex(nullifier: Fr) { - return await this.#findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier); - } - - async #findLeafIndex(blockNumber: BlockParameter, treeId: MerkleTreeId, leafValue: Fr): Promise { - const [leafIndex] = await this.aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]); - return leafIndex?.data; - } - public async validateEnqueuedNotesAndEvents( contractAddress: AztecAddress, noteValidationRequestsArrayBaseSlot: Fr, From 86db9f59348dcc941cd966630cc8a2a9d45071e6 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 12:07:30 +0000 Subject: [PATCH 027/140] remove aztec node getter from PXEOracleInterface --- .../contract_function_simulator/execution_data_provider.ts | 4 ---- .../oracle/private_execution_oracle.ts | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index b10484702e67..459453acc1fc 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -1,6 +1,5 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import type { NodeStats } from '@aztec/stdlib/tx'; /** @@ -55,7 +54,4 @@ export interface ExecutionDataProvider { * Looks for nullifiers of active contract notes and marks them as nullified in the db if a nullifier is found. */ syncNoteNullifiers(contractAddress: AztecAddress): Promise; - - // Exposed when moving in the direction of #17776 - get aztecNode(): AztecNode; } diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index f8cd5ebf5e4e..0c4761b0b95a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -265,13 +265,10 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP if (lastUsedIndexInTx !== undefined) { return lastUsedIndexInTx + 1; } else { - // TODO(#17776): Don't access the Aztec node and senderTaggingDataProvider via the executionDataProvider. - const aztecNode = this.executionDataProvider.aztecNode; - // This is a tagging secret we've not yet used in this tx, so first sync our store to make sure its indices // are up to date. We do this here because this store is not synced as part of the global sync because // that'd be wasteful as most tagging secrets are not used in each tx. - await syncSenderTaggingIndexes(secret, this.contractAddress, aztecNode, this.senderTaggingDataProvider); + await syncSenderTaggingIndexes(secret, this.contractAddress, this.aztecNode, this.senderTaggingDataProvider); const lastUsedIndex = await this.senderTaggingDataProvider.getLastUsedIndex(secret); // If lastUsedIndex is undefined, we've never used this secret, so start from 0 From ba6c7ed741211df21af7b139c6bc1bf4eb9d4b0a Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 12:37:39 +0000 Subject: [PATCH 028/140] note and event validation --- .../contract_function_simulator.ts | 4 + .../execution_data_provider.ts | 17 - .../oracle/common.test.ts | 337 +++++++++++++++++- .../oracle/common.ts | 236 +++++++++++- .../oracle/oracle_version_is_checked.test.ts | 4 + .../oracle/private_execution.test.ts | 4 + .../oracle/private_execution_oracle.ts | 4 + .../oracle/utility_execution.test.ts | 4 + .../oracle/utility_execution_oracle.ts | 10 +- .../pxe_oracle_interface.test.ts | 319 +---------------- .../pxe_oracle_interface.ts | 221 ------------ yarn-project/pxe/src/pxe.ts | 3 +- .../oracle/txe_oracle_top_level_context.ts | 4 + yarn-project/txe/src/txe_session.test.ts | 1 + yarn-project/txe/src/txe_session.ts | 8 +- 15 files changed, 606 insertions(+), 570 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index a7817c6f756c..ede60821473b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -77,6 +77,7 @@ import type { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../storage/index.js'; @@ -107,6 +108,7 @@ export class ContractFunctionSimulator { private senderTaggingDataProvider: SenderTaggingDataProvider, private recipientTaggingDataProvider: RecipientTaggingDataProvider, private capsuleDataProvider: CapsuleDataProvider, + private privateEventDataProvider: PrivateEventDataProvider, private simulator: CircuitSimulator, ) { this.log = createLogger('simulator'); @@ -190,6 +192,7 @@ export class ContractFunctionSimulator { this.senderTaggingDataProvider, this.recipientTaggingDataProvider, this.capsuleDataProvider, + this.privateEventDataProvider, 0, // totalPublicArgsCount startSideEffectCounter, undefined, // log @@ -290,6 +293,7 @@ export class ContractFunctionSimulator { this.senderTaggingDataProvider, this.recipientTaggingDataProvider, this.capsuleDataProvider, + this.privateEventDataProvider, undefined, scopes, ); diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 459453acc1fc..20e1423d124d 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -1,4 +1,3 @@ -import type { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { NodeStats } from '@aztec/stdlib/tx'; @@ -34,22 +33,6 @@ export type ExecutionStats = { * The interface for the data layer required to perform private and utility execution. */ export interface ExecutionDataProvider { - /** - * Validates all note and event validation requests enqueued via `enqueue_note_for_validation` and - * `enqueue_event_for_validation`, inserting them into the note database and event store respectively, making them - * queryable via `get_notes` and `getPrivateEvents`. - * - * This automatically clears both validation request queues, so no further work needs to be done by the caller. - * @param contractAddress - The address of the contract that the logs are tagged for. - * @param noteValidationRequestsArrayBaseSlot - The base slot of capsule array containing note validation requests. - * @param eventValidationRequestsArrayBaseSlot - The base slot of capsule array containing event validation requests. - */ - validateEnqueuedNotesAndEvents( - contractAddress: AztecAddress, - noteValidationRequestsArrayBaseSlot: Fr, - eventValidationRequestsArrayBaseSlot: Fr, - ): Promise; - /** * Looks for nullifiers of active contract notes and marks them as nullified in the db if a nullifier is found. */ diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index ae49707c936f..687f07a21c35 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -2,13 +2,22 @@ import { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; +import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; -import { siloPrivateLog } from '@aztec/stdlib/hash'; +import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; -import { BlockHeader, GlobalVariables, TxHash, randomIndexedTxEffect } from '@aztec/stdlib/tx'; +import { MerkleTreeId } from '@aztec/stdlib/trees'; +import { + BlockHeader, + GlobalVariables, + type IndexedTxEffect, + TxEffect, + TxHash, + randomIndexedTxEffect, +} from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; import { randomInt } from 'crypto'; @@ -19,10 +28,14 @@ import { AnchorBlockDataProvider, CapsuleDataProvider, ContractDataProvider, + NoteDataProvider, + PrivateEventDataProvider, } from '../../storage/index.js'; import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { bulkRetrieveLogs, + deliverEvent, + deliverNote, getBlock, getLowNullifierMembershipWitness, getPrivateLogByTag, @@ -41,6 +54,8 @@ describe('Common oracle functions', () => { let anchorBlockDataProvider: AnchorBlockDataProvider; let keyStore: KeyStore; let capsuleDataProvider: CapsuleDataProvider; + let privateEventDataProvider: PrivateEventDataProvider; + let noteDataProvider: NoteDataProvider; let recipient: CompleteAddress; let contractAddress: AztecAddress; @@ -57,7 +72,9 @@ describe('Common oracle functions', () => { addressDataProvider = new AddressDataProvider(store); anchorBlockDataProvider = new AnchorBlockDataProvider(store); keyStore = new KeyStore(store); + privateEventDataProvider = new PrivateEventDataProvider(store); capsuleDataProvider = new CapsuleDataProvider(store); + noteDataProvider = await NoteDataProvider.create(store); // Set up recipient account recipient = await keyStore.addAccount(new Fr(69), Fr.random()); @@ -324,6 +341,322 @@ describe('Common oracle functions', () => { }); }); + describe('deliverEvent', () => { + let blockNumber: BlockNumber; + let eventSelector: EventSelector; + let eventContent: Fr[]; + let eventCommitment: Fr; + let eventNullifier: Fr; + let txEffect: TxEffect; + let indexedTxEffect: IndexedTxEffect; + + // beforeEach sets up the happy path case, so error modes are tested + // by minimally failing happy path conditions + beforeEach(async () => { + blockNumber = BlockNumber(42); + eventSelector = EventSelector.random(); + eventContent = [Fr.random(), Fr.random()]; + + eventCommitment = Fr.random(); + eventNullifier = await siloNullifier(contractAddress, eventCommitment); + + txEffect = TxEffect.from({ + ...(await TxEffect.random()), + nullifiers: [eventNullifier], + }); + + indexedTxEffect = { + l2BlockNumber: blockNumber, + l2BlockHash: L2BlockHash.random(), + data: txEffect, + txIndexInBlock: 0, + }; + + /* Happy path context conditions: + ** - PXE is sync'd to _at least_ block including tx + ** - Node knows tx effect + ** - Node knows siloed event commitment + */ + await setSyncedBlockNumber(blockNumber); + + aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(indexedTxEffect)); + + aztecNode.findLeavesIndexes.mockImplementation(() => + Promise.resolve([ + { + data: BigInt(0), + l2BlockNumber: indexedTxEffect.l2BlockNumber, + l2BlockHash: indexedTxEffect.l2BlockHash, + }, + ]), + ); + }); + + function runDeliverEvent( + overrides: { + eventCommitment?: Fr; + } = {}, + ) { + return deliverEvent( + contractAddress, + eventSelector, + eventContent, + overrides.eventCommitment || eventCommitment, + txEffect.txHash, + recipient.address, + anchorBlockDataProvider, + aztecNode, + privateEventDataProvider, + ); + } + + it('should throw when tx does not exist or has no effects', async () => { + aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(undefined)); + await expect(runDeliverEvent).rejects.toThrow(/Could not find tx effect for tx hash/); + }); + + it('should throw when tx block has not yet been synchronized', async () => { + indexedTxEffect = { + ...indexedTxEffect, + l2BlockNumber: BlockNumber(blockNumber + 1), + }; + aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(indexedTxEffect)); + + await expect(runDeliverEvent).rejects.toThrow(/Could not find tx effect for tx hash .* as of block number/); + }); + + it('should throw if event is not in tx effects', async () => { + await expect(runDeliverEvent({ eventCommitment: Fr.random() })).rejects.toThrow( + /Event commitment .* is not present in tx/, + ); + }); + + it('should throw if event is not in nullifiers', async () => { + aztecNode.findLeavesIndexes.mockImplementation(() => Promise.resolve([])); + + await expect(runDeliverEvent).rejects.toThrow(/Event commitment .* is not present on the nullifier tree/); + }); + + it('should store event for later retrieval', async () => { + await runDeliverEvent(); + + // I should be able to retrieve the private event I just saved using getPrivateEvents + const result = await privateEventDataProvider.getPrivateEvents(eventSelector, { + contractAddress, + fromBlock: blockNumber, + toBlock: blockNumber + 1, + scopes: [recipient.address], + }); + + expect(result.length).toEqual(1); + expect(result[0].packedEvent).toEqual(eventContent); + }); + }); + + describe('deliverNote', () => { + // Recipient is different from the owner because recipient refers to the + // recipient of the message containing the note, while owner refers to the + // owner of the note. + let owner: AztecAddress; + let storageSlot: Fr; + let randomness: Fr; + let noteNonce: Fr; + let content: Fr[]; + + let noteHash: Fr; + let uniqueNoteHash: Fr; + let nullifier: Fr; + let siloedNullifier: Fr; + + let txHash: TxHash; + let txEffect: TxEffect; + let indexedTxEffect: IndexedTxEffect; + let blockNumber: BlockNumber; + + let nullified = false; + + // beforeEach sets up the happy path case, so error modes are tested + // by minimally failing happy path conditions + beforeEach(async () => { + noteHash = Fr.random(); + nullifier = Fr.random(); + txHash = TxHash.random(); + owner = await AztecAddress.random(); + storageSlot = Fr.random(); + randomness = Fr.random(); + noteNonce = Fr.random(); + content = [Fr.random(), Fr.random()]; + + uniqueNoteHash = await computeUniqueNoteHash(noteNonce, await siloNoteHash(contractAddress, noteHash)); + siloedNullifier = await siloNullifier(contractAddress, nullifier); + + blockNumber = BlockNumber(42); + + txEffect = TxEffect.from({ + ...(await TxEffect.random()), + noteHashes: [uniqueNoteHash], + }); + + indexedTxEffect = { + l2BlockNumber: blockNumber, + l2BlockHash: L2BlockHash.random(), + data: txEffect, + txIndexInBlock: 0, + }; + + /* Happy path context conditions: + ** - PXE is sync'd to _at least_ block including tx + ** - Node knows tx effect + ** - Node knows unique note hash (and siloed nullifier if requested) + */ + await setSyncedBlockNumber(blockNumber); + + aztecNode.getTxEffect.mockImplementation(queryTxHash => + Promise.resolve(queryTxHash == txHash ? indexedTxEffect : undefined), + ); + + aztecNode.findLeavesIndexes.mockImplementation((queryBlockNum, treeId, leaves) => { + if (queryBlockNum != blockNumber) { + throw new Error(`Got a tree query for block ${queryBlockNum} but synced block is ${blockNumber}`); + } + + if (treeId == MerkleTreeId.NOTE_HASH_TREE && leaves[0].equals(uniqueNoteHash)) { + return Promise.resolve([ + { + data: BigInt(0), + l2BlockNumber: indexedTxEffect.l2BlockNumber, + l2BlockHash: indexedTxEffect.l2BlockHash, + }, + ]); + } else if (treeId == MerkleTreeId.NULLIFIER_TREE && leaves[0].equals(siloedNullifier)) { + // Note that returning undefined (i.e. the un-nullified case) covers both scenarios where the note has not + // been nullified and where the nullifier is in a block past the synced block. + return Promise.resolve([ + nullified + ? { + data: BigInt(0), + l2BlockNumber: indexedTxEffect.l2BlockNumber, + l2BlockHash: indexedTxEffect.l2BlockHash, + } + : undefined, + ]); + } else { + throw new Error(); + } + }); + }); + + it('should store note if it exists in note hash tree and is not nullified', async () => { + await deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + noteHash, + nullifier, + txHash, + recipient.address, + anchorBlockDataProvider, + aztecNode, + noteDataProvider, + ); + + // Verify note was stored + const notes = await noteDataProvider.getNotes({ contractAddress, scopes: [recipient.address] }); + + expect(notes).toHaveLength(1); + expect(notes[0].noteHash.equals(noteHash)).toBe(true); + }); + + it('should throw if tx hash does not exist', async () => { + await expect( + deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + noteHash, + nullifier, + TxHash.random(), + recipient.address, + anchorBlockDataProvider, + aztecNode, + noteDataProvider, + ), + ).rejects.toThrow(/Could not find tx effect/); + }); + + it('should throw if note was not emitted in the tx', async () => { + await expect( + deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + Fr.random(), // note hash + nullifier, + txHash, + recipient.address, + anchorBlockDataProvider, + aztecNode, + noteDataProvider, + ), + ).rejects.toThrow(/is not present in tx/); + }); + + it('should throw if tx was mined after synced block number', async () => { + await setSyncedBlockNumber(BlockNumber(blockNumber - 1)); + + await expect( + deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + noteHash, + nullifier, + txHash, + recipient.address, + anchorBlockDataProvider, + aztecNode, + noteDataProvider, + ), + ).rejects.toThrow(/as of block number/); + }); + + it('should store and immediately remove note if it is already nullified', async () => { + nullified = true; + + await deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + noteHash, + nullifier, + txHash, + recipient.address, + anchorBlockDataProvider, + aztecNode, + noteDataProvider, + ); + + // Verify note was removed + const notes = await noteDataProvider.getNotes({ contractAddress, scopes: [recipient.address] }); + expect(notes).toHaveLength(0); + }); + }); + const setSyncedBlockNumber = (blockNumber: BlockNumber) => { return anchorBlockDataProvider.setHeader( BlockHeader.empty({ diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 073e2333ed4e..fa7f85cae330 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -2,11 +2,11 @@ import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { KeyStore } from '@aztec/key-store'; -import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; +import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; -import { siloPrivateLog } from '@aztec/stdlib/hash'; +import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { computeAddressSecret } from '@aztec/stdlib/keys'; import { @@ -19,22 +19,26 @@ import { deriveEcdhSharedSecret, } from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; -import type { NoteStatus } from '@aztec/stdlib/note'; +import { Note, NoteDao, type NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; +import type { TxHash } from '@aztec/stdlib/tx'; import { ORACLE_VERSION } from '../../oracle_version.js'; -import type { - AddressDataProvider, +import { + type AddressDataProvider, AnchorBlockDataProvider, - CapsuleDataProvider, - ContractDataProvider, - NoteDataProvider, - RecipientTaggingDataProvider, + type CapsuleDataProvider, + type ContractDataProvider, + type NoteDataProvider, + PrivateEventDataProvider, + type RecipientTaggingDataProvider, } from '../../storage/index.js'; import { SiloedTag, Tag, WINDOW_HALF_SIZE, getInitialIndexesMap, getPreTagsForTheWindow } from '../../tagging/index.js'; import type { ExecutionStats } from '../execution_data_provider.js'; +import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; +import { NoteValidationRequest } from '../noir-structs/note_validation_request.js'; import type { ProxiedNode } from '../proxied_node.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; @@ -676,3 +680,217 @@ async function findLeafIndex( const [leafIndex] = await aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]); return leafIndex?.data; } + +export async function validateEnqueuedNotesAndEvents( + contractAddress: AztecAddress, + noteValidationRequestsArrayBaseSlot: Fr, + eventValidationRequestsArrayBaseSlot: Fr, + capsuleDataProvider: CapsuleDataProvider, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, + noteDataProvider: NoteDataProvider, + privateEventDataProvider: PrivateEventDataProvider, +): Promise { + // We read all note and event validation requests and process them all concurrently. This makes the process much + // faster as we don't need to wait for the network round-trip. + const noteValidationRequests = ( + await capsuleDataProvider.readCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot) + ).map(NoteValidationRequest.fromFields); + + const eventValidationRequests = ( + await capsuleDataProvider.readCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot) + ).map(EventValidationRequest.fromFields); + + const noteDeliveries = noteValidationRequests.map(request => + deliverNote( + request.contractAddress, + request.owner, + request.storageSlot, + request.randomness, + request.noteNonce, + request.content, + request.noteHash, + request.nullifier, + request.txHash, + request.recipient, + anchorBlockDataProvider, + aztecNode, + noteDataProvider, + ), + ); + + const eventDeliveries = eventValidationRequests.map(request => + deliverEvent( + request.contractAddress, + request.eventTypeId, + request.serializedEvent, + request.eventCommitment, + request.txHash, + request.recipient, + anchorBlockDataProvider, + aztecNode, + privateEventDataProvider, + ), + ); + + await Promise.all([...noteDeliveries, ...eventDeliveries]); + + // Requests are cleared once we're done. + await capsuleDataProvider.setCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot, []); + await capsuleDataProvider.setCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot, []); +} + +export async function deliverNote( + contractAddress: AztecAddress, + owner: AztecAddress, + storageSlot: Fr, + randomness: Fr, + noteNonce: Fr, + content: Fr[], + noteHash: Fr, + nullifier: Fr, + txHash: TxHash, + recipient: AztecAddress, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, + noteDataProvider: NoteDataProvider, +): Promise { + // We are going to store the new note in the NoteDataProvider, which will let us later return it via `getNotes`. + // There's two things we need to check before we do this however: + // - we must make sure the note does actually exist in the note hash tree + // - we need to check if the note has already been nullified + // + // Failing to do either of the above would result in circuits getting either non-existent notes and failing to + // produce inclusion proofs for them, or getting nullified notes and producing duplicate nullifiers, both of which + // are catastrophic failure modes. + // + // Note that adding a note and removing it is *not* equivalent to never adding it in the first place. A nullifier + // emitted in a block that comes after note creation might result in the note being de-nullified by a chain reorg, + // so we must store both the note hash and nullifier block information. + + // We avoid making node queries at 'latest' since we don't want to process notes or nullifiers that only exist ahead + // in time of the locally synced state. + // Note that while this technically results in historical queries, we perform it at the latest locally synced block + // number which *should* be recent enough to be available, even for non-archive nodes. + // Also note that the note should never be ahead of the synced block here since `fetchTaggedLogs` only processes + // logs up to the synced block making this only an additional safety check. + const syncedBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + + // By computing siloed and unique note hashes ourselves we prevent contracts from interfering with the note storage + // of other contracts, which would constitute a security breach. + const uniqueNoteHash = await computeUniqueNoteHash(noteNonce, await siloNoteHash(contractAddress, noteHash)); + const siloedNullifier = await siloNullifier(contractAddress, nullifier); + + const txEffect = await aztecNode.getTxEffect(txHash); + if (!txEffect) { + throw new Error(`Could not find tx effect for tx hash ${txHash}`); + } + + if (txEffect.l2BlockNumber > syncedBlockNumber) { + throw new Error(`Could not find tx effect for tx hash ${txHash} as of block number ${syncedBlockNumber}`); + } + + const noteInTx = txEffect.data.noteHashes.some(nh => nh.equals(uniqueNoteHash)); + if (!noteInTx) { + throw new Error(`Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present in tx ${txHash}`); + } + + // We store notes by their index in the global note hash tree, which has the convenient side effect of validating + // note existence in said tree. We concurrently also check if the note's nullifier exists, performing all node + // queries in a single round-trip. + const [[uniqueNoteHashTreeIndexInBlock], [nullifierIndex]] = await Promise.all([ + aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash]), + aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, [siloedNullifier]), + ]); + + if (uniqueNoteHashTreeIndexInBlock === undefined) { + throw new Error( + `Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present on the tree at block ${syncedBlockNumber} (from tx ${txHash})`, + ); + } + + const noteDao = new NoteDao( + new Note(content), + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + noteHash, + siloedNullifier, + txHash, + uniqueNoteHashTreeIndexInBlock.l2BlockNumber, + uniqueNoteHashTreeIndexInBlock.l2BlockHash.toString(), + uniqueNoteHashTreeIndexInBlock.data, + ); + + // The note was found by `recipient`, so we use that as the scope when storing the note. + await noteDataProvider.addNotes([noteDao], recipient); + + if (nullifierIndex !== undefined) { + const { data: _, ...blockHashAndNum } = nullifierIndex; + await noteDataProvider.applyNullifiers([{ data: siloedNullifier, ...blockHashAndNum }]); + } +} + +export async function deliverEvent( + contractAddress: AztecAddress, + selector: EventSelector, + content: Fr[], + eventCommitment: Fr, + txHash: TxHash, + scope: AztecAddress, + anchorBlockDataProvider: AnchorBlockDataProvider, + aztecNode: AztecNode, + privateEventDataProvider: PrivateEventDataProvider, +): Promise { + // While using 'latest' block number would be fine for private events since they cannot be accessed from Aztec.nr + // (and thus we're less concerned about being ahead of the synced block), we use the synced block number to + // maintain consistent behavior in the PXE. Additionally, events should never be ahead of the synced block here + // since `fetchTaggedLogs` only processes logs up to the synced block. + const [syncedBlockHeader, siloedEventCommitment, txEffect] = await Promise.all([ + anchorBlockDataProvider.getBlockHeader(), + siloNullifier(contractAddress, eventCommitment), + aztecNode.getTxEffect(txHash), + ]); + + const syncedBlockNumber = syncedBlockHeader.getBlockNumber(); + + if (!txEffect) { + throw new Error(`Could not find tx effect for tx hash ${txHash}`); + } + + if (txEffect.l2BlockNumber > syncedBlockNumber) { + throw new Error(`Could not find tx effect for tx hash ${txHash} as of block number ${syncedBlockNumber}`); + } + + const eventInTx = txEffect.data.nullifiers.some(n => n.equals(siloedEventCommitment)); + if (!eventInTx) { + throw new Error( + `Event commitment ${eventCommitment} (siloed as ${siloedEventCommitment}) is not present in tx ${txHash}`, + ); + } + + const [nullifierIndex] = await aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, [ + siloedEventCommitment, + ]); + + if (nullifierIndex === undefined) { + throw new Error( + `Event commitment ${eventCommitment} (siloed as ${siloedEventCommitment}) is not present on the nullifier tree at block ${syncedBlockNumber} (from tx ${txHash})`, + ); + } + + return privateEventDataProvider.storePrivateEventLog( + selector, + content, + Number(nullifierIndex.data), // Index of the event commitment in the nullifier tree + { + contractAddress, + scope, + txHash, + l2BlockNumber: nullifierIndex.l2BlockNumber, // Block number in which the event was emitted + l2BlockHash: nullifierIndex.l2BlockHash, // Block hash in which the event was emitted + }, + ); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 7b3296054b2a..aaf46ec1d350 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -18,6 +18,7 @@ import { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; @@ -38,6 +39,7 @@ describe('Oracle Version Check test suite', () => { let senderTaggingDataProvider: ReturnType>; let recipientTaggingDataProvider: ReturnType>; let capsuleDataProvider: ReturnType>; + let privateEventDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; let anchorBlockHeader: BlockHeader; @@ -56,6 +58,7 @@ describe('Oracle Version Check test suite', () => { senderTaggingDataProvider = mock(); recipientTaggingDataProvider = mock(); capsuleDataProvider = mock(); + privateEventDataProvider = mock(); assertCompatibleOracleVersionSpy = jest.spyOn( UtilityExecutionOracle.prototype, 'utilityAssertCompatibleOracleVersion', @@ -97,6 +100,7 @@ describe('Oracle Version Check test suite', () => { senderTaggingDataProvider, recipientTaggingDataProvider, capsuleDataProvider, + privateEventDataProvider, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index f3a0c19cb466..953f0da7b30a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -69,6 +69,7 @@ import { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + PrivateEventDataProvider, RecipientTaggingDataProvider, } from '../../storage/index.js'; import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; @@ -121,6 +122,7 @@ describe('Private Execution test suite', () => { let aztecNode: MockProxy; let anchorBlockDataProvider: MockProxy; let capsuleDataProvider: MockProxy; + let privateEventDataProvider: MockProxy; let acirSimulator: ContractFunctionSimulator; let anchorBlockHeader = BlockHeader.empty(); @@ -310,6 +312,7 @@ describe('Private Execution test suite', () => { keyStore = mock(); anchorBlockDataProvider = mock(); capsuleDataProvider = mock(); + privateEventDataProvider = mock(); contracts = {}; anchorBlockHeader = makeBlockHeader(); anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); @@ -439,6 +442,7 @@ describe('Private Execution test suite', () => { senderTaggingDataProvider, recipientTaggingDataProvider, capsuleDataProvider, + privateEventDataProvider, simulator, ); }); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 0c4761b0b95a..d79f51fe674d 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -34,6 +34,7 @@ import type { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; @@ -101,6 +102,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP senderTaggingDataProvider: SenderTaggingDataProvider, recipientTaggingDataProvider: RecipientTaggingDataProvider, capsuleDataProvider: CapsuleDataProvider, + privateEventDataProvider: PrivateEventDataProvider, private totalPublicCalldataCount: number = 0, protected sideEffectCounter: number = 0, log = createLogger('simulator:client_execution_context'), @@ -123,6 +125,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP senderTaggingDataProvider, recipientTaggingDataProvider, capsuleDataProvider, + privateEventDataProvider, log, scopes, ); @@ -575,6 +578,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.senderTaggingDataProvider, this.recipientTaggingDataProvider, this.capsuleDataProvider, + this.privateEventDataProvider, this.totalPublicCalldataCount, sideEffectCounter, this.log, 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 25a6abf3eb10..066d58bca852 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 @@ -19,6 +19,7 @@ import { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; @@ -38,6 +39,7 @@ describe('Utility Execution test suite', () => { let senderTaggingDataProvider: ReturnType>; let recipientTaggingDataProvider: ReturnType>; let capsuleDataProvider: ReturnType>; + let privateEventDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; let anchorBlockHeader: BlockHeader; @@ -58,6 +60,7 @@ describe('Utility Execution test suite', () => { senderTaggingDataProvider = mock(); recipientTaggingDataProvider = mock(); capsuleDataProvider = mock(); + privateEventDataProvider = mock(); anchorBlockHeader = BlockHeader.random(); anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); senderTaggingDataProvider.getLastFinalizedIndex.mockResolvedValue(undefined); @@ -80,6 +83,7 @@ describe('Utility Execution test suite', () => { senderTaggingDataProvider, recipientTaggingDataProvider, capsuleDataProvider, + privateEventDataProvider, simulator, ); 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 cedde0b53556..fe174f3b7864 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 @@ -20,6 +20,7 @@ import type { CapsuleDataProvider, ContractDataProvider, NoteDataProvider, + PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; @@ -42,6 +43,7 @@ import { getPublicStorageAt, getSharedSecret, syncTaggedLogs, + validateEnqueuedNotesAndEvents, } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; @@ -70,6 +72,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly senderTaggingDataProvider: SenderTaggingDataProvider, protected readonly recipientTaggingDataProvider: RecipientTaggingDataProvider, protected readonly capsuleDataProvider: CapsuleDataProvider, + protected readonly privateEventDataProvider: PrivateEventDataProvider, protected log = createLogger('simulator:client_view_context'), protected readonly scopes?: AztecAddress[], ) {} @@ -349,10 +352,15 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra throw new Error(`Got a note validation request from ${contractAddress}, expected ${this.contractAddress}`); } - await this.executionDataProvider.validateEnqueuedNotesAndEvents( + await validateEnqueuedNotesAndEvents( contractAddress, noteValidationRequestsArrayBaseSlot, eventValidationRequestsArrayBaseSlot, + this.capsuleDataProvider, + this.anchorBlockDataProvider, + this.aztecNode, + this.noteDataProvider, + this.privateEventDataProvider, ); } diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts index 5d9aebed5516..3938f7dfe263 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts @@ -2,25 +2,20 @@ import { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; -import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; +import { randomDataInBlock } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; -import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { NoteDao, NoteStatus } from '@aztec/stdlib/note'; -import { MerkleTreeId } from '@aztec/stdlib/trees'; -import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect, TxHash } from '@aztec/stdlib/tx'; +import { BlockHeader, GlobalVariables } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; import { AddressDataProvider } from '../storage/address_data_provider/address_data_provider.js'; import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; -import { CapsuleDataProvider } from '../storage/capsule_data_provider/capsule_data_provider.js'; import { ContractDataProvider } from '../storage/contract_data_provider/contract_data_provider.js'; import { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; -import { PrivateEventDataProvider } from '../storage/private_event_data_provider/private_event_data_provider.js'; import { PXEOracleInterface } from './pxe_oracle_interface.js'; jest.setTimeout(30_000); @@ -29,11 +24,9 @@ describe('PXEOracleInterface', () => { let aztecNode: MockProxy; let addressDataProvider: AddressDataProvider; - let privateEventDataProvider: PrivateEventDataProvider; let contractDataProvider: ContractDataProvider; let noteDataProvider: NoteDataProvider; let anchorBlockDataProvider: AnchorBlockDataProvider; - let capsuleDataProvider: CapsuleDataProvider; let keyStore: KeyStore; let recipient: CompleteAddress; @@ -51,18 +44,10 @@ describe('PXEOracleInterface', () => { jest.spyOn(contractDataProvider, 'getDebugContractName').mockImplementation(() => Promise.resolve('TestContract')); addressDataProvider = new AddressDataProvider(store); - privateEventDataProvider = new PrivateEventDataProvider(store); noteDataProvider = await NoteDataProvider.create(store); anchorBlockDataProvider = new AnchorBlockDataProvider(store); - capsuleDataProvider = new CapsuleDataProvider(store); keyStore = new KeyStore(store); - pxeOracleInterface = new PXEOracleInterface( - aztecNode, - noteDataProvider, - capsuleDataProvider, - anchorBlockDataProvider, - privateEventDataProvider, - ); // Set up contract address + pxeOracleInterface = new PXEOracleInterface(aztecNode, noteDataProvider, anchorBlockDataProvider); // Set up contract address contractAddress = await AztecAddress.random(); // Set up recipient account recipient = await keyStore.addAccount(new Fr(69), Fr.random()); @@ -73,304 +58,6 @@ describe('PXEOracleInterface', () => { await setSyncedBlockNumber(MAX_BLOCK_NUMBER_OF_A_LOG); }); - describe('deliverEvent', () => { - let blockNumber: BlockNumber; - let eventSelector: EventSelector; - let eventContent: Fr[]; - let eventCommitment: Fr; - let eventNullifier: Fr; - let txEffect: TxEffect; - let indexedTxEffect: IndexedTxEffect; - - // beforeEach sets up the happy path case, so error modes are tested - // by minimally failing happy path conditions - beforeEach(async () => { - blockNumber = BlockNumber(42); - eventSelector = EventSelector.random(); - eventContent = [Fr.random(), Fr.random()]; - - eventCommitment = Fr.random(); - eventNullifier = await siloNullifier(contractAddress, eventCommitment); - - txEffect = TxEffect.from({ - ...(await TxEffect.random()), - nullifiers: [eventNullifier], - }); - - indexedTxEffect = { - l2BlockNumber: blockNumber, - l2BlockHash: L2BlockHash.random(), - data: txEffect, - txIndexInBlock: 0, - }; - - /* Happy path context conditions: - ** - PXE is sync'd to _at least_ block including tx - ** - Node knows tx effect - ** - Node knows siloed event commitment - */ - await setSyncedBlockNumber(blockNumber); - - aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(indexedTxEffect)); - - aztecNode.findLeavesIndexes.mockImplementation(() => - Promise.resolve([ - { - data: BigInt(0), - l2BlockNumber: indexedTxEffect.l2BlockNumber, - l2BlockHash: indexedTxEffect.l2BlockHash, - }, - ]), - ); - }); - - function deliverEvent( - overrides: { - eventCommitment?: Fr; - } = {}, - ) { - return pxeOracleInterface.deliverEvent( - contractAddress, - eventSelector, - eventContent, - overrides.eventCommitment || eventCommitment, - txEffect.txHash, - recipient.address, - ); - } - - it('should throw when tx does not exist or has no effects', async () => { - aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(undefined)); - await expect(deliverEvent).rejects.toThrow(/Could not find tx effect for tx hash/); - }); - - it('should throw when tx block has not yet been synchronized', async () => { - indexedTxEffect = { - ...indexedTxEffect, - l2BlockNumber: BlockNumber(blockNumber + 1), - }; - aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(indexedTxEffect)); - - await expect(deliverEvent).rejects.toThrow(/Could not find tx effect for tx hash .* as of block number/); - }); - - it('should throw if event is not in tx effects', async () => { - await expect(deliverEvent({ eventCommitment: Fr.random() })).rejects.toThrow( - /Event commitment .* is not present in tx/, - ); - }); - - it('should throw if event is not in nullifiers', async () => { - aztecNode.findLeavesIndexes.mockImplementation(() => Promise.resolve([])); - - await expect(deliverEvent).rejects.toThrow(/Event commitment .* is not present on the nullifier tree/); - }); - - it('should store event for later retrieval', async () => { - await deliverEvent(); - - // I should be able to retrieve the private event I just saved using getPrivateEvents - const result = await privateEventDataProvider.getPrivateEvents(eventSelector, { - contractAddress, - fromBlock: blockNumber, - toBlock: blockNumber + 1, - scopes: [recipient.address], - }); - - expect(result.length).toEqual(1); - expect(result[0].packedEvent).toEqual(eventContent); - }); - }); - - describe('deliverNote', () => { - // Recipient is different from the owner because recipient refers to the - // recipient of the message containing the note, while owner refers to the - // owner of the note. - let owner: AztecAddress; - let storageSlot: Fr; - let randomness: Fr; - let noteNonce: Fr; - let content: Fr[]; - - let noteHash: Fr; - let uniqueNoteHash: Fr; - let nullifier: Fr; - let siloedNullifier: Fr; - - let txHash: TxHash; - let txEffect: TxEffect; - let indexedTxEffect: IndexedTxEffect; - let blockNumber: BlockNumber; - - let nullified = false; - - // beforeEach sets up the happy path case, so error modes are tested - // by minimally failing happy path conditions - beforeEach(async () => { - noteHash = Fr.random(); - nullifier = Fr.random(); - txHash = TxHash.random(); - owner = await AztecAddress.random(); - storageSlot = Fr.random(); - randomness = Fr.random(); - noteNonce = Fr.random(); - content = [Fr.random(), Fr.random()]; - - uniqueNoteHash = await computeUniqueNoteHash(noteNonce, await siloNoteHash(contractAddress, noteHash)); - siloedNullifier = await siloNullifier(contractAddress, nullifier); - - blockNumber = BlockNumber(42); - - txEffect = TxEffect.from({ - ...(await TxEffect.random()), - noteHashes: [uniqueNoteHash], - }); - - indexedTxEffect = { - l2BlockNumber: blockNumber, - l2BlockHash: L2BlockHash.random(), - data: txEffect, - txIndexInBlock: 0, - }; - - /* Happy path context conditions: - ** - PXE is sync'd to _at least_ block including tx - ** - Node knows tx effect - ** - Node knows unique note hash (and siloed nullifier if requested) - */ - await setSyncedBlockNumber(blockNumber); - - aztecNode.getTxEffect.mockImplementation(queryTxHash => - Promise.resolve(queryTxHash == txHash ? indexedTxEffect : undefined), - ); - - aztecNode.findLeavesIndexes.mockImplementation((queryBlockNum, treeId, leaves) => { - if (queryBlockNum != blockNumber) { - throw new Error(`Got a tree query for block ${queryBlockNum} but synced block is ${blockNumber}`); - } - - if (treeId == MerkleTreeId.NOTE_HASH_TREE && leaves[0].equals(uniqueNoteHash)) { - return Promise.resolve([ - { - data: BigInt(0), - l2BlockNumber: indexedTxEffect.l2BlockNumber, - l2BlockHash: indexedTxEffect.l2BlockHash, - }, - ]); - } else if (treeId == MerkleTreeId.NULLIFIER_TREE && leaves[0].equals(siloedNullifier)) { - // Note that returning undefined (i.e. the un-nullified case) covers both scenarios where the note has not - // been nullified and where the nullifier is in a block past the synced block. - return Promise.resolve([ - nullified - ? { - data: BigInt(0), - l2BlockNumber: indexedTxEffect.l2BlockNumber, - l2BlockHash: indexedTxEffect.l2BlockHash, - } - : undefined, - ]); - } else { - throw new Error(); - } - }); - }); - - it('should store note if it exists in note hash tree and is not nullified', async () => { - await pxeOracleInterface.deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - noteHash, - nullifier, - txHash, - recipient.address, - ); - - // Verify note was stored - const notes = await noteDataProvider.getNotes({ contractAddress, scopes: [recipient.address] }); - - expect(notes).toHaveLength(1); - expect(notes[0].noteHash.equals(noteHash)).toBe(true); - }); - - it('should throw if tx hash does not exist', async () => { - await expect( - pxeOracleInterface.deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - noteHash, - nullifier, - TxHash.random(), - recipient.address, - ), - ).rejects.toThrow(/Could not find tx effect/); - }); - - it('should throw if note was not emitted in the tx', async () => { - await expect( - pxeOracleInterface.deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - Fr.random(), // note hash - nullifier, - txHash, - recipient.address, - ), - ).rejects.toThrow(/is not present in tx/); - }); - - it('should throw if tx was mined after synced block number', async () => { - await setSyncedBlockNumber(BlockNumber(blockNumber - 1)); - - await expect( - pxeOracleInterface.deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - noteHash, - nullifier, - txHash, - recipient.address, - ), - ).rejects.toThrow(/as of block number/); - }); - - it('should store and immediately remove note if it is already nullified', async () => { - nullified = true; - - await pxeOracleInterface.deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - noteHash, - nullifier, - txHash, - recipient.address, - ); - - // Verify note was removed - const notes = await noteDataProvider.getNotes({ contractAddress, scopes: [recipient.address] }); - expect(notes).toHaveLength(0); - }); - }); - describe('syncNoteNullifiers', () => { let recipient: AztecAddress; diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts index 35919e0cfc8c..d21d8f01add7 100644 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts @@ -1,21 +1,13 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { createLogger } from '@aztec/foundation/log'; -import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { DataInBlock } from '@aztec/stdlib/block'; -import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/client'; -import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; -import { TxHash } from '@aztec/stdlib/tx'; import type { ExecutionDataProvider } from '../contract_function_simulator/execution_data_provider.js'; import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; -import type { CapsuleDataProvider } from '../storage/capsule_data_provider/capsule_data_provider.js'; import type { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; -import type { PrivateEventDataProvider } from '../storage/private_event_data_provider/private_event_data_provider.js'; -import { EventValidationRequest } from './noir-structs/event_validation_request.js'; -import { NoteValidationRequest } from './noir-structs/note_validation_request.js'; import type { ProxiedNode } from './proxied_node.js'; /** @@ -29,223 +21,10 @@ export class PXEOracleInterface implements ExecutionDataProvider { constructor( public readonly aztecNode: AztecNode | ProxiedNode, private noteDataProvider: NoteDataProvider, - private capsuleDataProvider: CapsuleDataProvider, private anchorBlockDataProvider: AnchorBlockDataProvider, - private privateEventDataProvider: PrivateEventDataProvider, private log = createLogger('pxe:pxe_oracle_interface'), ) {} - public async validateEnqueuedNotesAndEvents( - contractAddress: AztecAddress, - noteValidationRequestsArrayBaseSlot: Fr, - eventValidationRequestsArrayBaseSlot: Fr, - ): Promise { - // We read all note and event validation requests and process them all concurrently. This makes the process much - // faster as we don't need to wait for the network round-trip. - const noteValidationRequests = ( - await this.capsuleDataProvider.readCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot) - ).map(NoteValidationRequest.fromFields); - - const eventValidationRequests = ( - await this.capsuleDataProvider.readCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot) - ).map(EventValidationRequest.fromFields); - - const noteDeliveries = noteValidationRequests.map(request => - this.deliverNote( - request.contractAddress, - request.owner, - request.storageSlot, - request.randomness, - request.noteNonce, - request.content, - request.noteHash, - request.nullifier, - request.txHash, - request.recipient, - ), - ); - - const eventDeliveries = eventValidationRequests.map(request => - this.deliverEvent( - request.contractAddress, - request.eventTypeId, - request.serializedEvent, - request.eventCommitment, - request.txHash, - request.recipient, - ), - ); - - await Promise.all([...noteDeliveries, ...eventDeliveries]); - - // Requests are cleared once we're done. - await this.capsuleDataProvider.setCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot, []); - await this.capsuleDataProvider.setCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot, []); - } - - async deliverNote( - contractAddress: AztecAddress, - owner: AztecAddress, - storageSlot: Fr, - randomness: Fr, - noteNonce: Fr, - content: Fr[], - noteHash: Fr, - nullifier: Fr, - txHash: TxHash, - recipient: AztecAddress, - ): Promise { - // We are going to store the new note in the NoteDataProvider, which will let us later return it via `getNotes`. - // There's two things we need to check before we do this however: - // - we must make sure the note does actually exist in the note hash tree - // - we need to check if the note has already been nullified - // - // Failing to do either of the above would result in circuits getting either non-existent notes and failing to - // produce inclusion proofs for them, or getting nullified notes and producing duplicate nullifiers, both of which - // are catastrophic failure modes. - // - // Note that adding a note and removing it is *not* equivalent to never adding it in the first place. A nullifier - // emitted in a block that comes after note creation might result in the note being de-nullified by a chain reorg, - // so we must store both the note hash and nullifier block information. - - // We avoid making node queries at 'latest' since we don't want to process notes or nullifiers that only exist ahead - // in time of the locally synced state. - // Note that while this technically results in historical queries, we perform it at the latest locally synced block - // number which *should* be recent enough to be available, even for non-archive nodes. - // Also note that the note should never be ahead of the synced block here since `fetchTaggedLogs` only processes - // logs up to the synced block making this only an additional safety check. - const syncedBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - - // By computing siloed and unique note hashes ourselves we prevent contracts from interfering with the note storage - // of other contracts, which would constitute a security breach. - const uniqueNoteHash = await computeUniqueNoteHash(noteNonce, await siloNoteHash(contractAddress, noteHash)); - const siloedNullifier = await siloNullifier(contractAddress, nullifier); - - const txEffect = await this.aztecNode.getTxEffect(txHash); - if (!txEffect) { - throw new Error(`Could not find tx effect for tx hash ${txHash}`); - } - - if (txEffect.l2BlockNumber > syncedBlockNumber) { - throw new Error(`Could not find tx effect for tx hash ${txHash} as of block number ${syncedBlockNumber}`); - } - - const noteInTx = txEffect.data.noteHashes.some(nh => nh.equals(uniqueNoteHash)); - if (!noteInTx) { - throw new Error(`Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present in tx ${txHash}`); - } - - // We store notes by their index in the global note hash tree, which has the convenient side effect of validating - // note existence in said tree. We concurrently also check if the note's nullifier exists, performing all node - // queries in a single round-trip. - const [[uniqueNoteHashTreeIndexInBlock], [nullifierIndex]] = await Promise.all([ - this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash]), - this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, [siloedNullifier]), - ]); - - if (uniqueNoteHashTreeIndexInBlock === undefined) { - throw new Error( - `Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present on the tree at block ${syncedBlockNumber} (from tx ${txHash})`, - ); - } - - const noteDao = new NoteDao( - new Note(content), - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - noteHash, - siloedNullifier, - txHash, - uniqueNoteHashTreeIndexInBlock.l2BlockNumber, - uniqueNoteHashTreeIndexInBlock.l2BlockHash.toString(), - uniqueNoteHashTreeIndexInBlock.data, - ); - - // The note was found by `recipient`, so we use that as the scope when storing the note. - await this.noteDataProvider.addNotes([noteDao], recipient); - this.log.verbose('Added note', { - index: noteDao.index, - contract: noteDao.contractAddress.toString(), - slot: noteDao.storageSlot.toString(), - noteHash: noteDao.noteHash.toString(), - nullifier: noteDao.siloedNullifier.toString(), - }); - - if (nullifierIndex !== undefined) { - const { data: _, ...blockHashAndNum } = nullifierIndex; - await this.noteDataProvider.applyNullifiers([{ data: siloedNullifier, ...blockHashAndNum }]); - - this.log.verbose(`Removed just-added note`, { - contract: contractAddress, - slot: storageSlot, - noteHash: noteHash, - nullifier: siloedNullifier.toString(), - }); - } - } - - async deliverEvent( - contractAddress: AztecAddress, - selector: EventSelector, - content: Fr[], - eventCommitment: Fr, - txHash: TxHash, - scope: AztecAddress, - ): Promise { - // While using 'latest' block number would be fine for private events since they cannot be accessed from Aztec.nr - // (and thus we're less concerned about being ahead of the synced block), we use the synced block number to - // maintain consistent behavior in the PXE. Additionally, events should never be ahead of the synced block here - // since `fetchTaggedLogs` only processes logs up to the synced block. - const [syncedBlockHeader, siloedEventCommitment, txEffect] = await Promise.all([ - this.anchorBlockDataProvider.getBlockHeader(), - siloNullifier(contractAddress, eventCommitment), - this.aztecNode.getTxEffect(txHash), - ]); - - const syncedBlockNumber = syncedBlockHeader.getBlockNumber(); - - if (!txEffect) { - throw new Error(`Could not find tx effect for tx hash ${txHash}`); - } - - if (txEffect.l2BlockNumber > syncedBlockNumber) { - throw new Error(`Could not find tx effect for tx hash ${txHash} as of block number ${syncedBlockNumber}`); - } - - const eventInTx = txEffect.data.nullifiers.some(n => n.equals(siloedEventCommitment)); - if (!eventInTx) { - throw new Error( - `Event commitment ${eventCommitment} (siloed as ${siloedEventCommitment}) is not present in tx ${txHash}`, - ); - } - - const [nullifierIndex] = await this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, [ - siloedEventCommitment, - ]); - - if (nullifierIndex === undefined) { - throw new Error( - `Event commitment ${eventCommitment} (siloed as ${siloedEventCommitment}) is not present on the nullifier tree at block ${syncedBlockNumber} (from tx ${txHash})`, - ); - } - - return this.privateEventDataProvider.storePrivateEventLog( - selector, - content, - Number(nullifierIndex.data), // Index of the event commitment in the nullifier tree - { - contractAddress, - scope, - txHash, - l2BlockNumber: nullifierIndex.l2BlockNumber, // Block number in which the event was emitted - l2BlockHash: nullifierIndex.l2BlockHash, // Block hash in which the event was emitted - }, - ); - } - /** * Looks for nullifiers of active contract notes and marks them as nullified if a nullifier is found. * diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 16bec5eea235..78711ec6b1f8 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -198,9 +198,7 @@ export class PXE { const pxeOracleInterface = new PXEOracleInterface( ProxiedNodeFactory.create(this.node), this.noteDataProvider, - this.capsuleDataProvider, this.anchorBlockDataProvider, - this.privateEventDataProvider, this.log, ); return new ContractFunctionSimulator( @@ -214,6 +212,7 @@ export class PXE { this.senderTaggingDataProvider, this.recipientTaggingDataProvider, this.capsuleDataProvider, + this.privateEventDataProvider, this.simulator, ); } 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 dab34423839e..5ea5d6ecf91b 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 @@ -18,6 +18,7 @@ import { NoteDataProvider, ORACLE_VERSION, PXEOracleInterface, + PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, enrichPublicSimulationError, @@ -103,6 +104,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl private senderTaggingDataProvider: SenderTaggingDataProvider, private recipientTaggingDataProvider: RecipientTaggingDataProvider, private capsuleDataProvider: CapsuleDataProvider, + private privateEventDataProvider: PrivateEventDataProvider, private pxeOracleInterface: PXEOracleInterface, private nextBlockTimestamp: bigint, private version: Fr, @@ -320,6 +322,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.senderTaggingDataProvider, this.recipientTaggingDataProvider, this.capsuleDataProvider, + this.privateEventDataProvider, 0, 1, undefined, // log @@ -643,6 +646,7 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl this.senderTaggingDataProvider, this.recipientTaggingDataProvider, this.capsuleDataProvider, + this.privateEventDataProvider, ); const acirExecutionResult = await new WASMSimulator() .executeUserCircuit(toACVMWitness(0, args), entryPointArtifact, new Oracle(oracle).toACIRCallback()) diff --git a/yarn-project/txe/src/txe_session.test.ts b/yarn-project/txe/src/txe_session.test.ts index 878663bb0304..7e0cd006330e 100644 --- a/yarn-project/txe/src/txe_session.test.ts +++ b/yarn-project/txe/src/txe_session.test.ts @@ -19,6 +19,7 @@ describe('TXESession.processFunction', () => { {} as any, // senderTaggingDataProvider {} as any, // recipientTaggingDataProvider {} as any, // capsuleDataProvider + {} as any, // privateEventDataProvider new Fr(1), // chainId new Fr(1), // version 0n, // nextBlockTimestamp diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index bb439bda5812..740bea1cdeda 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -128,6 +128,7 @@ export class TXESession implements TXESessionStateHandler { private senderTaggingDataProvider: SenderTaggingDataProvider, private recipientTaggingDataProvider: RecipientTaggingDataProvider, private capsuleDataProvider: CapsuleDataProvider, + private privateEventDataProvider: PrivateEventDataProvider, private chainId: Fr, private version: Fr, private nextBlockTimestamp: bigint, @@ -162,9 +163,7 @@ export class TXESession implements TXESessionStateHandler { const pxeOracleInterface = new PXEOracleInterface( stateMachine.node, noteDataProvider, - capsuleDataProvider, stateMachine.anchorBlockDataProvider, - privateEventDataProvider, ); const topLevelOracleHandler = new TXEOracleTopLevelContext( @@ -177,6 +176,7 @@ export class TXESession implements TXESessionStateHandler { senderTaggingDataProvider, recipientTaggingDataProvider, capsuleDataProvider, + privateEventDataProvider, pxeOracleInterface, nextBlockTimestamp, version, @@ -197,6 +197,7 @@ export class TXESession implements TXESessionStateHandler { senderTaggingDataProvider, recipientTaggingDataProvider, capsuleDataProvider, + privateEventDataProvider, version, chainId, nextBlockTimestamp, @@ -267,6 +268,7 @@ export class TXESession implements TXESessionStateHandler { this.senderTaggingDataProvider, this.recipientTaggingDataProvider, this.capsuleDataProvider, + this.privateEventDataProvider, this.pxeOracleInterface, this.nextBlockTimestamp, this.version, @@ -329,6 +331,7 @@ export class TXESession implements TXESessionStateHandler { this.senderTaggingDataProvider, this.recipientTaggingDataProvider, this.capsuleDataProvider, + this.privateEventDataProvider, ); // We store the note and tagging index caches fed into the PrivateExecutionOracle (along with some other auxiliary @@ -393,6 +396,7 @@ export class TXESession implements TXESessionStateHandler { this.senderTaggingDataProvider, this.recipientTaggingDataProvider, this.capsuleDataProvider, + this.privateEventDataProvider, ); this.state = { name: 'UTILITY' }; From 2b60f30e93f0b0deb272970692e66e9b52a7a6f4 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 12:59:51 +0000 Subject: [PATCH 029/140] good bye PXEOracleInterface --- .../contract_function_simulator.ts | 4 - .../execution_data_provider.ts | 11 - .../oracle/common.test.ts | 126 +++++++++++- .../oracle/common.ts | 59 +++++- .../oracle/oracle_version_is_checked.test.ts | 5 +- .../oracle/private_execution.test.ts | 17 +- .../oracle/private_execution_oracle.ts | 4 - .../oracle/utility_execution.test.ts | 4 - .../oracle/utility_execution_oracle.ts | 5 +- .../pxe_oracle_interface.test.ts | 190 ------------------ .../pxe_oracle_interface.ts | 88 -------- .../src/entrypoints/client/bundle/index.ts | 1 - .../pxe/src/entrypoints/client/lazy/index.ts | 1 - .../pxe/src/entrypoints/server/index.ts | 1 - yarn-project/pxe/src/pxe.ts | 9 - .../oracle/txe_oracle_top_level_context.ts | 4 - yarn-project/txe/src/txe_session.test.ts | 1 - yarn-project/txe/src/txe_session.ts | 28 ++- 18 files changed, 200 insertions(+), 358 deletions(-) delete mode 100644 yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts delete mode 100644 yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index ede60821473b..e9c3093e24a4 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -81,7 +81,6 @@ import type { RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../storage/index.js'; -import type { ExecutionDataProvider } from './execution_data_provider.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; import { HashedValuesCache } from './hashed_values_cache.js'; @@ -98,7 +97,6 @@ export class ContractFunctionSimulator { private log: Logger; constructor( - private executionDataProvider: ExecutionDataProvider, private contractDataProvider: ContractDataProvider, private noteDataProvider: NoteDataProvider, private keyStore: KeyStore, @@ -182,7 +180,6 @@ export class ContractFunctionSimulator { HashedValuesCache.create(request.argsOfCalls), noteCache, taggingIndexCache, - this.executionDataProvider, this.contractDataProvider, this.noteDataProvider, this.keyStore, @@ -283,7 +280,6 @@ export class ContractFunctionSimulator { authwits, [], anchorBlockHeader, - this.executionDataProvider, this.contractDataProvider, this.noteDataProvider, this.keyStore, diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts index 20e1423d124d..fc6c464753af 100644 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts @@ -1,4 +1,3 @@ -import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { NodeStats } from '@aztec/stdlib/tx'; /** @@ -28,13 +27,3 @@ export type ExecutionStats = { */ nodeRPCCalls: NodeStats; }; - -/** - * The interface for the data layer required to perform private and utility execution. - */ -export interface ExecutionDataProvider { - /** - * Looks for nullifiers of active contract notes and marks them as nullified in the db if a nullifier is found. - */ - syncNoteNullifiers(contractAddress: AztecAddress): Promise; -} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index 687f07a21c35..0bf2f3b5fb88 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -4,11 +4,12 @@ import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { L2BlockHash } from '@aztec/stdlib/block'; +import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; +import { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import { BlockHeader, @@ -28,6 +29,7 @@ import { AnchorBlockDataProvider, CapsuleDataProvider, ContractDataProvider, + NoteDao, NoteDataProvider, PrivateEventDataProvider, } from '../../storage/index.js'; @@ -42,6 +44,7 @@ import { getPublicDataWitness, getPublicLogByTag, getPublicStorageAt, + syncNoteNullifiers, } from './common.js'; jest.setTimeout(30_000); @@ -657,6 +660,127 @@ describe('Common oracle functions', () => { }); }); + describe('syncNoteNullifiers', () => { + let recipient: AztecAddress; + + beforeEach(async () => { + // Check that there are no notes in the database + const notes = await noteDataProvider.getNotes({ contractAddress }); + expect(notes).toHaveLength(0); + + // Check that the expected number of accounts is present + const accounts = await keyStore.getAccounts(); + expect(accounts).toHaveLength(1); + + recipient = accounts[0]; + }); + + it('should remove notes that have been nullified', async () => { + // Set up initial state with a note + const noteDao = await NoteDao.random({ contractAddress }); + + // Spy on the noteDataProvider.applyNullifiers to later on have additional guarantee that we really removed + // the note. + jest.spyOn(noteDataProvider, 'applyNullifiers'); + + // Add the note to storage + await noteDataProvider.addNotes([noteDao], recipient); + + // Set up the nullifier in the merkle tree + const nullifierIndex = randomDataInBlock(123n); + aztecNode.findLeavesIndexes.mockResolvedValue([nullifierIndex]); + + // Call the function under test + await syncNoteNullifiers(contractAddress, anchorBlockDataProvider, noteDataProvider, aztecNode); + + // Verify the note was removed by checking storage + const remainingNotes = await noteDataProvider.getNotes({ + contractAddress, + status: NoteStatus.ACTIVE, + scopes: [recipient], + }); + expect(remainingNotes).toHaveLength(0); + + // Verify the note was removed by checking the spy + expect(noteDataProvider.applyNullifiers).toHaveBeenCalledTimes(1); + }); + + it('should keep notes that have not been nullified', async () => { + // Set up initial state with a note + const noteDao = await NoteDao.random({ contractAddress }); + + // Add the note to storage + await noteDataProvider.addNotes([noteDao], recipient); + + // No nullifier found in merkle tree + aztecNode.findLeavesIndexes.mockResolvedValue([undefined]); + + // Call the function under test + await syncNoteNullifiers(contractAddress, anchorBlockDataProvider, noteDataProvider, aztecNode); + + // Verify note still exists + const remainingNotes = await noteDataProvider.getNotes({ + contractAddress, + status: NoteStatus.ACTIVE, + scopes: [recipient], + }); + expect(remainingNotes).toHaveLength(1); + expect(remainingNotes[0]).toEqual(noteDao); + }); + + // Verifies that notes are not marked as nullified when their nullifier only exists in blocks that haven't been + // synced yet. We mock the nullifier to only exist in blocks beyond our current sync point, then verify the note + // is not removed by applyNullifiers. + it('should not remove notes if nullifier is in unsynced blocks', async () => { + // Set up initial state with a note + const noteDao = await NoteDao.random({ contractAddress }); + const syncedBlockNumber = 100; + await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); + + // Add the note to storage + await noteDataProvider.addNotes([noteDao], recipient); + + // Mock nullifier to only exist after synced block + aztecNode.findLeavesIndexes.mockImplementation(blockNum => { + if (typeof blockNum === 'number' && blockNum > syncedBlockNumber) { + return Promise.resolve([randomDataInBlock(0n)]); + } + return Promise.resolve([undefined]); + }); + + // Call the function under test + await syncNoteNullifiers(contractAddress, anchorBlockDataProvider, noteDataProvider, aztecNode); + // Verify note still exists + const remainingNotes = await noteDataProvider.getNotes({ + contractAddress, + status: NoteStatus.ACTIVE, + scopes: [recipient], + }); + expect(remainingNotes).toHaveLength(1); + expect(remainingNotes[0]).toEqual(noteDao); + }); + + it('should search for notes from all accounts', async () => { + // Add multiple accounts to keystore + await keyStore.addAccount(Fr.random(), Fr.random()); + await keyStore.addAccount(Fr.random(), Fr.random()); + + expect(await keyStore.getAccounts()).toHaveLength(3); + + // Spy on the noteDataProvider.getNotesSpy + const getNotesSpy = jest.spyOn(noteDataProvider, 'getNotes'); + + // Call the function under test + await syncNoteNullifiers(contractAddress, anchorBlockDataProvider, noteDataProvider, aztecNode); + + // Verify applyNullifiers was called once for all accounts + expect(getNotesSpy).toHaveBeenCalledTimes(1); + + // Verify getNotes was called with the correct contract address + expect(getNotesSpy).toHaveBeenCalledWith(expect.objectContaining({ contractAddress })); + }); + }); + const setSyncedBlockNumber = (blockNumber: BlockNumber) => { return anchorBlockDataProvider.setHeader( BlockHeader.empty({ diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index fa7f85cae330..77e5cb262782 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -4,10 +4,10 @@ import type { Point } from '@aztec/foundation/curves/grumpkin'; import type { KeyStore } from '@aztec/key-store'; import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter, L2Block } from '@aztec/stdlib/block'; +import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; -import type { AztecNode } from '@aztec/stdlib/interfaces/server'; +import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; import { computeAddressSecret } from '@aztec/stdlib/keys'; import { DirectionalAppTaggingSecret, @@ -894,3 +894,58 @@ export async function deliverEvent( }, ); } + +/** + * Looks for nullifiers of active contract notes and marks them as nullified if a nullifier is found. + * + * Fetches notes from the NoteDataProvider and checks which nullifiers are present in the + * onchain nullifier Merkle tree - up to the latest locally synced block. We use the + * locally synced block instead of querying the chain's 'latest' block to ensure correctness: + * notes are only marked nullified once their corresponding nullifier has been included in a + * block up to which the PXE has synced. + * This allows recent nullifications to be processed even if the node is not an archive node. + * + * @param contractAddress - The contract whose notes should be checked and nullified. + */ +export async function syncNoteNullifiers( + contractAddress: AztecAddress, + anchorBlockDataProvider: AnchorBlockDataProvider, + noteDataProvider: NoteDataProvider, + aztecNode: AztecNode, +) { + const syncedBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + + const contractNotes = await noteDataProvider.getNotes({ contractAddress }); + + if (contractNotes.length === 0) { + return; + } + + const nullifiersToCheck = contractNotes.map(note => note.siloedNullifier); + const nullifierBatches = nullifiersToCheck.reduce( + (acc, nullifier) => { + if (acc[acc.length - 1].length < MAX_RPC_LEN) { + acc[acc.length - 1].push(nullifier); + } else { + acc.push([nullifier]); + } + return acc; + }, + [[]] as Fr[][], + ); + const nullifierIndexes = ( + await Promise.all( + nullifierBatches.map(batch => aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, batch)), + ) + ).flat(); + + const foundNullifiers = nullifiersToCheck + .map((nullifier, i) => { + if (nullifierIndexes[i] !== undefined) { + return { ...nullifierIndexes[i], ...{ data: nullifier } } as DataInBlock; + } + }) + .filter(nullifier => nullifier !== undefined) as DataInBlock[]; + + await noteDataProvider.applyNullifiers(foundNullifiers); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index aaf46ec1d350..b0f77470b015 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -23,13 +23,11 @@ import { SenderTaggingDataProvider, } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; -import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; describe('Oracle Version Check test suite', () => { const simulator = new WASMSimulator(); - let executionDataProvider: ReturnType>; let contractDataProvider: ReturnType>; let noteDataProvider: ReturnType>; let keyStore: ReturnType>; @@ -48,7 +46,6 @@ describe('Oracle Version Check test suite', () => { >; beforeEach(async () => { - executionDataProvider = mock(); contractDataProvider = mock(); noteDataProvider = mock(); keyStore = mock(); @@ -79,6 +76,7 @@ describe('Oracle Version Check test suite', () => { recipientTaggingDataProvider.getLastUsedIndexes.mockImplementation(secrets => Promise.resolve(secrets.map(() => undefined)), ); + noteDataProvider.getNotes.mockResolvedValue([]); keyStore.getAccounts.mockResolvedValue([]); contractAddress = await AztecAddress.random(); @@ -90,7 +88,6 @@ describe('Oracle Version Check test suite', () => { } as ContractInstanceWithAddress); acirSimulator = new ContractFunctionSimulator( - executionDataProvider, contractDataProvider, noteDataProvider, keyStore, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 953f0da7b30a..dc39c8c5bade 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -74,7 +74,6 @@ import { } from '../../storage/index.js'; import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; -import type { ExecutionDataProvider } from '../execution_data_provider.js'; jest.setTimeout(60_000); @@ -112,7 +111,6 @@ export const buildL1ToL2Message = async ( describe('Private Execution test suite', () => { const simulator = new WASMSimulator(); - let executionDataProvider: MockProxy; let contractDataProvider: MockProxy; let noteDataProvider: MockProxy; let addressDataProvider: MockProxy; @@ -302,9 +300,9 @@ describe('Private Execution test suite', () => { beforeEach(async () => { trees = {}; - executionDataProvider = mock(); contractDataProvider = mock(); noteDataProvider = mock(); + noteDataProvider.getNotes.mockResolvedValue([]); addressDataProvider = mock(); senderTaggingDataProvider = mock(); recipientTaggingDataProvider = mock(); @@ -318,16 +316,6 @@ describe('Private Execution test suite', () => { anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); capsuleDataProvider.readCapsuleArray.mockResolvedValue([]); - // Mock the senderTaggingDataProvider getter - Object.defineProperty(executionDataProvider, 'senderTaggingDataProvider', { - get: () => senderTaggingDataProvider, - }); - - // Mock the aztecNode getter - Object.defineProperty(executionDataProvider, 'aztecNode', { - get: () => aztecNode, - }); - // Mock sender tagging data provider methods senderTaggingDataProvider.getLastFinalizedIndex.mockResolvedValue(undefined); senderTaggingDataProvider.getLastUsedIndex.mockResolvedValue(undefined); @@ -335,7 +323,7 @@ describe('Private Execution test suite', () => { senderTaggingDataProvider.storePendingIndexes.mockResolvedValue(); recipientTaggingDataProvider.getSenderAddresses.mockResolvedValue([]); recipientTaggingDataProvider.getLastUsedIndexes.mockImplementation(secrets => - Promise.resolve(secrets.map(() => undefined)), + Promise.resolve(new Array(secrets?.length ?? 0).fill(undefined)), ); // Mock aztec node methods - the return array needs to have the same length as the number of tags @@ -432,7 +420,6 @@ describe('Private Execution test suite', () => { ); acirSimulator = new ContractFunctionSimulator( - executionDataProvider, contractDataProvider, noteDataProvider, keyStore, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index d79f51fe674d..174b2ca92823 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -40,7 +40,6 @@ import type { } from '../../storage/index.js'; import { syncSenderTaggingIndexes } from '../../tagging/sync/sync_sender_tagging_indexes.js'; import { Tag } from '../../tagging/tag.js'; -import type { ExecutionDataProvider } from '../execution_data_provider.js'; import type { ExecutionNoteCache } from '../execution_note_cache.js'; import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import type { HashedValuesCache } from '../hashed_values_cache.js'; @@ -92,7 +91,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP private readonly executionCache: HashedValuesCache, private readonly noteCache: ExecutionNoteCache, private readonly taggingIndexCache: ExecutionTaggingIndexCache, - executionDataProvider: ExecutionDataProvider, contractDataProvider: ContractDataProvider, noteDataProvider: NoteDataProvider, keyStore: KeyStore, @@ -115,7 +113,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP authWitnesses, capsules, anchorBlockHeader, - executionDataProvider, contractDataProvider, noteDataProvider, keyStore, @@ -568,7 +565,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.executionCache, this.noteCache, this.taggingIndexCache, - this.executionDataProvider, this.contractDataProvider, this.noteDataProvider, this.keyStore, 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 066d58bca852..1105d3050249 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 @@ -24,12 +24,10 @@ import { SenderTaggingDataProvider, } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; -import type { ExecutionDataProvider } from '../execution_data_provider.js'; describe('Utility Execution test suite', () => { const simulator = new WASMSimulator(); - let executionDataProvider: ReturnType>; let contractDataProvider: ReturnType>; let noteDataProvider: ReturnType>; let keyStore: ReturnType>; @@ -50,7 +48,6 @@ describe('Utility Execution test suite', () => { }; beforeEach(async () => { - executionDataProvider = mock(); contractDataProvider = mock(); noteDataProvider = mock(); keyStore = mock(); @@ -73,7 +70,6 @@ describe('Utility Execution test suite', () => { ); capsuleDataProvider.readCapsuleArray.mockResolvedValue([]); acirSimulator = new ContractFunctionSimulator( - executionDataProvider, contractDataProvider, noteDataProvider, keyStore, 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 fe174f3b7864..0afbeee824c0 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 @@ -24,7 +24,6 @@ import type { RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; -import type { ExecutionDataProvider } from '../execution_data_provider.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; import { @@ -42,6 +41,7 @@ import { getPublicDataWitness, getPublicStorageAt, getSharedSecret, + syncNoteNullifiers, syncTaggedLogs, validateEnqueuedNotesAndEvents, } from './common.js'; @@ -62,7 +62,6 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra protected readonly authWitnesses: AuthWitness[], protected readonly capsules: Capsule[], // TODO(#12425): Rename to transientCapsules protected readonly anchorBlockHeader: BlockHeader, - protected readonly executionDataProvider: ExecutionDataProvider, protected readonly contractDataProvider: ContractDataProvider, protected readonly noteDataProvider: NoteDataProvider, protected readonly keyStore: KeyStore, @@ -339,7 +338,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra this.scopes, ); - await this.executionDataProvider.syncNoteNullifiers(this.contractAddress); + await syncNoteNullifiers(this.contractAddress, this.anchorBlockDataProvider, this.noteDataProvider, this.aztecNode); } public async utilityValidateEnqueuedNotesAndEvents( diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts deleted file mode 100644 index 3938f7dfe263..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { BlockNumber } from '@aztec/foundation/branded-types'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import { KeyStore } from '@aztec/key-store'; -import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { randomDataInBlock } from '@aztec/stdlib/block'; -import { CompleteAddress } from '@aztec/stdlib/contract'; -import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { NoteDao, NoteStatus } from '@aztec/stdlib/note'; -import { BlockHeader, GlobalVariables } from '@aztec/stdlib/tx'; - -import { jest } from '@jest/globals'; -import { type MockProxy, mock } from 'jest-mock-extended'; - -import { AddressDataProvider } from '../storage/address_data_provider/address_data_provider.js'; -import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; -import { ContractDataProvider } from '../storage/contract_data_provider/contract_data_provider.js'; -import { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; -import { PXEOracleInterface } from './pxe_oracle_interface.js'; - -jest.setTimeout(30_000); - -describe('PXEOracleInterface', () => { - let aztecNode: MockProxy; - - let addressDataProvider: AddressDataProvider; - let contractDataProvider: ContractDataProvider; - let noteDataProvider: NoteDataProvider; - let anchorBlockDataProvider: AnchorBlockDataProvider; - let keyStore: KeyStore; - - let recipient: CompleteAddress; - let contractAddress: AztecAddress; - - let pxeOracleInterface: PXEOracleInterface; - - // The block number of the last log to be emitted. - const MAX_BLOCK_NUMBER_OF_A_LOG = BlockNumber(3); - - beforeEach(async () => { - const store = await openTmpStore('test'); - aztecNode = mock(); - contractDataProvider = new ContractDataProvider(store); - jest.spyOn(contractDataProvider, 'getDebugContractName').mockImplementation(() => Promise.resolve('TestContract')); - - addressDataProvider = new AddressDataProvider(store); - noteDataProvider = await NoteDataProvider.create(store); - anchorBlockDataProvider = new AnchorBlockDataProvider(store); - keyStore = new KeyStore(store); - pxeOracleInterface = new PXEOracleInterface(aztecNode, noteDataProvider, anchorBlockDataProvider); // Set up contract address - contractAddress = await AztecAddress.random(); - // Set up recipient account - recipient = await keyStore.addAccount(new Fr(69), Fr.random()); - await addressDataProvider.addCompleteAddress(recipient); - - // PXEOracleInterface.syncTaggedLogs(...) function syncs logs up to the block number up to which PXE synced. We set - // the synced block number to that of the last emitted log to receive all the logs by default. - await setSyncedBlockNumber(MAX_BLOCK_NUMBER_OF_A_LOG); - }); - - describe('syncNoteNullifiers', () => { - let recipient: AztecAddress; - - beforeEach(async () => { - // Check that there are no notes in the database - const notes = await noteDataProvider.getNotes({ contractAddress }); - expect(notes).toHaveLength(0); - - // Check that the expected number of accounts is present - const accounts = await keyStore.getAccounts(); - expect(accounts).toHaveLength(1); - - recipient = accounts[0]; - }); - - it('should remove notes that have been nullified', async () => { - // Set up initial state with a note - const noteDao = await NoteDao.random({ contractAddress }); - - // Spy on the noteDataProvider.applyNullifiers to later on have additional guarantee that we really removed - // the note. - jest.spyOn(noteDataProvider, 'applyNullifiers'); - - // Add the note to storage - await noteDataProvider.addNotes([noteDao], recipient); - - // Set up the nullifier in the merkle tree - const nullifierIndex = randomDataInBlock(123n); - aztecNode.findLeavesIndexes.mockResolvedValue([nullifierIndex]); - - // Call the function under test - await pxeOracleInterface.syncNoteNullifiers(contractAddress); - - // Verify the note was removed by checking storage - const remainingNotes = await noteDataProvider.getNotes({ - contractAddress, - status: NoteStatus.ACTIVE, - scopes: [recipient], - }); - expect(remainingNotes).toHaveLength(0); - - // Verify the note was removed by checking the spy - expect(noteDataProvider.applyNullifiers).toHaveBeenCalledTimes(1); - }); - - it('should keep notes that have not been nullified', async () => { - // Set up initial state with a note - const noteDao = await NoteDao.random({ contractAddress }); - - // Add the note to storage - await noteDataProvider.addNotes([noteDao], recipient); - - // No nullifier found in merkle tree - aztecNode.findLeavesIndexes.mockResolvedValue([undefined]); - - // Call the function under test - await pxeOracleInterface.syncNoteNullifiers(contractAddress); - - // Verify note still exists - const remainingNotes = await noteDataProvider.getNotes({ - contractAddress, - status: NoteStatus.ACTIVE, - scopes: [recipient], - }); - expect(remainingNotes).toHaveLength(1); - expect(remainingNotes[0]).toEqual(noteDao); - }); - - // Verifies that notes are not marked as nullified when their nullifier only exists in blocks that haven't been - // synced yet. We mock the nullifier to only exist in blocks beyond our current sync point, then verify the note - // is not removed by applyNullifiers. - it('should not remove notes if nullifier is in unsynced blocks', async () => { - // Set up initial state with a note - const noteDao = await NoteDao.random({ contractAddress }); - const syncedBlockNumber = 100; - await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); - - // Add the note to storage - await noteDataProvider.addNotes([noteDao], recipient); - - // Mock nullifier to only exist after synced block - aztecNode.findLeavesIndexes.mockImplementation(blockNum => { - if (typeof blockNum === 'number' && blockNum > syncedBlockNumber) { - return Promise.resolve([randomDataInBlock(0n)]); - } - return Promise.resolve([undefined]); - }); - - // Call the function under test - await pxeOracleInterface.syncNoteNullifiers(contractAddress); - - // Verify note still exists - const remainingNotes = await noteDataProvider.getNotes({ - contractAddress, - status: NoteStatus.ACTIVE, - scopes: [recipient], - }); - expect(remainingNotes).toHaveLength(1); - expect(remainingNotes[0]).toEqual(noteDao); - }); - - it('should search for notes from all accounts', async () => { - // Add multiple accounts to keystore - await keyStore.addAccount(Fr.random(), Fr.random()); - await keyStore.addAccount(Fr.random(), Fr.random()); - - expect(await keyStore.getAccounts()).toHaveLength(3); - - // Spy on the noteDataProvider.getNotesSpy - const getNotesSpy = jest.spyOn(noteDataProvider, 'getNotes'); - - // Call the function under test - await pxeOracleInterface.syncNoteNullifiers(contractAddress); - - // Verify applyNullifiers was called once for all accounts - expect(getNotesSpy).toHaveBeenCalledTimes(1); - - // Verify getNotes was called with the correct contract address - expect(getNotesSpy).toHaveBeenCalledWith(expect.objectContaining({ contractAddress })); - }); - }); - - const setSyncedBlockNumber = (blockNumber: BlockNumber) => { - return anchorBlockDataProvider.setHeader( - BlockHeader.empty({ - globalVariables: GlobalVariables.empty({ blockNumber }), - }), - ); - }; -}); diff --git a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts b/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts deleted file mode 100644 index d21d8f01add7..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/pxe_oracle_interface.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Fr } from '@aztec/foundation/curves/bn254'; -import { createLogger } from '@aztec/foundation/log'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { DataInBlock } from '@aztec/stdlib/block'; -import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/client'; -import { MerkleTreeId } from '@aztec/stdlib/trees'; - -import type { ExecutionDataProvider } from '../contract_function_simulator/execution_data_provider.js'; -import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; -import type { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; -import type { ProxiedNode } from './proxied_node.js'; - -/** - * A data layer that provides and stores information needed for simulating/proving a transaction. - */ -export class PXEOracleInterface implements ExecutionDataProvider { - // Note: The Aztec node and senderDataProvider are exposed publicly since PXEOracleInterface will be deprecated soon - // (issue #17776). When refactoring tagging, it made sense to align with this future change by moving the sender - // tagging index sync functionality elsewhere. This required exposing these two properties since there is currently - // no alternative way to access them in the PrivateExecutionOracle. - constructor( - public readonly aztecNode: AztecNode | ProxiedNode, - private noteDataProvider: NoteDataProvider, - private anchorBlockDataProvider: AnchorBlockDataProvider, - private log = createLogger('pxe:pxe_oracle_interface'), - ) {} - - /** - * Looks for nullifiers of active contract notes and marks them as nullified if a nullifier is found. - * - * Fetches notes from the NoteDataProvider and checks which nullifiers are present in the - * onchain nullifier Merkle tree - up to the latest locally synced block. We use the - * locally synced block instead of querying the chain's 'latest' block to ensure correctness: - * notes are only marked nullified once their corresponding nullifier has been included in a - * block up to which the PXE has synced. - * This allows recent nullifications to be processed even if the node is not an archive node. - * - * @param contractAddress - The contract whose notes should be checked and nullified. - */ - public async syncNoteNullifiers(contractAddress: AztecAddress) { - this.log.verbose('Searching for nullifiers of known notes', { contract: contractAddress }); - - const syncedBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - - const contractNotes = await this.noteDataProvider.getNotes({ contractAddress }); - - if (contractNotes.length === 0) { - return; - } - - const nullifiersToCheck = contractNotes.map(note => note.siloedNullifier); - const nullifierBatches = nullifiersToCheck.reduce( - (acc, nullifier) => { - if (acc[acc.length - 1].length < MAX_RPC_LEN) { - acc[acc.length - 1].push(nullifier); - } else { - acc.push([nullifier]); - } - return acc; - }, - [[]] as Fr[][], - ); - const nullifierIndexes = ( - await Promise.all( - nullifierBatches.map(batch => - this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, batch), - ), - ) - ).flat(); - - const foundNullifiers = nullifiersToCheck - .map((nullifier, i) => { - if (nullifierIndexes[i] !== undefined) { - return { ...nullifierIndexes[i], ...{ data: nullifier } } as DataInBlock; - } - }) - .filter(nullifier => nullifier !== undefined) as DataInBlock[]; - - const nullifiedNotes = await this.noteDataProvider.applyNullifiers(foundNullifiers); - nullifiedNotes.forEach(noteDao => { - this.log.verbose(`Removed note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`, { - contract: noteDao.contractAddress, - slot: noteDao.storageSlot, - nullifier: noteDao.siloedNullifier.toString(), - }); - }); - } -} diff --git a/yarn-project/pxe/src/entrypoints/client/bundle/index.ts b/yarn-project/pxe/src/entrypoints/client/bundle/index.ts index 9d60537ef276..418b4f0eba11 100644 --- a/yarn-project/pxe/src/entrypoints/client/bundle/index.ts +++ b/yarn-project/pxe/src/entrypoints/client/bundle/index.ts @@ -4,5 +4,4 @@ export * from '../../../error_enriching.js'; export * from '../../../storage/index.js'; export * from './utils.js'; export * from '../../../contract_function_simulator/oracle/common.js'; -export { PXEOracleInterface } from '../../../contract_function_simulator/pxe_oracle_interface.js'; export type { PXECreationOptions } from '../../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/entrypoints/client/lazy/index.ts b/yarn-project/pxe/src/entrypoints/client/lazy/index.ts index f84c9d5a79bb..90daeea05941 100644 --- a/yarn-project/pxe/src/entrypoints/client/lazy/index.ts +++ b/yarn-project/pxe/src/entrypoints/client/lazy/index.ts @@ -4,5 +4,4 @@ export * from '../../../storage/index.js'; export * from '../../../error_enriching.js'; export * from './utils.js'; export * from '../../../contract_function_simulator/oracle/common.js'; -export { PXEOracleInterface } from '../../../contract_function_simulator/pxe_oracle_interface.js'; export { type PXECreationOptions } from '../../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/entrypoints/server/index.ts b/yarn-project/pxe/src/entrypoints/server/index.ts index 5a70a5108d17..c0c687e17023 100644 --- a/yarn-project/pxe/src/entrypoints/server/index.ts +++ b/yarn-project/pxe/src/entrypoints/server/index.ts @@ -4,6 +4,5 @@ export * from '../../error_enriching.js'; export * from '../../storage/index.js'; export * from './utils.js'; export * from '../../contract_function_simulator/oracle/common.js'; -export { PXEOracleInterface } from '../../contract_function_simulator/pxe_oracle_interface.js'; export { ORACLE_VERSION } from '../../oracle_version.js'; export { type PXECreationOptions } from '../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 78711ec6b1f8..28adf3acb740 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -60,8 +60,6 @@ import { } from './contract_function_simulator/contract_function_simulator.js'; import { readCurrentClassIdFromNode } from './contract_function_simulator/oracle/private_execution.js'; import { ProxiedContractDataProviderFactory } from './contract_function_simulator/proxied_contract_data_source.js'; -import { ProxiedNodeFactory } from './contract_function_simulator/proxied_node.js'; -import { PXEOracleInterface } from './contract_function_simulator/pxe_oracle_interface.js'; import { PXEDebugUtils } from './debug/pxe_debug_utils.js'; import { enrichPublicSimulationError, enrichSimulationError } from './error_enriching.js'; import { PrivateEventFilterValidator } from './events/private_event_filter_validator.js'; @@ -195,14 +193,7 @@ export class PXE { overrides?.contracts, ); - const pxeOracleInterface = new PXEOracleInterface( - ProxiedNodeFactory.create(this.node), - this.noteDataProvider, - this.anchorBlockDataProvider, - this.log, - ); return new ContractFunctionSimulator( - pxeOracleInterface, proxyContractDataProvider, this.noteDataProvider, this.keyStore, 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 5ea5d6ecf91b..e7e7cff7f265 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 @@ -17,7 +17,6 @@ import { CapsuleDataProvider, NoteDataProvider, ORACLE_VERSION, - PXEOracleInterface, PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, @@ -105,7 +104,6 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl private recipientTaggingDataProvider: RecipientTaggingDataProvider, private capsuleDataProvider: CapsuleDataProvider, private privateEventDataProvider: PrivateEventDataProvider, - private pxeOracleInterface: PXEOracleInterface, private nextBlockTimestamp: bigint, private version: Fr, private chainId: Fr, @@ -312,7 +310,6 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl HashedValuesCache.create([new HashedValues(args, argsHash)]), noteCache, taggingIndexCache, - this.pxeOracleInterface, this.contractDataProvider, this.noteDataProvider, this.keyStore, @@ -636,7 +633,6 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl [], [], anchorBlockHeader, - this.pxeOracleInterface, this.contractDataProvider, this.noteDataProvider, this.keyStore, diff --git a/yarn-project/txe/src/txe_session.test.ts b/yarn-project/txe/src/txe_session.test.ts index 7e0cd006330e..bf2a0d0cf32c 100644 --- a/yarn-project/txe/src/txe_session.test.ts +++ b/yarn-project/txe/src/txe_session.test.ts @@ -23,7 +23,6 @@ describe('TXESession.processFunction', () => { new Fr(1), // chainId new Fr(1), // version 0n, // nextBlockTimestamp - {} as any, // pxeOracleInterface ); }); diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 740bea1cdeda..c1587fa93aad 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -8,10 +8,10 @@ import { AddressDataProvider, CapsuleDataProvider, NoteDataProvider, - PXEOracleInterface, PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, + syncNoteNullifiers, } from '@aztec/pxe/server'; import { ExecutionNoteCache, @@ -132,7 +132,6 @@ export class TXESession implements TXESessionStateHandler { private chainId: Fr, private version: Fr, private nextBlockTimestamp: bigint, - private pxeOracleInterface: PXEOracleInterface, ) {} static async init(protocolContracts: ProtocolContract[]) { @@ -160,12 +159,6 @@ export class TXESession implements TXESessionStateHandler { const version = new Fr(await stateMachine.node.getVersion()); const chainId = new Fr(await stateMachine.node.getChainId()); - const pxeOracleInterface = new PXEOracleInterface( - stateMachine.node, - noteDataProvider, - stateMachine.anchorBlockDataProvider, - ); - const topLevelOracleHandler = new TXEOracleTopLevelContext( stateMachine, contractDataProvider, @@ -177,7 +170,6 @@ export class TXESession implements TXESessionStateHandler { recipientTaggingDataProvider, capsuleDataProvider, privateEventDataProvider, - pxeOracleInterface, nextBlockTimestamp, version, chainId, @@ -201,7 +193,6 @@ export class TXESession implements TXESessionStateHandler { version, chainId, nextBlockTimestamp, - pxeOracleInterface, ); } @@ -269,7 +260,6 @@ export class TXESession implements TXESessionStateHandler { this.recipientTaggingDataProvider, this.capsuleDataProvider, this.privateEventDataProvider, - this.pxeOracleInterface, this.nextBlockTimestamp, this.version, this.chainId, @@ -291,7 +281,12 @@ export class TXESession implements TXESessionStateHandler { // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not // be removed from the database. // TODO(#12553): make the synchronizer sync here instead and remove this - await this.pxeOracleInterface.syncNoteNullifiers(contractAddress); + await syncNoteNullifiers( + contractAddress, + this.stateMachine.anchorBlockDataProvider, + this.noteDataProvider, + this.stateMachine.node, + ); // Private execution has two associated block numbers: the anchor block (i.e. the historical block that is used to // build the proof), and the *next* block, i.e. the one we'll create once the execution ends, and which will contain @@ -321,7 +316,6 @@ export class TXESession implements TXESessionStateHandler { new HashedValuesCache(), noteCache, taggingIndexCache, - this.pxeOracleInterface, this.contractDataProvider, this.noteDataProvider, this.keyStore, @@ -377,7 +371,12 @@ export class TXESession implements TXESessionStateHandler { // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not // be removed from the database. // TODO(#12553): make the synchronizer sync here instead and remove this - await this.pxeOracleInterface.syncNoteNullifiers(contractAddress); + await syncNoteNullifiers( + contractAddress, + this.stateMachine.anchorBlockDataProvider, + this.noteDataProvider, + this.stateMachine.node, + ); const anchorBlockHeader = await this.stateMachine.anchorBlockDataProvider.getBlockHeader(); @@ -386,7 +385,6 @@ export class TXESession implements TXESessionStateHandler { [], [], anchorBlockHeader, - this.pxeOracleInterface, this.contractDataProvider, this.noteDataProvider, this.keyStore, From 46c5d3cccce1c2870223fcabef0cce1afc2f0d28 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 13:59:56 +0000 Subject: [PATCH 030/140] move getContractInstance to utility execution oracle --- .../src/contract_function_simulator/oracle/common.ts | 12 +----------- .../oracle/private_execution.ts | 8 ++++++-- .../oracle/utility_execution_oracle.ts | 11 +++++++++-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 77e5cb262782..c078f15bf2de 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -5,7 +5,7 @@ import type { KeyStore } from '@aztec/key-store'; import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; -import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; +import type { CompleteAddress } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; import { computeAddressSecret } from '@aztec/stdlib/keys'; @@ -44,16 +44,6 @@ import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; // TODO: this might not be the final home for these functions, // it's just a way of starting to dissolve PXEOracleInterface -export async function getContractInstance( - address: AztecAddress, - contractDataProvider: ContractDataProvider, -): Promise { - const instance = await contractDataProvider.getContractInstance(address); - if (!instance) { - throw new Error(`No contract instance found for address ${address.toString()}`); - } - return instance; -} export async function getFunctionArtifact( contractAddress: AztecAddress, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts index 8d1011850178..08d87f887a7a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts @@ -28,7 +28,7 @@ import { BlockHeader, PrivateCallExecutionResult } from '@aztec/stdlib/tx'; import type { UInt64 } from '@aztec/stdlib/types'; import { AnchorBlockDataProvider, ContractDataProvider } from '../../storage/index.js'; -import { getContractInstance, getPublicStorageAt } from './common.js'; +import { getPublicStorageAt } from './common.js'; import { Oracle } from './oracle.js'; import type { PrivateExecutionOracle } from './private_execution_oracle.js'; @@ -228,7 +228,11 @@ export async function verifyCurrentClassId( contractDataProvider: ContractDataProvider, header: BlockHeader, ) { - const instance = await getContractInstance(contractAddress, contractDataProvider); + const instance = await contractDataProvider.getContractInstance(contractAddress); + if (!instance) { + throw new Error(`No contract instance found for address ${contractAddress.toString()}`); + } + const currentClassId = await readCurrentClassIdFromCurrentBlockAnchor( contractAddress, instance, 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 0afbeee824c0..d9576e978053 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 @@ -31,7 +31,6 @@ import { bulkRetrieveLogs, getBlock, getCompleteAddress, - getContractInstance, getL1ToL2MembershipWitness, getLowNullifierMembershipWitness, getMembershipWitness, @@ -186,7 +185,15 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns A contract instance. */ public utilityGetContractInstance(address: AztecAddress): Promise { - return getContractInstance(address, this.contractDataProvider); + return this.getContractInstance(address); + } + + protected async getContractInstance(address: AztecAddress): Promise { + const instance = await this.contractDataProvider.getContractInstance(address); + if (!instance) { + throw new Error(`No contract instance found for address ${address.toString()}`); + } + return instance; } /** From 74fdb1586e09c676391bca4da587e3bf4c2ab8e7 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 14:29:26 +0000 Subject: [PATCH 031/140] move getNotes to UtilityExecutionOracle --- .../oracle/common.ts | 33 +-------------- .../oracle/private_execution_oracle.ts | 11 +---- .../oracle/utility_execution_oracle.ts | 40 ++++++++++++++----- 3 files changed, 34 insertions(+), 50 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index c078f15bf2de..1e125f4ab98c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -19,7 +19,7 @@ import { deriveEcdhSharedSecret, } from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; -import { Note, NoteDao, type NoteStatus } from '@aztec/stdlib/note'; +import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; @@ -61,37 +61,6 @@ export async function getFunctionArtifact( }; } -export async function getNotes( - contractAddress: AztecAddress, - owner: AztecAddress | undefined, - storageSlot: Fr, - status: NoteStatus, - noteDataProvider: NoteDataProvider, - scopes?: AztecAddress[], -) { - const noteDaos = await noteDataProvider.getNotes({ - contractAddress, - owner, - storageSlot, - status, - scopes, - }); - return noteDaos.map( - ({ contractAddress, owner, storageSlot, randomness, noteNonce, note, noteHash, siloedNullifier, index }) => ({ - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - note, - noteHash, - siloedNullifier, - // PXE can use this index to get full MembershipWitness - index, - }), - ); -} - export async function getCompleteAddress( account: AztecAddress, addressDataProvider: AddressDataProvider, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 174b2ca92823..7a5f843655b9 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -44,7 +44,7 @@ import type { ExecutionNoteCache } from '../execution_note_cache.js'; import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import type { HashedValuesCache } from '../hashed_values_cache.js'; import { pickNotes } from '../pick_notes.js'; -import { calculateDirectionalAppTaggingSecret, getFunctionArtifact, getNotes } from './common.js'; +import { calculateDirectionalAppTaggingSecret, getFunctionArtifact } from './common.js'; import type { IPrivateExecutionOracle, NoteData } from './interfaces.js'; import { executePrivateFunction, verifyCurrentClassId } from './private_execution.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; @@ -358,14 +358,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP const pendingNotes = this.noteCache.getNotes(this.callContext.contractAddress, owner, storageSlot); const pendingNullifiers = this.noteCache.getNullifiers(this.callContext.contractAddress); - const dbNotes = await getNotes( - this.callContext.contractAddress, - owner, - storageSlot, - status, - this.noteDataProvider, - this.scopes, - ); + const dbNotes = await this.getNotes(this.callContext.contractAddress, owner, storageSlot, status, this.scopes); const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value)); const notes = pickNotes([...dbNotesFiltered, ...pendingNotes], { 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 d9576e978053..1235eae03010 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 @@ -34,7 +34,6 @@ import { getL1ToL2MembershipWitness, getLowNullifierMembershipWitness, getMembershipWitness, - getNotes, getNullifierIndex, getNullifierMembershipWitness, getPublicDataWitness, @@ -245,14 +244,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra offset: number, status: NoteStatus, ): Promise { - const dbNotes = await getNotes( - this.contractAddress, - owner, - storageSlot, - status, - this.noteDataProvider, - this.scopes, - ); + const dbNotes = await this.getNotes(this.contractAddress, owner, storageSlot, status, this.scopes); return pickNotes(dbNotes, { selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({ selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] }, @@ -268,6 +260,36 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra }); } + protected async getNotes( + contractAddress: AztecAddress, + owner: AztecAddress | undefined, + storageSlot: Fr, + status: NoteStatus, + scopes?: AztecAddress[], + ) { + const noteDaos = await this.noteDataProvider.getNotes({ + contractAddress, + owner, + storageSlot, + status, + scopes, + }); + return noteDaos.map( + ({ contractAddress, owner, storageSlot, randomness, noteNonce, note, noteHash, siloedNullifier, index }) => ({ + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + note, + noteHash, + siloedNullifier, + // PXE can use this index to get full MembershipWitness + index, + }), + ); + } + /** * Check if a nullifier exists in the nullifier tree. * @param innerNullifier - The inner nullifier. From f4c1c6b2069fd3987ebb5e2eb492442e8d92cf1f Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 15:43:41 +0000 Subject: [PATCH 032/140] bring some more stuff inside oracles --- .../oracle/common.ts | 297 +----------------- .../oracle/private_execution_oracle.ts | 28 +- .../oracle/utility_execution_oracle.ts | 277 +++++++++++++++- 3 files changed, 280 insertions(+), 322 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 1e125f4ab98c..a6a4fd58f706 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,23 +1,11 @@ import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { Fr } from '@aztec/foundation/curves/bn254'; -import type { Point } from '@aztec/foundation/curves/grumpkin'; -import type { KeyStore } from '@aztec/key-store'; import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; -import type { CompleteAddress } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; -import { computeAddressSecret } from '@aztec/stdlib/keys'; -import { - DirectionalAppTaggingSecret, - PendingTaggedLog, - PrivateLogWithTxData, - PublicLog, - PublicLogWithTxData, - TxScopedL2Log, - deriveEcdhSharedSecret, -} from '@aztec/stdlib/logs'; +import { PrivateLogWithTxData, PublicLog, PublicLogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; @@ -25,15 +13,12 @@ import type { TxHash } from '@aztec/stdlib/tx'; import { ORACLE_VERSION } from '../../oracle_version.js'; import { - type AddressDataProvider, AnchorBlockDataProvider, type CapsuleDataProvider, type ContractDataProvider, type NoteDataProvider, PrivateEventDataProvider, - type RecipientTaggingDataProvider, } from '../../storage/index.js'; -import { SiloedTag, Tag, WINDOW_HALF_SIZE, getInitialIndexesMap, getPreTagsForTheWindow } from '../../tagging/index.js'; import type { ExecutionStats } from '../execution_data_provider.js'; import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; @@ -61,45 +46,6 @@ export async function getFunctionArtifact( }; } -export async function getCompleteAddress( - account: AztecAddress, - addressDataProvider: AddressDataProvider, -): Promise { - const completeAddress = await addressDataProvider.getCompleteAddress(account); - if (!completeAddress) { - throw new Error( - `No public key registered for address ${account}. - Register it by calling pxe.addAccount(...).\nSee docs for context: https://docs.aztec.network/developers/resources/debugging/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount`, - ); - } - return completeAddress; -} - -export async function calculateDirectionalAppTaggingSecret( - contractAddress: AztecAddress, - sender: AztecAddress, - recipient: AztecAddress, - addressDataProvider: AddressDataProvider, - keyStore: KeyStore, -) { - const senderCompleteAddress = await getCompleteAddress(sender, addressDataProvider); - const senderIvsk = await keyStore.getMasterIncomingViewingSecretKey(sender); - return DirectionalAppTaggingSecret.compute(senderCompleteAddress, senderIvsk, recipient, contractAddress, recipient); -} - -export async function getSharedSecret( - address: AztecAddress, - ephPk: Point, - addressDataProvider: AddressDataProvider, - keyStore: KeyStore, -): Promise { - // TODO(#12656): return an app-siloed secret - const recipientCompleteAddress = await getCompleteAddress(address, addressDataProvider); - const ivskM = await keyStore.getMasterSecretKey(recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey); - const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM); - return deriveEcdhSharedSecret(addressSecret, ephPk); -} - /** * Fetches a message from the db, given its key. * @param contractAddress - Address of a contract by which the message was emitted. @@ -240,247 +186,6 @@ export function getStats(aztecNode: AztecNode): ExecutionStats { return { nodeRPCCalls }; } -// TODO(#17775): Replace this implementation of this function with one implementing an approach similar -// to syncSenderTaggingIndexes. Not done yet due to re-prioritization to devex and this doesn't directly affect -// devex. -export async function syncTaggedLogs( - contractAddress: AztecAddress, - pendingTaggedLogArrayBaseSlot: Fr, - anchorBlockDataProvider: AnchorBlockDataProvider, - keyStore: KeyStore, - contractDataProvider: ContractDataProvider, - capsuleDataProvider: CapsuleDataProvider, - addressDataProvider: AddressDataProvider, - recipientTaggingDataProvider: RecipientTaggingDataProvider, - aztecNode: AztecNode, - scopes?: AztecAddress[], -) { - const maxBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - - // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles. - // However it is impossible at the moment due to the language not supporting nested slices. - // This nesting is necessary because for a given set of tags we don't - // know how many logs we will get back. Furthermore, these logs are of undetermined - // length, since we don't really know the note they correspond to until we decrypt them. - const recipients = scopes ? scopes : await keyStore.getAccounts(); - for (const recipient of recipients) { - // Get all the secrets for the recipient and sender pairs (#9365) - const indexedSecrets = await getLastUsedTaggingIndexesForSenders( - contractAddress, - recipient, - addressDataProvider, - keyStore, - recipientTaggingDataProvider, - ); - - // We fetch logs for a window of indexes in a range: - // . - // - // We use this window approach because it could happen that a sender might have messed up and inadvertently - // incremented their index without us getting any logs (for example, in case of a revert). If we stopped looking - // for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again. - // Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed - // some logs. For these reasons, we have to look both back and ahead of the stored index. - let secretsAndWindows = indexedSecrets.map(indexedSecret => { - if (indexedSecret.index === undefined) { - return { - secret: indexedSecret.secret, - leftMostIndex: 0, - rightMostIndex: WINDOW_HALF_SIZE, - }; - } else { - return { - secret: indexedSecret.secret, - leftMostIndex: Math.max(0, indexedSecret.index - WINDOW_HALF_SIZE), - rightMostIndex: indexedSecret.index + WINDOW_HALF_SIZE, - }; - } - }); - - // As we iterate we store the largest index we have seen for a given secret to later on store it in the db. - const newLargestIndexMapToStore: { [k: string]: number } = {}; - - // The initial/unmodified indexes of the secrets stored in a key-value map where key is the directional app - // tagging secret. - const initialIndexesMap = getInitialIndexesMap(indexedSecrets); - - while (secretsAndWindows.length > 0) { - const preTagsForTheWholeWindow = getPreTagsForTheWindow(secretsAndWindows); - const tagsForTheWholeWindow = await Promise.all( - preTagsForTheWholeWindow.map(async preTag => { - return SiloedTag.compute(await Tag.compute(preTag), contractAddress); - }), - ); - - // We store the new largest indexes we find in the iteration in the following map to later on construct - // a new set of secrets and windows to fetch logs for. - const newLargestIndexMapForIteration: { [k: string]: number } = {}; - - // Fetch the private logs for the tags and iterate over them - // TODO: The following conversion is unfortunate and we should most likely just type the #getPrivateLogsByTags - // to accept SiloedTag[] instead of Fr[]. That would result in a large change so I didn't do it yet. - const tagsForTheWholeWindowAsFr = tagsForTheWholeWindow.map(tag => tag.value); - const logsByTags = await internalGetPrivateLogsByTags(tagsForTheWholeWindowAsFr, aztecNode); - - for (let logIndex = 0; logIndex < logsByTags.length; logIndex++) { - const logsByTag = logsByTags[logIndex]; - if (logsByTag.length > 0) { - // We filter out the logs that are newer than the anchor block number of the tx currently being constructed - const filteredLogsByBlockNumber = logsByTag.filter(l => l.blockNumber <= maxBlockNumber); - - // We store the logs in capsules (to later be obtained in Noir) - await storePendingTaggedLogs( - contractAddress, - pendingTaggedLogArrayBaseSlot, - recipient, - filteredLogsByBlockNumber, - aztecNode, - capsuleDataProvider, - ); - - // We retrieve the pre-tag corresponding to the log as I need that to evaluate whether - // a new largest index have been found. - const preTagCorrespondingToLog = preTagsForTheWholeWindow[logIndex]; - const initialIndex = initialIndexesMap[preTagCorrespondingToLog.secret.toString()]; - - if ( - preTagCorrespondingToLog.index >= initialIndex && - (newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] === undefined || - preTagCorrespondingToLog.index >= - newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()]) - ) { - // We have found a new largest index so we store it for later processing (storing it in the db + fetching - // the difference of the window sets of current and the next iteration) - newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] = - preTagCorrespondingToLog.index + 1; - } - } - } - - // Now based on the new largest indexes we found, we will construct a new secrets and windows set to fetch logs - // for. Note that it's very unlikely that a new log from the current window would appear between the iterations - // so we fetch the logs only for the difference of the window sets. - const newSecretsAndWindows = []; - for (const [directionalAppTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration)) { - const maybeIndexedSecret = indexedSecrets.find( - indexedSecret => indexedSecret.secret.toString() === directionalAppTaggingSecret, - ); - if (maybeIndexedSecret) { - newSecretsAndWindows.push({ - secret: maybeIndexedSecret.secret, - // We set the left most index to the new index to avoid fetching the same logs again - leftMostIndex: newIndex, - rightMostIndex: newIndex + WINDOW_HALF_SIZE, - }); - - // We store the new largest index in the map to later store it in the db. - newLargestIndexMapToStore[directionalAppTaggingSecret] = newIndex; - } else { - throw new Error( - `Secret not found for directionalAppTaggingSecret ${directionalAppTaggingSecret}. This is a bug as it should never happen!`, - ); - } - } - - // Now we set the new secrets and windows and proceed to the next iteration. - secretsAndWindows = newSecretsAndWindows; - } - - // At this point we have processed all the logs for the recipient so we store the last used indexes in the db. - // newLargestIndexMapToStore contains "next" indexes to look for (one past the last found), so subtract 1 to get - // last used. - await recipientTaggingDataProvider.setLastUsedIndexes( - Object.entries(newLargestIndexMapToStore).map(([directionalAppTaggingSecret, index]) => ({ - secret: DirectionalAppTaggingSecret.fromString(directionalAppTaggingSecret), - index: index - 1, - })), - ); - } -} - -async function storePendingTaggedLogs( - contractAddress: AztecAddress, - capsuleArrayBaseSlot: Fr, - recipient: AztecAddress, - privateLogs: TxScopedL2Log[], - aztecNode: AztecNode, - capsuleDataProvider: CapsuleDataProvider, -) { - // Build all pending tagged logs upfront with their tx effects - const pendingTaggedLogs = await Promise.all( - privateLogs.map(async scopedLog => { - // TODO(#9789): get these effects along with the log - const txEffect = await aztecNode.getTxEffect(scopedLog.txHash); - if (!txEffect) { - throw new Error(`Could not find tx effect for tx hash ${scopedLog.txHash}`); - } - - const pendingTaggedLog = new PendingTaggedLog( - scopedLog.log.fields, - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - recipient, - ); - - return pendingTaggedLog.toFields(); - }), - ); - - return capsuleDataProvider.appendToCapsuleArray(contractAddress, capsuleArrayBaseSlot, pendingTaggedLogs); -} - -/** - * Returns the last used tagging indexes along with the directional app tagging secrets for a given recipient and all - * the senders in the address book. - * This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration - * of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment, - * so we're keeping it private for now. - * @param contractAddress - The contract address to silo the secret for - * @param recipient - The address receiving the notes - * @returns A list of directional app tagging secrets along with the last used tagging indexes. If the corresponding - * secret was never used, the index is undefined. - * TODO(#17775): The naming here is broken as the function name does not reflect the return type. Make sure this gets - * fixed when implementing the linked issue. - */ -async function getLastUsedTaggingIndexesForSenders( - contractAddress: AztecAddress, - recipient: AztecAddress, - addressDataProvider: AddressDataProvider, - keyStore: KeyStore, - recipientTaggingDataProvider: RecipientTaggingDataProvider, -): Promise<{ secret: DirectionalAppTaggingSecret; index: number | undefined }[]> { - const recipientCompleteAddress = await getCompleteAddress(recipient, addressDataProvider); - const recipientIvsk = await keyStore.getMasterIncomingViewingSecretKey(recipient); - - // We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves - // (recipient = us, sender = us) - const senders = [ - ...(await recipientTaggingDataProvider.getSenderAddresses()), - ...(await keyStore.getAccounts()), - ].filter((address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address))); - const secrets = await Promise.all( - senders.map(contact => { - return DirectionalAppTaggingSecret.compute( - recipientCompleteAddress, - recipientIvsk, - contact, - contractAddress, - recipient, - ); - }), - ); - const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); - if (indexes.length !== secrets.length) { - throw new Error('Indexes and directional app tagging secrets have different lengths'); - } - - return secrets.map((secret, i) => ({ - secret, - index: indexes[i], - })); -} - // TODO(#14555): delete this function and implement this behavior in the node instead export async function getPublicLogByTag( tag: Fr, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 7a5f843655b9..6c12fd5afb30 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -16,7 +16,7 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { PrivateContextInputs } from '@aztec/stdlib/kernel'; -import type { ContractClassLog, DirectionalAppTaggingSecret, PreTag } from '@aztec/stdlib/logs'; +import { type ContractClassLog, DirectionalAppTaggingSecret, type PreTag } from '@aztec/stdlib/logs'; import { Note, type NoteStatus } from '@aztec/stdlib/note'; import { type BlockHeader, @@ -44,7 +44,7 @@ import type { ExecutionNoteCache } from '../execution_note_cache.js'; import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import type { HashedValuesCache } from '../hashed_values_cache.js'; import { pickNotes } from '../pick_notes.js'; -import { calculateDirectionalAppTaggingSecret, getFunctionArtifact } from './common.js'; +import { getFunctionArtifact } from './common.js'; import type { IPrivateExecutionOracle, NoteData } from './interfaces.js'; import { executePrivateFunction, verifyCurrentClassId } from './private_execution.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; @@ -241,13 +241,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP * @returns An app tag to be used in a log. */ public async privateGetNextAppTagAsSender(sender: AztecAddress, recipient: AztecAddress): Promise { - const secret = await calculateDirectionalAppTaggingSecret( - this.contractAddress, - sender, - recipient, - this.addressDataProvider, - this.keyStore, - ); + const secret = await this.#calculateDirectionalAppTaggingSecret(this.contractAddress, sender, recipient); const index = await this.#getIndexToUseForSecret(secret); this.log.debug( @@ -258,6 +252,22 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP return Tag.compute({ secret, index }); } + async #calculateDirectionalAppTaggingSecret( + contractAddress: AztecAddress, + sender: AztecAddress, + recipient: AztecAddress, + ) { + const senderCompleteAddress = await this.getCompleteAddress(sender); + const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); + return DirectionalAppTaggingSecret.compute( + senderCompleteAddress, + senderIvsk, + recipient, + contractAddress, + recipient, + ); + } + async #getIndexToUseForSecret(secret: DirectionalAppTaggingSecret): Promise { // If we have the tagging index in the cache, we use it. If not we obtain it from the execution data provider. const lastUsedIndexInTx = this.taggingIndexCache.getLastUsedIndex(secret); 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 1235eae03010..b06c8185cad2 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 @@ -10,6 +10,13 @@ import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import { siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; +import { computeAddressSecret } from '@aztec/stdlib/keys'; +import { + DirectionalAppTaggingSecret, + PendingTaggedLog, + TxScopedL2Log, + deriveEcdhSharedSecret, +} from '@aztec/stdlib/logs'; import type { NoteStatus } from '@aztec/stdlib/note'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; @@ -24,13 +31,13 @@ import type { RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; +import { SiloedTag, Tag, WINDOW_HALF_SIZE, getInitialIndexesMap, getPreTagsForTheWindow } from '../../tagging/index.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; import { assertCompatibleOracleVersion, bulkRetrieveLogs, getBlock, - getCompleteAddress, getL1ToL2MembershipWitness, getLowNullifierMembershipWitness, getMembershipWitness, @@ -38,9 +45,7 @@ import { getNullifierMembershipWitness, getPublicDataWitness, getPublicStorageAt, - getSharedSecret, syncNoteNullifiers, - syncTaggedLogs, validateEnqueuedNotesAndEvents, } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; @@ -175,7 +180,18 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @throws An error if the account is not registered in the database. */ public utilityGetPublicKeysAndPartialAddress(account: AztecAddress): Promise { - return getCompleteAddress(account, this.addressDataProvider); + return this.getCompleteAddress(account); + } + + protected async getCompleteAddress(account: AztecAddress): Promise { + const completeAddress = await this.addressDataProvider.getCompleteAddress(account); + if (!completeAddress) { + throw new Error( + `No public key registered for address ${account}. + Register it by calling pxe.addAccount(...).\nSee docs for context: https://docs.aztec.network/developers/resources/debugging/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount`, + ); + } + return completeAddress; } /** @@ -354,18 +370,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } public async utilityFetchTaggedLogs(pendingTaggedLogArrayBaseSlot: Fr) { - await syncTaggedLogs( - this.contractAddress, - pendingTaggedLogArrayBaseSlot, - this.anchorBlockDataProvider, - this.keyStore, - this.contractDataProvider, - this.capsuleDataProvider, - this.addressDataProvider, - this.recipientTaggingDataProvider, - this.aztecNode, - this.scopes, - ); + await this.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes); await syncNoteNullifiers(this.contractAddress, this.anchorBlockDataProvider, this.noteDataProvider, this.aztecNode); } @@ -459,6 +464,244 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } public utilityGetSharedSecret(address: AztecAddress, ephPk: Point): Promise { - return getSharedSecret(address, ephPk, this.addressDataProvider, this.keyStore); + return this.getSharedSecret(address, ephPk); + } + + protected async getSharedSecret(address: AztecAddress, ephPk: Point): Promise { + // TODO(#12656): return an app-siloed secret + const recipientCompleteAddress = await this.getCompleteAddress(address); + const ivskM = await this.keyStore.getMasterSecretKey( + recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey, + ); + const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM); + return deriveEcdhSharedSecret(addressSecret, ephPk); + } + + // TODO(#17775): Replace this implementation of this function with one implementing an approach similar + // to syncSenderTaggingIndexes. Not done yet due to re-prioritization to devex and this doesn't directly affect + // devex. + protected async syncTaggedLogs( + contractAddress: AztecAddress, + pendingTaggedLogArrayBaseSlot: Fr, + scopes?: AztecAddress[], + ) { + const maxBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + + // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles. + // However it is impossible at the moment due to the language not supporting nested slices. + // This nesting is necessary because for a given set of tags we don't + // know how many logs we will get back. Furthermore, these logs are of undetermined + // length, since we don't really know the note they correspond to until we decrypt them. + const recipients = scopes ? scopes : await this.keyStore.getAccounts(); + for (const recipient of recipients) { + // Get all the secrets for the recipient and sender pairs (#9365) + const indexedSecrets = await this.getLastUsedTaggingIndexesForSenders(contractAddress, recipient); + + // We fetch logs for a window of indexes in a range: + // . + // + // We use this window approach because it could happen that a sender might have messed up and inadvertently + // incremented their index without us getting any logs (for example, in case of a revert). If we stopped looking + // for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again. + // Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed + // some logs. For these reasons, we have to look both back and ahead of the stored index. + let secretsAndWindows = indexedSecrets.map(indexedSecret => { + if (indexedSecret.index === undefined) { + return { + secret: indexedSecret.secret, + leftMostIndex: 0, + rightMostIndex: WINDOW_HALF_SIZE, + }; + } else { + return { + secret: indexedSecret.secret, + leftMostIndex: Math.max(0, indexedSecret.index - WINDOW_HALF_SIZE), + rightMostIndex: indexedSecret.index + WINDOW_HALF_SIZE, + }; + } + }); + + // As we iterate we store the largest index we have seen for a given secret to later on store it in the db. + const newLargestIndexMapToStore: { [k: string]: number } = {}; + + // The initial/unmodified indexes of the secrets stored in a key-value map where key is the directional app + // tagging secret. + const initialIndexesMap = getInitialIndexesMap(indexedSecrets); + + while (secretsAndWindows.length > 0) { + const preTagsForTheWholeWindow = getPreTagsForTheWindow(secretsAndWindows); + const tagsForTheWholeWindow = await Promise.all( + preTagsForTheWholeWindow.map(async preTag => { + return SiloedTag.compute(await Tag.compute(preTag), contractAddress); + }), + ); + + // We store the new largest indexes we find in the iteration in the following map to later on construct + // a new set of secrets and windows to fetch logs for. + const newLargestIndexMapForIteration: { [k: string]: number } = {}; + + // Fetch the private logs for the tags and iterate over them + // TODO: The following conversion is unfortunate and we should most likely just type the #getPrivateLogsByTags + // to accept SiloedTag[] instead of Fr[]. That would result in a large change so I didn't do it yet. + const tagsForTheWholeWindowAsFr = tagsForTheWholeWindow.map(tag => tag.value); + const logsByTags = await this.internalGetPrivateLogsByTags(tagsForTheWholeWindowAsFr); + + for (let logIndex = 0; logIndex < logsByTags.length; logIndex++) { + const logsByTag = logsByTags[logIndex]; + if (logsByTag.length > 0) { + // We filter out the logs that are newer than the anchor block number of the tx currently being constructed + const filteredLogsByBlockNumber = logsByTag.filter(l => l.blockNumber <= maxBlockNumber); + + // We store the logs in capsules (to later be obtained in Noir) + await this.storePendingTaggedLogs( + contractAddress, + pendingTaggedLogArrayBaseSlot, + recipient, + filteredLogsByBlockNumber, + ); + + // We retrieve the pre-tag corresponding to the log as I need that to evaluate whether + // a new largest index have been found. + const preTagCorrespondingToLog = preTagsForTheWholeWindow[logIndex]; + const initialIndex = initialIndexesMap[preTagCorrespondingToLog.secret.toString()]; + + if ( + preTagCorrespondingToLog.index >= initialIndex && + (newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] === undefined || + preTagCorrespondingToLog.index >= + newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()]) + ) { + // We have found a new largest index so we store it for later processing (storing it in the db + fetching + // the difference of the window sets of current and the next iteration) + newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] = + preTagCorrespondingToLog.index + 1; + } + } + } + + // Now based on the new largest indexes we found, we will construct a new secrets and windows set to fetch logs + // for. Note that it's very unlikely that a new log from the current window would appear between the iterations + // so we fetch the logs only for the difference of the window sets. + const newSecretsAndWindows = []; + for (const [directionalAppTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration)) { + const maybeIndexedSecret = indexedSecrets.find( + indexedSecret => indexedSecret.secret.toString() === directionalAppTaggingSecret, + ); + if (maybeIndexedSecret) { + newSecretsAndWindows.push({ + secret: maybeIndexedSecret.secret, + // We set the left most index to the new index to avoid fetching the same logs again + leftMostIndex: newIndex, + rightMostIndex: newIndex + WINDOW_HALF_SIZE, + }); + + // We store the new largest index in the map to later store it in the db. + newLargestIndexMapToStore[directionalAppTaggingSecret] = newIndex; + } else { + throw new Error( + `Secret not found for directionalAppTaggingSecret ${directionalAppTaggingSecret}. This is a bug as it should never happen!`, + ); + } + } + + // Now we set the new secrets and windows and proceed to the next iteration. + secretsAndWindows = newSecretsAndWindows; + } + + // At this point we have processed all the logs for the recipient so we store the last used indexes in the db. + // newLargestIndexMapToStore contains "next" indexes to look for (one past the last found), so subtract 1 to get + // last used. + await this.recipientTaggingDataProvider.setLastUsedIndexes( + Object.entries(newLargestIndexMapToStore).map(([directionalAppTaggingSecret, index]) => ({ + secret: DirectionalAppTaggingSecret.fromString(directionalAppTaggingSecret), + index: index - 1, + })), + ); + } + } + + protected async storePendingTaggedLogs( + contractAddress: AztecAddress, + capsuleArrayBaseSlot: Fr, + recipient: AztecAddress, + privateLogs: TxScopedL2Log[], + ) { + // Build all pending tagged logs upfront with their tx effects + const pendingTaggedLogs = await Promise.all( + privateLogs.map(async scopedLog => { + // TODO(#9789): get these effects along with the log + const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); + if (!txEffect) { + throw new Error(`Could not find tx effect for tx hash ${scopedLog.txHash}`); + } + + const pendingTaggedLog = new PendingTaggedLog( + scopedLog.log.fields, + scopedLog.txHash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + recipient, + ); + + return pendingTaggedLog.toFields(); + }), + ); + + return this.capsuleDataProvider.appendToCapsuleArray(contractAddress, capsuleArrayBaseSlot, pendingTaggedLogs); + } + + /** + * Returns the last used tagging indexes along with the directional app tagging secrets for a given recipient and all + * the senders in the address book. + * This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration + * of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment, + * so we're keeping it private for now. + * @param contractAddress - The contract address to silo the secret for + * @param recipient - The address receiving the notes + * @returns A list of directional app tagging secrets along with the last used tagging indexes. If the corresponding + * secret was never used, the index is undefined. + * TODO(#17775): The naming here is broken as the function name does not reflect the return type. Make sure this gets + * fixed when implementing the linked issue. + */ + protected async getLastUsedTaggingIndexesForSenders( + contractAddress: AztecAddress, + recipient: AztecAddress, + ): Promise<{ secret: DirectionalAppTaggingSecret; index: number | undefined }[]> { + const recipientCompleteAddress = await this.getCompleteAddress(recipient); + const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); + + // We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves + // (recipient = us, sender = us) + const senders = [ + ...(await this.recipientTaggingDataProvider.getSenderAddresses()), + ...(await this.keyStore.getAccounts()), + ].filter((address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address))); + const secrets = await Promise.all( + senders.map(contact => { + return DirectionalAppTaggingSecret.compute( + recipientCompleteAddress, + recipientIvsk, + contact, + contractAddress, + recipient, + ); + }), + ); + const indexes = await this.recipientTaggingDataProvider.getLastUsedIndexes(secrets); + if (indexes.length !== secrets.length) { + throw new Error('Indexes and directional app tagging secrets have different lengths'); + } + + return secrets.map((secret, i) => ({ + secret, + index: indexes[i], + })); + } + + // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This + // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. + protected async internalGetPrivateLogsByTags(tags: Fr[]): Promise { + const allLogs = await this.aztecNode.getLogsByTags(tags); + return allLogs.map(logs => logs.filter(log => !log.isFromPublic)); } } From 2a3c9dee1afa6bf4583a015e3aef7eb79902a256 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 15:51:21 +0000 Subject: [PATCH 033/140] getMembershipWitness --- .../oracle/common.ts | 33 ----------------- .../oracle/utility_execution_oracle.ts | 37 +++++++++++++++++-- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index a6a4fd58f706..b9e16668f32d 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -71,39 +71,6 @@ export async function getL1ToL2MembershipWitness( return new MessageLoadOracleInputs(messageIndex, siblingPath); } -export async function getMembershipWitness( - blockNumber: BlockParameter, - treeId: MerkleTreeId, - leafValue: Fr, - aztecNode: AztecNode, -): Promise { - const witness = await tryGetMembershipWitness(blockNumber, treeId, leafValue, aztecNode); - if (!witness) { - throw new Error(`Leaf value ${leafValue} not found in tree ${MerkleTreeId[treeId]} at block ${blockNumber}`); - } - return witness; -} - -async function tryGetMembershipWitness( - blockNumber: BlockParameter, - treeId: MerkleTreeId, - value: Fr, - aztecNode: AztecNode, -): Promise { - switch (treeId) { - case MerkleTreeId.NULLIFIER_TREE: - return (await aztecNode.getNullifierMembershipWitness(blockNumber, value))?.withoutPreimage().toFields(); - case MerkleTreeId.NOTE_HASH_TREE: - return (await aztecNode.getNoteHashMembershipWitness(blockNumber, value))?.toFields(); - case MerkleTreeId.PUBLIC_DATA_TREE: - return (await aztecNode.getPublicDataWitness(blockNumber, value))?.withoutPreimage().toFields(); - case MerkleTreeId.ARCHIVE: - return (await aztecNode.getArchiveMembershipWitness(blockNumber, value))?.toFields(); - default: - throw new Error('Not implemented'); - } -} - export async function getLowNullifierMembershipWitness( blockNumber: BlockParameter, nullifier: Fr, 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 b06c8185cad2..77279419bf8e 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 @@ -6,6 +6,7 @@ import { LogLevels, applyStringFormatting, createLogger } from '@aztec/foundatio import type { KeyStore } from '@aztec/key-store'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import type { BlockParameter } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import { siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; @@ -18,7 +19,7 @@ import { deriveEcdhSharedSecret, } from '@aztec/stdlib/logs'; import type { NoteStatus } from '@aztec/stdlib/note'; -import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; +import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; import type { @@ -40,7 +41,6 @@ import { getBlock, getL1ToL2MembershipWitness, getLowNullifierMembershipWitness, - getMembershipWitness, getNullifierIndex, getNullifierMembershipWitness, getPublicDataWitness, @@ -115,7 +115,38 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns The index and sibling path concatenated [index, sibling_path] */ public utilityGetMembershipWitness(blockNumber: BlockNumber, treeId: MerkleTreeId, leafValue: Fr): Promise { - return getMembershipWitness(blockNumber, treeId, leafValue, this.aztecNode); + return this.getMembershipWitness(blockNumber, treeId, leafValue); + } + + protected async getMembershipWitness( + blockNumber: BlockParameter, + treeId: MerkleTreeId, + leafValue: Fr, + ): Promise { + const witness = await this.tryGetMembershipWitness(blockNumber, treeId, leafValue); + if (!witness) { + throw new Error(`Leaf value ${leafValue} not found in tree ${MerkleTreeId[treeId]} at block ${blockNumber}`); + } + return witness; + } + + protected async tryGetMembershipWitness( + blockNumber: BlockParameter, + treeId: MerkleTreeId, + value: Fr, + ): Promise { + switch (treeId) { + case MerkleTreeId.NULLIFIER_TREE: + return (await this.aztecNode.getNullifierMembershipWitness(blockNumber, value))?.withoutPreimage().toFields(); + case MerkleTreeId.NOTE_HASH_TREE: + return (await this.aztecNode.getNoteHashMembershipWitness(blockNumber, value))?.toFields(); + case MerkleTreeId.PUBLIC_DATA_TREE: + return (await this.aztecNode.getPublicDataWitness(blockNumber, value))?.withoutPreimage().toFields(); + case MerkleTreeId.ARCHIVE: + return (await this.aztecNode.getArchiveMembershipWitness(blockNumber, value))?.toFields(); + default: + throw new Error('Not implemented'); + } } /** From 8ae2fb2edcad74846cca20db78f748eddd9c446f Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 15:56:52 +0000 Subject: [PATCH 034/140] more --- .../oracle/common.ts | 28 ------------------- .../oracle/utility_execution_oracle.ts | 22 +++++++++++++-- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index b9e16668f32d..9918a32827d6 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,4 +1,3 @@ -import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { Fr } from '@aztec/foundation/curves/bn254'; import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; @@ -6,7 +5,6 @@ import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; import { PrivateLogWithTxData, PublicLog, PublicLogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; -import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; @@ -25,7 +23,6 @@ import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; import { NoteValidationRequest } from '../noir-structs/note_validation_request.js'; import type { ProxiedNode } from '../proxied_node.js'; -import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; // TODO: this might not be the final home for these functions, // it's just a way of starting to dissolve PXEOracleInterface @@ -46,31 +43,6 @@ export async function getFunctionArtifact( }; } -/** - * Fetches a message from the db, given its key. - * @param contractAddress - Address of a contract by which the message was emitted. - * @param messageHash - Hash of the message. - * @param secret - Secret used to compute a nullifier. - * @dev Contract address and secret are only used to compute the nullifier to get non-nullified messages - * @returns The l1 to l2 membership witness (index of message in the tree and sibling path). - */ -export async function getL1ToL2MembershipWitness( - contractAddress: AztecAddress, - messageHash: Fr, - secret: Fr, - aztecNode: AztecNode, -): Promise> { - const [messageIndex, siblingPath] = await getNonNullifiedL1ToL2MessageWitness( - aztecNode, - contractAddress, - messageHash, - secret, - ); - - // Assuming messageIndex is what you intended to use for the index in MessageLoadOracleInputs - return new MessageLoadOracleInputs(messageIndex, siblingPath); -} - export async function getLowNullifierMembershipWitness( blockNumber: BlockParameter, nullifier: Fr, 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 77279419bf8e..9790c8514d5a 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,3 +1,4 @@ +import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { BlockNumber } from '@aztec/foundation/branded-types'; import { Aes128 } from '@aztec/foundation/crypto/aes128'; import { Fr } from '@aztec/foundation/curves/bn254'; @@ -18,6 +19,7 @@ import { TxScopedL2Log, deriveEcdhSharedSecret, } from '@aztec/stdlib/logs'; +import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; @@ -39,7 +41,6 @@ import { assertCompatibleOracleVersion, bulkRetrieveLogs, getBlock, - getL1ToL2MembershipWitness, getLowNullifierMembershipWitness, getNullifierIndex, getNullifierMembershipWitness, @@ -49,6 +50,7 @@ import { validateEnqueuedNotesAndEvents, } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; +import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; /** * The oracle for an execution of utility contract functions. @@ -357,7 +359,23 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns The l1 to l2 membership witness (index of message in the tree and sibling path). */ public async utilityGetL1ToL2MembershipWitness(contractAddress: AztecAddress, messageHash: Fr, secret: Fr) { - return await getL1ToL2MembershipWitness(contractAddress, messageHash, secret, this.aztecNode); + return await this.getL1ToL2MembershipWitness(contractAddress, messageHash, secret); + } + + protected async getL1ToL2MembershipWitness( + contractAddress: AztecAddress, + messageHash: Fr, + secret: Fr, + ): Promise> { + const [messageIndex, siblingPath] = await getNonNullifiedL1ToL2MessageWitness( + this.aztecNode, + contractAddress, + messageHash, + secret, + ); + + // Assuming messageIndex is what you intended to use for the index in MessageLoadOracleInputs + return new MessageLoadOracleInputs(messageIndex, siblingPath); } /** From 79d4fdac4a353580612051bbd72b6fa77b363db3 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 16:33:30 +0000 Subject: [PATCH 035/140] common => utility_execution_oracle 1/ --- .../oracle/common.test.ts | 14 ------ .../oracle/common.ts | 13 ------ .../oracle/utility_execution.test.ts | 46 ++++++++++++++++++- .../oracle/utility_execution_oracle.ts | 14 +++++- 4 files changed, 57 insertions(+), 30 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index 0bf2f3b5fb88..cfb86ffffcf8 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -39,7 +39,6 @@ import { deliverEvent, deliverNote, getBlock, - getLowNullifierMembershipWitness, getPrivateLogByTag, getPublicDataWitness, getPublicLogByTag, @@ -92,28 +91,15 @@ describe('Common oracle functions', () => { describe('Respects synced block number', () => { const syncedBlockNumber = 100; - let nullifier: Fr; let contractAddress: AztecAddress; let leafSlot: Fr; beforeEach(async () => { leafSlot = Fr.random(); - nullifier = Fr.random(); contractAddress = await AztecAddress.random(); await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); }); - it('throws when getting low nullifier membership witness for future block', async () => { - await expect( - getLowNullifierMembershipWitness( - BlockNumber(syncedBlockNumber + 1), - nullifier, - anchorBlockDataProvider, - aztecNode, - ), - ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); - }); - it('throws when getting block for future block number', async () => { await expect(getBlock(BlockNumber(syncedBlockNumber + 1), anchorBlockDataProvider, aztecNode)).rejects.toThrow( `Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 9918a32827d6..6a1e1bb7b64b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -43,19 +43,6 @@ export async function getFunctionArtifact( }; } -export async function getLowNullifierMembershipWitness( - blockNumber: BlockParameter, - nullifier: Fr, - anchorBlockDataProvider: AnchorBlockDataProvider, - aztecNode: AztecNode, -): Promise { - const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier); -} - export async function getBlock( blockNumber: BlockParameter, anchorBlockDataProvider: AnchorBlockDataProvider, 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 1105d3050249..540d6c3b7121 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 @@ -9,7 +9,7 @@ import { L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress, type ContractInstanceWithAddress } from '@aztec/stdlib/contract'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { Note, NoteDao } from '@aztec/stdlib/note'; -import { BlockHeader, TxHash } from '@aztec/stdlib/tx'; +import { BlockHeader, GlobalVariables, TxHash } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; @@ -24,6 +24,7 @@ import { SenderTaggingDataProvider, } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; +import { UtilityExecutionOracle } from './utility_execution_oracle.js'; describe('Utility Execution test suite', () => { const simulator = new WASMSimulator(); @@ -148,4 +149,47 @@ describe('Utility Execution test suite', () => { expect(result).toEqual([new Fr(9)]); }, 30_000); + + describe('UtilityExecutionOracle', () => { + describe('Respects synced block number', () => { + const syncedBlockNumber = 100; + let nullifier: Fr; + let contractAddress: AztecAddress; + // let leafSlot: Fr; + let utilityExecutionOracle: UtilityExecutionOracle; + + beforeEach(async () => { + // leafSlot = Fr.random(); + nullifier = Fr.random(); + contractAddress = await AztecAddress.random(); + anchorBlockHeader = BlockHeader.empty({ + globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(syncedBlockNumber) }), + }); + anchorBlockDataProvider.getBlockHeader.mockResolvedValue(anchorBlockHeader); + + utilityExecutionOracle = new UtilityExecutionOracle( + contractAddress, + [], + [], + anchorBlockHeader, + contractDataProvider, + noteDataProvider, + keyStore, + addressDataProvider, + aztecNode, + anchorBlockDataProvider, + senderTaggingDataProvider, + recipientTaggingDataProvider, + capsuleDataProvider, + privateEventDataProvider, + ); + }); + + it('throws when getting low nullifier membership witness for future block', async () => { + await expect( + utilityExecutionOracle.utilityGetLowNullifierMembershipWitness(BlockNumber(syncedBlockNumber + 1), nullifier), + ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); + }); + }); + }); }); 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 9790c8514d5a..8d3d1b7df367 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 @@ -41,7 +41,6 @@ import { assertCompatibleOracleVersion, bulkRetrieveLogs, getBlock, - getLowNullifierMembershipWitness, getNullifierIndex, getNullifierMembershipWitness, getPublicDataWitness, @@ -177,7 +176,18 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, nullifier: Fr, ): Promise { - return await getLowNullifierMembershipWitness(blockNumber, nullifier, this.anchorBlockDataProvider, this.aztecNode); + return await this.getLowNullifierMembershipWitness(blockNumber, nullifier); + } + + protected async getLowNullifierMembershipWitness( + blockNumber: BlockParameter, + nullifier: Fr, + ): Promise { + const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return this.aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier); } /** From 71d42bed2bc10d984ceefc47f6ba68c2294566e4 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 16:39:20 +0000 Subject: [PATCH 036/140] common => utility_execution_oracle 2/ --- .../oracle/common.test.ts | 7 ------- .../contract_function_simulator/oracle/common.ts | 14 +------------- .../oracle/utility_execution.test.ts | 6 ++++++ .../oracle/utility_execution_oracle.ts | 13 ++++++++++--- 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index cfb86ffffcf8..d5a4803d7069 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -38,7 +38,6 @@ import { bulkRetrieveLogs, deliverEvent, deliverNote, - getBlock, getPrivateLogByTag, getPublicDataWitness, getPublicLogByTag, @@ -100,12 +99,6 @@ describe('Common oracle functions', () => { await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); }); - it('throws when getting block for future block number', async () => { - await expect(getBlock(BlockNumber(syncedBlockNumber + 1), anchorBlockDataProvider, aztecNode)).rejects.toThrow( - `Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`, - ); - }); - it('throws when getting public data witness for future block', async () => { await expect( getPublicDataWitness(BlockNumber(syncedBlockNumber + 1), leafSlot, anchorBlockDataProvider, aztecNode), diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 6a1e1bb7b64b..c96837b26d4b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,7 +1,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter, DataInBlock, L2Block } from '@aztec/stdlib/block'; +import type { BlockParameter, DataInBlock } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; import { PrivateLogWithTxData, PublicLog, PublicLogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; @@ -43,18 +43,6 @@ export async function getFunctionArtifact( }; } -export async function getBlock( - blockNumber: BlockParameter, - anchorBlockDataProvider: AnchorBlockDataProvider, - aztecNode: AztecNode, -): Promise { - const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return await aztecNode.getBlock(blockNumber); -} - export function getNullifierMembershipWitness( blockNumber: BlockParameter, nullifier: Fr, 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 540d6c3b7121..d3c68abeef37 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 @@ -190,6 +190,12 @@ describe('Utility Execution test suite', () => { utilityExecutionOracle.utilityGetLowNullifierMembershipWitness(BlockNumber(syncedBlockNumber + 1), nullifier), ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); }); + + it('throws when getting block for future block number', async () => { + await expect(utilityExecutionOracle.utilityGetBlockHeader(BlockNumber(syncedBlockNumber + 1))).rejects.toThrow( + `Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`, + ); + }); }); }); }); 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 8d3d1b7df367..da58ede747d6 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 @@ -7,7 +7,7 @@ import { LogLevels, applyStringFormatting, createLogger } from '@aztec/foundatio import type { KeyStore } from '@aztec/key-store'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter } from '@aztec/stdlib/block'; +import type { BlockParameter, L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import { siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; @@ -40,7 +40,6 @@ import { pickNotes } from '../pick_notes.js'; import { assertCompatibleOracleVersion, bulkRetrieveLogs, - getBlock, getNullifierIndex, getNullifierMembershipWitness, getPublicDataWitness, @@ -209,13 +208,21 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns Block extracted from a block with block number `blockNumber`. */ public async utilityGetBlockHeader(blockNumber: BlockNumber): Promise { - const block = await getBlock(blockNumber, this.anchorBlockDataProvider, this.aztecNode); + const block = await this.getBlock(blockNumber); if (!block) { return undefined; } return block.getBlockHeader(); } + protected async getBlock(blockNumber: BlockParameter): Promise { + const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return await this.aztecNode.getBlock(blockNumber); + } + /** * Retrieve the complete address associated to a given address. * @param account - The account address. From 7b6e8297b5bd8c9c3a5b2074cb2214b334be9664 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 16:48:56 +0000 Subject: [PATCH 037/140] common => utility_execution_oracle 3/ --- .../oracle/common.ts | 19 +------------------ .../oracle/utility_execution_oracle.ts | 3 +-- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index c96837b26d4b..c2a7871c0b3a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -6,7 +6,7 @@ import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } fr import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; import { PrivateLogWithTxData, PublicLog, PublicLogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; -import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; +import { MerkleTreeId, PublicDataWitness } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; import { ORACLE_VERSION } from '../../oracle_version.js'; @@ -43,23 +43,6 @@ export async function getFunctionArtifact( }; } -export function getNullifierMembershipWitness( - blockNumber: BlockParameter, - nullifier: Fr, - aztecNode: AztecNode, -): Promise { - return aztecNode.getNullifierMembershipWitness(blockNumber, nullifier); -} - -export async function getNullifierMembershipWitnessAtLatestBlock( - nullifier: Fr, - anchorBlockDataProvider: AnchorBlockDataProvider, - aztecNode: AztecNode, -) { - const blockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - return getNullifierMembershipWitness(blockNumber, nullifier, aztecNode); -} - export async function getPublicDataWitness( blockNumber: BlockParameter, leafSlot: Fr, 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 da58ede747d6..5e441ff8795c 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 @@ -41,7 +41,6 @@ import { assertCompatibleOracleVersion, bulkRetrieveLogs, getNullifierIndex, - getNullifierMembershipWitness, getPublicDataWitness, getPublicStorageAt, syncNoteNullifiers, @@ -159,7 +158,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, nullifier: Fr, ): Promise { - return await getNullifierMembershipWitness(blockNumber, nullifier, this.aztecNode); + return await this.aztecNode.getNullifierMembershipWitness(blockNumber, nullifier); } /** From 9074c82b307dd988d5d4b1879b61ece6893c4a34 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 16:54:39 +0000 Subject: [PATCH 038/140] common => utility_execution_oracle 4/ --- .../oracle/common.test.ts | 7 ------- .../contract_function_simulator/oracle/common.ts | 15 +-------------- .../oracle/utility_execution.test.ts | 10 ++++++++-- .../oracle/utility_execution_oracle.ts | 14 ++++++++++++-- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index d5a4803d7069..1bfc34e2054c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -39,7 +39,6 @@ import { deliverEvent, deliverNote, getPrivateLogByTag, - getPublicDataWitness, getPublicLogByTag, getPublicStorageAt, syncNoteNullifiers, @@ -99,12 +98,6 @@ describe('Common oracle functions', () => { await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); }); - it('throws when getting public data witness for future block', async () => { - await expect( - getPublicDataWitness(BlockNumber(syncedBlockNumber + 1), leafSlot, anchorBlockDataProvider, aztecNode), - ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); - }); - it('throws when getting public storage for future block', async () => { await expect( getPublicStorageAt( diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index c2a7871c0b3a..b095129e5c56 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -6,7 +6,7 @@ import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } fr import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; import { PrivateLogWithTxData, PublicLog, PublicLogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; -import { MerkleTreeId, PublicDataWitness } from '@aztec/stdlib/trees'; +import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; import { ORACLE_VERSION } from '../../oracle_version.js'; @@ -43,19 +43,6 @@ export async function getFunctionArtifact( }; } -export async function getPublicDataWitness( - blockNumber: BlockParameter, - leafSlot: Fr, - anchorBlockDataProvider: AnchorBlockDataProvider, - aztecNode: AztecNode, -): Promise { - const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return await aztecNode.getPublicDataWitness(blockNumber, leafSlot); -} - export async function getPublicStorageAt( blockNumber: BlockParameter, contract: AztecAddress, 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 d3c68abeef37..bede9c473843 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 @@ -155,11 +155,11 @@ describe('Utility Execution test suite', () => { const syncedBlockNumber = 100; let nullifier: Fr; let contractAddress: AztecAddress; - // let leafSlot: Fr; + let leafSlot: Fr; let utilityExecutionOracle: UtilityExecutionOracle; beforeEach(async () => { - // leafSlot = Fr.random(); + leafSlot = Fr.random(); nullifier = Fr.random(); contractAddress = await AztecAddress.random(); anchorBlockHeader = BlockHeader.empty({ @@ -196,6 +196,12 @@ describe('Utility Execution test suite', () => { `Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`, ); }); + + it('throws when getting public data witness for future block', async () => { + await expect( + utilityExecutionOracle.utilityGetPublicDataWitness(BlockNumber(syncedBlockNumber + 1), leafSlot), + ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); + }); }); }); }); 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 5e441ff8795c..964a51723325 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 @@ -41,7 +41,6 @@ import { assertCompatibleOracleVersion, bulkRetrieveLogs, getNullifierIndex, - getPublicDataWitness, getPublicStorageAt, syncNoteNullifiers, validateEnqueuedNotesAndEvents, @@ -198,7 +197,18 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, leafSlot: Fr, ): Promise { - return await getPublicDataWitness(blockNumber, leafSlot, this.anchorBlockDataProvider, this.aztecNode); + return await this.getPublicDataWitness(blockNumber, leafSlot); + } + + protected async getPublicDataWitness( + blockNumber: BlockParameter, + leafSlot: Fr, + ): Promise { + const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return await this.aztecNode.getPublicDataWitness(blockNumber, leafSlot); } /** From fb87a5a6ce9693e81b24af407e07f27432ad2648 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 17:06:46 +0000 Subject: [PATCH 039/140] common => utility_execution_oracle 5/ --- .../oracle/common.test.ts | 25 ------------------- .../oracle/common.ts | 14 ----------- .../oracle/private_execution.ts | 19 +++++++------- .../oracle/utility_execution.test.ts | 6 +++++ .../oracle/utility_execution_oracle.ts | 17 +++++++------ 5 files changed, 24 insertions(+), 57 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index 1bfc34e2054c..cb2201ed40e4 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -40,7 +40,6 @@ import { deliverNote, getPrivateLogByTag, getPublicLogByTag, - getPublicStorageAt, syncNoteNullifiers, } from './common.js'; @@ -87,30 +86,6 @@ describe('Common oracle functions', () => { contractAddress = await AztecAddress.random(); }); - describe('Respects synced block number', () => { - const syncedBlockNumber = 100; - let contractAddress: AztecAddress; - let leafSlot: Fr; - - beforeEach(async () => { - leafSlot = Fr.random(); - contractAddress = await AztecAddress.random(); - await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); - }); - - it('throws when getting public storage for future block', async () => { - await expect( - getPublicStorageAt( - BlockNumber(syncedBlockNumber + 1), - contractAddress, - leafSlot, - anchorBlockDataProvider, - aztecNode, - ), - ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); - }); - }); - describe('utilityBulkRetrieveLogs', () => { const unsiloedTag = Fr.random(); const REQUEST_SLOT = Fr.random(); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index b095129e5c56..aad9ecbc5edc 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -43,20 +43,6 @@ export async function getFunctionArtifact( }; } -export async function getPublicStorageAt( - blockNumber: BlockParameter, - contract: AztecAddress, - slot: Fr, - anchorBlockDataProvider: AnchorBlockDataProvider, - aztecNode: AztecNode, -): Promise { - const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return await aztecNode.getPublicStorageAt(blockNumber, contract, slot); -} - export function assertCompatibleOracleVersion(version: number): void { if (version !== ORACLE_VERSION) { throw new Error(`Incompatible oracle version. Expected version ${ORACLE_VERSION}, got ${version}.`); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts index 08d87f887a7a..ab2324f019de 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts @@ -28,7 +28,6 @@ import { BlockHeader, PrivateCallExecutionResult } from '@aztec/stdlib/tx'; import type { UInt64 } from '@aztec/stdlib/types'; import { AnchorBlockDataProvider, ContractDataProvider } from '../../storage/index.js'; -import { getPublicStorageAt } from './common.js'; import { Oracle } from './oracle.js'; import type { PrivateExecutionOracle } from './private_execution_oracle.js'; @@ -168,16 +167,16 @@ export async function readCurrentClassIdFromCurrentBlockAnchor( blockNumber: BlockNumber, timestamp: UInt64, ) { + const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + const { delayedPublicMutableSlot } = await DelayedPublicMutableValuesWithHash.getContractUpdateSlots(contractAddress); - const delayedPublicMutableValues = await DelayedPublicMutableValues.readFromTree(delayedPublicMutableSlot, slot => - getPublicStorageAt( - blockNumber, - ProtocolContractAddress.ContractInstanceRegistry, - slot, - anchorBlockDataProvider, - aztecNode, - ), - ); + const delayedPublicMutableValues = await DelayedPublicMutableValues.readFromTree(delayedPublicMutableSlot, slot => { + return aztecNode.getPublicStorageAt(blockNumber, ProtocolContractAddress.ContractInstanceRegistry, slot); + }); + let currentClassId = delayedPublicMutableValues.svc.getCurrentAt(timestamp)[0]; if (currentClassId.isZero()) { currentClassId = instance.originalContractClassId; 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 bede9c473843..4a10a94e961d 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 @@ -202,6 +202,12 @@ describe('Utility Execution test suite', () => { utilityExecutionOracle.utilityGetPublicDataWitness(BlockNumber(syncedBlockNumber + 1), leafSlot), ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); }); + + it('throws when getting public storage for future block', async () => { + await expect( + utilityExecutionOracle.utilityStorageRead(contractAddress, leafSlot, BlockNumber(syncedBlockNumber + 1), 1), + ).rejects.toThrow(`Block number ${syncedBlockNumber + 1} is higher than current block ${syncedBlockNumber}`); + }); }); }); }); 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 964a51723325..30f7f6d8254e 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 @@ -41,7 +41,6 @@ import { assertCompatibleOracleVersion, bulkRetrieveLogs, getNullifierIndex, - getPublicStorageAt, syncNoteNullifiers, validateEnqueuedNotesAndEvents, } from './common.js'; @@ -420,13 +419,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra const values = []; for (let i = 0n; i < numberOfElements; i++) { const storageSlot = new Fr(startStorageSlot.value + i); - const value = await getPublicStorageAt( - blockNumber, - contractAddress, - storageSlot, - this.anchorBlockDataProvider, - this.aztecNode, - ); + const value = await this.getPublicStorageAt(blockNumber, contractAddress, storageSlot); this.log.debug( `Oracle storage read: slot=${storageSlot.toString()} address-${contractAddress.toString()} value=${value}`, @@ -436,6 +429,14 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra return values; } + protected async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise { + const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return await this.aztecNode.getPublicStorageAt(blockNumber, contract, slot); + } + public utilityDebugLog(level: number, message: string, fields: Fr[]): void { if (!LogLevels[level]) { throw new Error(`Invalid debug log level: ${level}`); From 014fca71216bcbf4bc9a1526136aa54ce8b54667 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 17:20:29 +0000 Subject: [PATCH 040/140] common => utility_execution_oracle 6/ --- .../contract_function_simulator.ts | 8 ++++++-- .../pxe/src/contract_function_simulator/oracle/common.ts | 7 ------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index e9c3093e24a4..f01e33945c50 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -84,11 +84,12 @@ import type { import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; import { HashedValuesCache } from './hashed_values_cache.js'; -import { getFunctionArtifact, getStats } from './oracle/common.js'; +import { getFunctionArtifact } from './oracle/common.js'; import { Oracle } from './oracle/oracle.js'; import { executePrivateFunction, verifyCurrentClassId } from './oracle/private_execution.js'; import { PrivateExecutionOracle } from './oracle/private_execution_oracle.js'; import { UtilityExecutionOracle } from './oracle/utility_execution_oracle.js'; +import type { ProxiedNode } from './proxied_node.js'; /** * The contract function simulator. @@ -325,7 +326,10 @@ export class ContractFunctionSimulator { // docs:end:execute_utility_function getStats() { - return getStats(this.aztecNode); + const nodeRPCCalls = + typeof (this.aztecNode as ProxiedNode).getStats === 'function' ? (this.aztecNode as ProxiedNode).getStats() : {}; + + return { nodeRPCCalls }; } } diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index aad9ecbc5edc..99056e983f98 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -49,13 +49,6 @@ export function assertCompatibleOracleVersion(version: number): void { } } -export function getStats(aztecNode: AztecNode): ExecutionStats { - const nodeRPCCalls = - typeof (aztecNode as ProxiedNode).getStats === 'function' ? (aztecNode as ProxiedNode).getStats() : {}; - - return { nodeRPCCalls }; -} - // TODO(#14555): delete this function and implement this behavior in the node instead export async function getPublicLogByTag( tag: Fr, From b2cfbfb5133e79a5aa82684f14d8dc58132d186e Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 17:45:53 +0000 Subject: [PATCH 041/140] common => utility_execution_oracle 7/ --- .../oracle/common.test.ts | 94 +---------- .../oracle/common.ts | 64 +------- .../oracle/utility_execution.test.ts | 149 ++++++++++++++---- .../oracle/utility_execution_oracle.ts | 65 +++++++- 4 files changed, 183 insertions(+), 189 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index cb2201ed40e4..81396c9423ea 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -6,7 +6,7 @@ import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; -import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; +import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; import { NoteStatus } from '@aztec/stdlib/note'; @@ -27,21 +27,12 @@ import { type MockProxy, mock } from 'jest-mock-extended'; import { AddressDataProvider, AnchorBlockDataProvider, - CapsuleDataProvider, ContractDataProvider, NoteDao, NoteDataProvider, PrivateEventDataProvider, } from '../../storage/index.js'; -import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; -import { - bulkRetrieveLogs, - deliverEvent, - deliverNote, - getPrivateLogByTag, - getPublicLogByTag, - syncNoteNullifiers, -} from './common.js'; +import { deliverEvent, deliverNote, getPrivateLogByTag, getPublicLogByTag, syncNoteNullifiers } from './common.js'; jest.setTimeout(30_000); @@ -52,7 +43,6 @@ describe('Common oracle functions', () => { let contractDataProvider: ContractDataProvider; let anchorBlockDataProvider: AnchorBlockDataProvider; let keyStore: KeyStore; - let capsuleDataProvider: CapsuleDataProvider; let privateEventDataProvider: PrivateEventDataProvider; let noteDataProvider: NoteDataProvider; @@ -72,7 +62,6 @@ describe('Common oracle functions', () => { anchorBlockDataProvider = new AnchorBlockDataProvider(store); keyStore = new KeyStore(store); privateEventDataProvider = new PrivateEventDataProvider(store); - capsuleDataProvider = new CapsuleDataProvider(store); noteDataProvider = await NoteDataProvider.create(store); // Set up recipient account @@ -86,85 +75,6 @@ describe('Common oracle functions', () => { contractAddress = await AztecAddress.random(); }); - describe('utilityBulkRetrieveLogs', () => { - const unsiloedTag = Fr.random(); - const REQUEST_SLOT = Fr.random(); - const RESPONSE_SLOT = Fr.random(); - - beforeEach(() => { - aztecNode.getLogsByTags.mockReset(); - aztecNode.getTxEffect.mockReset(); - }); - - it('returns no logs if none are found', async () => { - aztecNode.getLogsByTags.mockResolvedValue([[]]); - - const request = new LogRetrievalRequest(contractAddress, unsiloedTag); - - await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); - await bulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT, capsuleDataProvider, aztecNode); - - expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); - - const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); - expect(responses.length).toEqual(1); - - // Check Option::none - expect(responses[0][0]).toEqual(new Fr(0)); // TODO: deserialize into option and check properly - }); - - it('returns a public log if one is found', async () => { - const scopedLog = await TxScopedL2Log.random(true); - (scopedLog.log as PublicLog).contractAddress = contractAddress; - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - - aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => - txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), - ); - - const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); - - await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); - await bulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT, capsuleDataProvider, aztecNode); - - expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); - - const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); - expect(responses.length).toEqual(1); - - // Check Option::some - expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly - }); - - it('returns a private log if one is found', async () => { - const scopedLog = await TxScopedL2Log.random(false); - scopedLog.log.fields[0] = await siloPrivateLog(contractAddress, Fr.random()); - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - aztecNode.getTxEffect.mockResolvedValue(indexedTxEffect); - - aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => - txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), - ); - - const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); - - await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); - await bulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT, capsuleDataProvider, aztecNode); - - expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); - - const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); - expect(responses.length).toEqual(1); - - // Check Option::some - expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly - }); - }); - describe('getPublicLogByTag', () => { const tag = Fr.random(); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 99056e983f98..f08694b1f9f1 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -2,7 +2,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock } from '@aztec/stdlib/block'; -import { computeUniqueNoteHash, siloNoteHash, siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; +import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; import { PrivateLogWithTxData, PublicLog, PublicLogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; @@ -17,12 +17,8 @@ import { type NoteDataProvider, PrivateEventDataProvider, } from '../../storage/index.js'; -import type { ExecutionStats } from '../execution_data_provider.js'; import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; -import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; -import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; import { NoteValidationRequest } from '../noir-structs/note_validation_request.js'; -import type { ProxiedNode } from '../proxied_node.js'; // TODO: this might not be the final home for these functions, // it's just a way of starting to dissolve PXEOracleInterface @@ -136,64 +132,6 @@ async function internalGetPublicLogsByTagsFromContract( return allPublicLogs.map(logs => logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress))); } -export async function bulkRetrieveLogs( - contractAddress: AztecAddress, - logRetrievalRequestsArrayBaseSlot: Fr, - logRetrievalResponsesArrayBaseSlot: Fr, - capsuleDataProvider: CapsuleDataProvider, - aztecNode: AztecNode, -) { - // We read all log retrieval requests and process them all concurrently. This makes the process much faster as we - // don't need to wait for the network round-trip. - const logRetrievalRequests = ( - await capsuleDataProvider.readCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot) - ).map(LogRetrievalRequest.fromFields); - - const maybeLogRetrievalResponses = await Promise.all( - logRetrievalRequests.map(async request => { - // TODO(#14555): remove these internal functions and have node endpoints that do this instead - const [publicLog, privateLog] = await Promise.all([ - getPublicLogByTag(request.unsiloedTag, request.contractAddress, aztecNode), - getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag), aztecNode), - ]); - - if (publicLog !== null) { - if (privateLog !== null) { - throw new Error( - `Found both a public and private log when searching for tag ${request.unsiloedTag} from contract ${request.contractAddress}`, - ); - } - - return new LogRetrievalResponse( - publicLog.logPayload, - publicLog.txHash, - publicLog.uniqueNoteHashesInTx, - publicLog.firstNullifierInTx, - ); - } else if (privateLog !== null) { - return new LogRetrievalResponse( - privateLog.logPayload, - privateLog.txHash, - privateLog.uniqueNoteHashesInTx, - privateLog.firstNullifierInTx, - ); - } else { - return null; - } - }), - ); - - // Requests are cleared once we're done. - await capsuleDataProvider.setCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot, []); - - // The responses are stored as Option in a second CapsuleArray. - await capsuleDataProvider.setCapsuleArray( - contractAddress, - logRetrievalResponsesArrayBaseSlot, - maybeLogRetrievalResponses.map(LogRetrievalResponse.toSerializedOption), - ); -} - export async function getNullifierIndex(nullifier: Fr, aztecNode: AztecNode) { return await findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier, aztecNode); } 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 4a10a94e961d..b26052dae02d 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 @@ -7,11 +7,14 @@ import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress, type ContractInstanceWithAddress } from '@aztec/stdlib/contract'; +import { siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; +import { PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; -import { BlockHeader, GlobalVariables, TxHash } from '@aztec/stdlib/tx'; +import { BlockHeader, GlobalVariables, TxHash, randomIndexedTxEffect } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; +import type { _MockProxy } from 'jest-mock-extended/lib/Mock.js'; import { AddressDataProvider, @@ -24,6 +27,7 @@ import { SenderTaggingDataProvider, } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; +import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; describe('Utility Execution test suite', () => { @@ -59,6 +63,7 @@ describe('Utility Execution test suite', () => { recipientTaggingDataProvider = mock(); capsuleDataProvider = mock(); privateEventDataProvider = mock(); + const capsuleArrays = new Map(); anchorBlockHeader = BlockHeader.random(); anchorBlockDataProvider.getBlockHeader.mockImplementation(() => Promise.resolve(anchorBlockHeader)); senderTaggingDataProvider.getLastFinalizedIndex.mockResolvedValue(undefined); @@ -69,7 +74,12 @@ describe('Utility Execution test suite', () => { recipientTaggingDataProvider.getLastUsedIndexes.mockImplementation(secrets => Promise.resolve(secrets.map(() => undefined)), ); - capsuleDataProvider.readCapsuleArray.mockResolvedValue([]); + capsuleDataProvider.setCapsuleArray.mockImplementation(async (address, slot, content) => { + capsuleArrays.set(`${address.toString()}:${slot.toString()}`, content); + }); + capsuleDataProvider.readCapsuleArray.mockImplementation(async (address, slot) => { + return capsuleArrays.get(`${address.toString()}:${slot.toString()}`) ?? []; + }); acirSimulator = new ContractFunctionSimulator( contractDataProvider, noteDataProvider, @@ -151,38 +161,121 @@ describe('Utility Execution test suite', () => { }, 30_000); describe('UtilityExecutionOracle', () => { + let contractAddress: AztecAddress; + let utilityExecutionOracle: UtilityExecutionOracle; + const syncedBlockNumber = 100; + + beforeEach(async () => { + contractAddress = await AztecAddress.random(); + anchorBlockHeader = BlockHeader.empty({ + globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(syncedBlockNumber) }), + }); + anchorBlockDataProvider.getBlockHeader.mockResolvedValue(anchorBlockHeader); + + utilityExecutionOracle = new UtilityExecutionOracle( + contractAddress, + [], + [], + anchorBlockHeader, + contractDataProvider, + noteDataProvider, + keyStore, + addressDataProvider, + aztecNode, + anchorBlockDataProvider, + senderTaggingDataProvider, + recipientTaggingDataProvider, + capsuleDataProvider, + privateEventDataProvider, + ); + }); + + describe('utilityBulkRetrieveLogs', () => { + const unsiloedTag = Fr.random(); + const REQUEST_SLOT = Fr.random(); + const RESPONSE_SLOT = Fr.random(); + + beforeEach(() => { + aztecNode.getLogsByTags.mockReset(); + aztecNode.getTxEffect.mockReset(); + }); + + it('returns no logs if none are found', async () => { + aztecNode.getLogsByTags.mockResolvedValue([[]]); + + const request = new LogRetrievalRequest(contractAddress, unsiloedTag); + + await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); + await utilityExecutionOracle.utilityBulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT); + + expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); + + const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); + expect(responses.length).toEqual(1); + + // Check Option::none + expect(responses[0][0]).toEqual(new Fr(0)); // TODO: deserialize into option and check properly + }); + + it('returns a public log if one is found', async () => { + const scopedLog = await TxScopedL2Log.random(true); + (scopedLog.log as PublicLog).contractAddress = contractAddress; + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + const indexedTxEffect = await randomIndexedTxEffect(); + + aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => + txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), + ); + + const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); + + await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); + await utilityExecutionOracle.utilityBulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT); + + expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); + + const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); + expect(responses.length).toEqual(1); + + // Check Option::some + expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly + }); + + it('returns a private log if one is found', async () => { + const scopedLog = await TxScopedL2Log.random(false); + scopedLog.log.fields[0] = await siloPrivateLog(contractAddress, Fr.random()); + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + const indexedTxEffect = await randomIndexedTxEffect(); + aztecNode.getTxEffect.mockResolvedValue(indexedTxEffect); + + aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => + txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), + ); + + const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); + + await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); + await utilityExecutionOracle.utilityBulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT); + + expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); + + const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); + expect(responses.length).toEqual(1); + + // Check Option::some + expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly + }); + }); + describe('Respects synced block number', () => { - const syncedBlockNumber = 100; let nullifier: Fr; - let contractAddress: AztecAddress; let leafSlot: Fr; - let utilityExecutionOracle: UtilityExecutionOracle; - beforeEach(async () => { + beforeEach(() => { leafSlot = Fr.random(); nullifier = Fr.random(); - contractAddress = await AztecAddress.random(); - anchorBlockHeader = BlockHeader.empty({ - globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(syncedBlockNumber) }), - }); - anchorBlockDataProvider.getBlockHeader.mockResolvedValue(anchorBlockHeader); - - utilityExecutionOracle = new UtilityExecutionOracle( - contractAddress, - [], - [], - anchorBlockHeader, - contractDataProvider, - noteDataProvider, - keyStore, - addressDataProvider, - aztecNode, - anchorBlockDataProvider, - senderTaggingDataProvider, - recipientTaggingDataProvider, - capsuleDataProvider, - privateEventDataProvider, - ); }); it('throws when getting low nullifier membership witness for future block', async () => { 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 30f7f6d8254e..4790c42e57cb 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 @@ -9,7 +9,7 @@ import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; -import { siloNullifier } from '@aztec/stdlib/hash'; +import { siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import { computeAddressSecret } from '@aztec/stdlib/keys'; @@ -35,12 +35,15 @@ import type { SenderTaggingDataProvider, } from '../../storage/index.js'; import { SiloedTag, Tag, WINDOW_HALF_SIZE, getInitialIndexesMap, getPreTagsForTheWindow } from '../../tagging/index.js'; +import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; +import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; import { assertCompatibleOracleVersion, - bulkRetrieveLogs, getNullifierIndex, + getPrivateLogByTag, + getPublicLogByTag, syncNoteNullifiers, validateEnqueuedNotesAndEvents, } from './common.js'; @@ -483,12 +486,62 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra throw new Error(`Got a note validation request from ${contractAddress}, expected ${this.contractAddress}`); } - await bulkRetrieveLogs( + await this.bulkRetrieveLogs(contractAddress, logRetrievalRequestsArrayBaseSlot, logRetrievalResponsesArrayBaseSlot); + } + + protected async bulkRetrieveLogs( + contractAddress: AztecAddress, + logRetrievalRequestsArrayBaseSlot: Fr, + logRetrievalResponsesArrayBaseSlot: Fr, + ) { + // We read all log retrieval requests and process them all concurrently. This makes the process much faster as we + // don't need to wait for the network round-trip. + const logRetrievalRequests = ( + await this.capsuleDataProvider.readCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot) + ).map(LogRetrievalRequest.fromFields); + + const maybeLogRetrievalResponses = await Promise.all( + logRetrievalRequests.map(async request => { + // TODO(#14555): remove these internal functions and have node endpoints that do this instead + const [publicLog, privateLog] = await Promise.all([ + getPublicLogByTag(request.unsiloedTag, request.contractAddress, this.aztecNode), + getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag), this.aztecNode), + ]); + + if (publicLog !== null) { + if (privateLog !== null) { + throw new Error( + `Found both a public and private log when searching for tag ${request.unsiloedTag} from contract ${request.contractAddress}`, + ); + } + + return new LogRetrievalResponse( + publicLog.logPayload, + publicLog.txHash, + publicLog.uniqueNoteHashesInTx, + publicLog.firstNullifierInTx, + ); + } else if (privateLog !== null) { + return new LogRetrievalResponse( + privateLog.logPayload, + privateLog.txHash, + privateLog.uniqueNoteHashesInTx, + privateLog.firstNullifierInTx, + ); + } else { + return null; + } + }), + ); + + // Requests are cleared once we're done. + await this.capsuleDataProvider.setCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot, []); + + // The responses are stored as Option in a second CapsuleArray. + await this.capsuleDataProvider.setCapsuleArray( contractAddress, - logRetrievalRequestsArrayBaseSlot, logRetrievalResponsesArrayBaseSlot, - this.capsuleDataProvider, - this.aztecNode, + maybeLogRetrievalResponses.map(LogRetrievalResponse.toSerializedOption), ); } From 5b7d6cd6c2c8b0e1975ebd6d34c28ad6e7ecfdd8 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 17:57:57 +0000 Subject: [PATCH 042/140] common => utility_execution_oracle 8/ --- .../oracle/common.test.ts | 87 +------------------ .../oracle/common.ts | 50 +---------- .../oracle/utility_execution.test.ts | 7 +- .../oracle/utility_execution_oracle.ts | 51 ++++++++++- 4 files changed, 56 insertions(+), 139 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index 81396c9423ea..1ade16c1f5fb 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -8,7 +8,7 @@ import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; +import { TxScopedL2Log } from '@aztec/stdlib/logs'; import { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import { @@ -21,7 +21,6 @@ import { } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; -import { randomInt } from 'crypto'; import { type MockProxy, mock } from 'jest-mock-extended'; import { @@ -32,7 +31,7 @@ import { NoteDataProvider, PrivateEventDataProvider, } from '../../storage/index.js'; -import { deliverEvent, deliverNote, getPrivateLogByTag, getPublicLogByTag, syncNoteNullifiers } from './common.js'; +import { deliverEvent, deliverNote, getPrivateLogByTag, syncNoteNullifiers } from './common.js'; jest.setTimeout(30_000); @@ -75,88 +74,6 @@ describe('Common oracle functions', () => { contractAddress = await AztecAddress.random(); }); - describe('getPublicLogByTag', () => { - const tag = Fr.random(); - - beforeEach(() => { - aztecNode.getLogsByTags.mockReset(); - aztecNode.getTxEffect.mockReset(); - }); - - it('returns null if no logs found for tag', async () => { - aztecNode.getLogsByTags.mockResolvedValue([[]]); - - const result = await getPublicLogByTag(tag, contractAddress, aztecNode); - expect(result).toBeNull(); - }); - - it('returns log data when single log found', async () => { - const scopedLog = await TxScopedL2Log.random(true); - const logContractAddress = (scopedLog.log as PublicLog).contractAddress; - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => - txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), - ); - - const result = (await getPublicLogByTag(tag, logContractAddress, aztecNode))!; - - expect(result.logPayload).toEqual(scopedLog.log.getEmittedFieldsWithoutTag()); - expect(result.uniqueNoteHashesInTx).toEqual(indexedTxEffect.data.noteHashes); - expect(result.txHash).toEqual(scopedLog.txHash); - expect(result.firstNullifierInTx).toEqual(indexedTxEffect.data.nullifiers[0]); - - expect(aztecNode.getLogsByTags).toHaveBeenCalledWith([tag]); - expect(aztecNode.getTxEffect).toHaveBeenCalledWith(scopedLog.txHash); - }); - - it('throws if multiple logs found for tag', async () => { - const scopedLog = await TxScopedL2Log.random(true); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog, scopedLog]]); - const logContractAddress = (scopedLog.log as PublicLog).contractAddress; - - await expect(getPublicLogByTag(tag, logContractAddress, aztecNode)).rejects.toThrow(/Got 2 logs for tag/); - }); - - it('throws if tx effect not found', async () => { - const scopedLog = await TxScopedL2Log.random(true); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - aztecNode.getTxEffect.mockResolvedValue(undefined); - const logContractAddress = (scopedLog.log as PublicLog).contractAddress; - - await expect(getPublicLogByTag(tag, logContractAddress, aztecNode)).rejects.toThrow( - /failed to retrieve tx effects/, - ); - }); - - it('returns log fields that are actually emitted', async () => { - const logContractAddress = await AztecAddress.random(); - const logPlaintext = [Fr.random()]; - const logContent = [tag, ...logPlaintext]; - - const log = PublicLog.from({ - contractAddress: logContractAddress, - fields: logContent, - }); - const scopedLogWithPadding = new TxScopedL2Log( - TxHash.random(), - randomInt(100), - randomInt(100), - BlockNumber(randomInt(100)), - L2BlockHash.random(), - log, - ); - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLogWithPadding]]); - aztecNode.getTxEffect.mockResolvedValue(await randomIndexedTxEffect()); - - const result = await getPublicLogByTag(tag, logContractAddress, aztecNode); - - expect(result?.logPayload).toEqual(logPlaintext); - }); - }); - describe('getPrivateLogByTag', () => { let tag: Fr; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index f08694b1f9f1..d4b505eabaf4 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -4,7 +4,7 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; -import { PrivateLogWithTxData, PublicLog, PublicLogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; +import { PrivateLogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; @@ -45,42 +45,6 @@ export function assertCompatibleOracleVersion(version: number): void { } } -// TODO(#14555): delete this function and implement this behavior in the node instead -export async function getPublicLogByTag( - tag: Fr, - contractAddress: AztecAddress, - aztecNode: AztecNode, -): Promise { - const logs = await internalGetPublicLogsByTagsFromContract([tag], contractAddress, aztecNode); - const logsForTag = logs[0]; - - if (logsForTag.length == 0) { - return null; - } else if (logsForTag.length > 1) { - // TODO(#11627): handle this case - throw new Error( - `Got ${logsForTag.length} logs for tag ${tag} and contract ${contractAddress.toString()}. getPublicLogByTag currently only supports a single log per tag`, - ); - } - - const scopedLog = logsForTag[0]; - - // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so - // we need to make a second call to the node for `getTxEffect`. - // TODO(#9789): bundle this information in the `getLogsByTag` call. - const txEffect = await aztecNode.getTxEffect(scopedLog.txHash); - if (txEffect == undefined) { - throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); - } - - return new PublicLogWithTxData( - scopedLog.log.getEmittedFieldsWithoutTag(), - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - ); -} - // TODO(#14555): delete this function and implement this behavior in the node instead export async function getPrivateLogByTag(siloedTag: Fr, aztecNode: AztecNode): Promise { const logs = await internalGetPrivateLogsByTags([siloedTag], aztecNode); @@ -120,18 +84,6 @@ async function internalGetPrivateLogsByTags(tags: Fr[], aztecNode: AztecNode): P return allLogs.map(logs => logs.filter(log => !log.isFromPublic)); } -// TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This -// was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. -async function internalGetPublicLogsByTagsFromContract( - tags: Fr[], - contractAddress: AztecAddress, - aztecNode: AztecNode, -): Promise { - const allLogs = await aztecNode.getLogsByTags(tags); - const allPublicLogs = allLogs.map(logs => logs.filter(log => log.isFromPublic)); - return allPublicLogs.map(logs => logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress))); -} - export async function getNullifierIndex(nullifier: Fr, aztecNode: AztecNode) { return await findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier, aztecNode); } 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 b26052dae02d..529fe9c3f331 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 @@ -74,11 +74,12 @@ describe('Utility Execution test suite', () => { recipientTaggingDataProvider.getLastUsedIndexes.mockImplementation(secrets => Promise.resolve(secrets.map(() => undefined)), ); - capsuleDataProvider.setCapsuleArray.mockImplementation(async (address, slot, content) => { + capsuleDataProvider.setCapsuleArray.mockImplementation((address, slot, content) => { capsuleArrays.set(`${address.toString()}:${slot.toString()}`, content); + return Promise.resolve(); }); - capsuleDataProvider.readCapsuleArray.mockImplementation(async (address, slot) => { - return capsuleArrays.get(`${address.toString()}:${slot.toString()}`) ?? []; + capsuleDataProvider.readCapsuleArray.mockImplementation((address, slot) => { + return Promise.resolve(capsuleArrays.get(`${address.toString()}:${slot.toString()}`) ?? []); }); acirSimulator = new ContractFunctionSimulator( contractDataProvider, 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 4790c42e57cb..ecb750496a1c 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 @@ -16,6 +16,8 @@ import { computeAddressSecret } from '@aztec/stdlib/keys'; import { DirectionalAppTaggingSecret, PendingTaggedLog, + PublicLog, + PublicLogWithTxData, TxScopedL2Log, deriveEcdhSharedSecret, } from '@aztec/stdlib/logs'; @@ -43,7 +45,6 @@ import { assertCompatibleOracleVersion, getNullifierIndex, getPrivateLogByTag, - getPublicLogByTag, syncNoteNullifiers, validateEnqueuedNotesAndEvents, } from './common.js'; @@ -504,7 +505,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra logRetrievalRequests.map(async request => { // TODO(#14555): remove these internal functions and have node endpoints that do this instead const [publicLog, privateLog] = await Promise.all([ - getPublicLogByTag(request.unsiloedTag, request.contractAddress, this.aztecNode), + this.getPublicLogByTag(request.unsiloedTag, request.contractAddress), getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag), this.aztecNode), ]); @@ -833,4 +834,50 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra const allLogs = await this.aztecNode.getLogsByTags(tags); return allLogs.map(logs => logs.filter(log => !log.isFromPublic)); } + + // TODO(#14555): delete this function and implement this behavior in the node instead + protected async getPublicLogByTag(tag: Fr, contractAddress: AztecAddress): Promise { + const logs = await this.internalGetPublicLogsByTagsFromContract([tag], contractAddress, this.aztecNode); + const logsForTag = logs[0]; + + if (logsForTag.length == 0) { + return null; + } else if (logsForTag.length > 1) { + // TODO(#11627): handle this case + throw new Error( + `Got ${logsForTag.length} logs for tag ${tag} and contract ${contractAddress.toString()}. getPublicLogByTag currently only supports a single log per tag`, + ); + } + + const scopedLog = logsForTag[0]; + + // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so + // we need to make a second call to the node for `getTxEffect`. + // TODO(#9789): bundle this information in the `getLogsByTag` call. + const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); + if (txEffect == undefined) { + throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); + } + + return new PublicLogWithTxData( + scopedLog.log.getEmittedFieldsWithoutTag(), + scopedLog.txHash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + ); + } + + // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This + // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. + protected async internalGetPublicLogsByTagsFromContract( + tags: Fr[], + contractAddress: AztecAddress, + aztecNode: AztecNode, + ): Promise { + const allLogs = await aztecNode.getLogsByTags(tags); + const allPublicLogs = allLogs.map(logs => logs.filter(log => log.isFromPublic)); + return allPublicLogs.map(logs => + logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress)), + ); + } } From 78c89668a47c676d1907946926d42542186285a3 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Thu, 18 Dec 2025 18:05:39 +0000 Subject: [PATCH 043/140] common => utility_execution_oracle 9/ --- .../oracle/common.test.ts | 56 +------------------ .../oracle/common.ts | 40 ------------- .../oracle/utility_execution_oracle.ts | 36 +++++++++++- 3 files changed, 36 insertions(+), 96 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index 1ade16c1f5fb..9778a2142297 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -8,17 +8,9 @@ import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { TxScopedL2Log } from '@aztec/stdlib/logs'; import { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; -import { - BlockHeader, - GlobalVariables, - type IndexedTxEffect, - TxEffect, - TxHash, - randomIndexedTxEffect, -} from '@aztec/stdlib/tx'; +import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect, TxHash } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -31,7 +23,7 @@ import { NoteDataProvider, PrivateEventDataProvider, } from '../../storage/index.js'; -import { deliverEvent, deliverNote, getPrivateLogByTag, syncNoteNullifiers } from './common.js'; +import { deliverEvent, deliverNote, syncNoteNullifiers } from './common.js'; jest.setTimeout(30_000); @@ -74,50 +66,6 @@ describe('Common oracle functions', () => { contractAddress = await AztecAddress.random(); }); - describe('getPrivateLogByTag', () => { - let tag: Fr; - - beforeEach(() => { - tag = Fr.random(); - }); - - it('returns null if no logs found', async () => { - aztecNode.getLogsByTags.mockResolvedValue([[]]); - const result = await getPrivateLogByTag(tag, aztecNode); - expect(result).toBeNull(); - }); - - it('returns log and tx effect if single log found', async () => { - const scopedLog = await TxScopedL2Log.random(false); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - aztecNode.getTxEffect.mockResolvedValue(indexedTxEffect); - - const result = await getPrivateLogByTag(tag, aztecNode); - - expect(result?.logPayload).toEqual(scopedLog.log.getEmittedFieldsWithoutTag()); - expect(result?.uniqueNoteHashesInTx).toEqual(indexedTxEffect.data.noteHashes); - expect(result?.txHash).toEqual(scopedLog.txHash); - expect(result?.firstNullifierInTx).toEqual(indexedTxEffect.data.nullifiers[0]); - expect(aztecNode.getTxEffect).toHaveBeenCalledWith(scopedLog.txHash); - }); - - it('throws if multiple logs found for tag', async () => { - const scopedLog = await TxScopedL2Log.random(false); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog, scopedLog]]); - - await expect(getPrivateLogByTag(tag, aztecNode)).rejects.toThrow(/Got 2 logs for tag/); - }); - - it('throws if tx effect not found', async () => { - const scopedLog = await TxScopedL2Log.random(false); - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - aztecNode.getTxEffect.mockResolvedValue(undefined); - - await expect(getPrivateLogByTag(tag, aztecNode)).rejects.toThrow(/failed to retrieve tx effects/); - }); - }); - describe('deliverEvent', () => { let blockNumber: BlockNumber; let eventSelector: EventSelector; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index d4b505eabaf4..226135d5dfa3 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -4,7 +4,6 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, DataInBlock } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; -import { PrivateLogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; @@ -45,45 +44,6 @@ export function assertCompatibleOracleVersion(version: number): void { } } -// TODO(#14555): delete this function and implement this behavior in the node instead -export async function getPrivateLogByTag(siloedTag: Fr, aztecNode: AztecNode): Promise { - const logs = await internalGetPrivateLogsByTags([siloedTag], aztecNode); - const logsForTag = logs[0]; - - if (logsForTag.length == 0) { - return null; - } else if (logsForTag.length > 1) { - // TODO(#11627): handle this case - throw new Error( - `Got ${logsForTag.length} logs for tag ${siloedTag}. getPrivateLogByTag currently only supports a single log per tag`, - ); - } - - const scopedLog = logsForTag[0]; - - // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so - // we need to make a second call to the node for `getTxEffect`. - // TODO(#9789): bundle this information in the `getLogsByTag` call. - const txEffect = await aztecNode.getTxEffect(scopedLog.txHash); - if (txEffect == undefined) { - throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); - } - - return new PrivateLogWithTxData( - scopedLog.log.getEmittedFieldsWithoutTag(), - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - ); -} - -// TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This -// was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. -async function internalGetPrivateLogsByTags(tags: Fr[], aztecNode: AztecNode): Promise { - const allLogs = await aztecNode.getLogsByTags(tags); - return allLogs.map(logs => logs.filter(log => !log.isFromPublic)); -} - export async function getNullifierIndex(nullifier: Fr, aztecNode: AztecNode) { return await findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier, aztecNode); } 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 ecb750496a1c..45493ceaad51 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 @@ -16,6 +16,7 @@ import { computeAddressSecret } from '@aztec/stdlib/keys'; import { DirectionalAppTaggingSecret, PendingTaggedLog, + PrivateLogWithTxData, PublicLog, PublicLogWithTxData, TxScopedL2Log, @@ -44,7 +45,6 @@ import { pickNotes } from '../pick_notes.js'; import { assertCompatibleOracleVersion, getNullifierIndex, - getPrivateLogByTag, syncNoteNullifiers, validateEnqueuedNotesAndEvents, } from './common.js'; @@ -506,7 +506,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra // TODO(#14555): remove these internal functions and have node endpoints that do this instead const [publicLog, privateLog] = await Promise.all([ this.getPublicLogByTag(request.unsiloedTag, request.contractAddress), - getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag), this.aztecNode), + this.getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag)), ]); if (publicLog !== null) { @@ -880,4 +880,36 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress)), ); } + + // TODO(#14555): delete this function and implement this behavior in the node instead + protected async getPrivateLogByTag(siloedTag: Fr): Promise { + const logs = await this.internalGetPrivateLogsByTags([siloedTag]); + const logsForTag = logs[0]; + + if (logsForTag.length == 0) { + return null; + } else if (logsForTag.length > 1) { + // TODO(#11627): handle this case + throw new Error( + `Got ${logsForTag.length} logs for tag ${siloedTag}. getPrivateLogByTag currently only supports a single log per tag`, + ); + } + + const scopedLog = logsForTag[0]; + + // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so + // we need to make a second call to the node for `getTxEffect`. + // TODO(#9789): bundle this information in the `getLogsByTag` call. + const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); + if (txEffect == undefined) { + throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); + } + + return new PrivateLogWithTxData( + scopedLog.log.getEmittedFieldsWithoutTag(), + scopedLog.txHash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + ); + } } From 750862beaf241cef3a9ce07eeb16da5290104ccf Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 08:44:11 +0000 Subject: [PATCH 044/140] common => utility_execution_oracle 10/ --- .../oracle/common.ts | 16 +------------- .../oracle/utility_execution_oracle.ts | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 226135d5dfa3..db5b535e482d 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,7 +1,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter, DataInBlock } from '@aztec/stdlib/block'; +import type { DataInBlock } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; import { Note, NoteDao } from '@aztec/stdlib/note'; @@ -44,20 +44,6 @@ export function assertCompatibleOracleVersion(version: number): void { } } -export async function getNullifierIndex(nullifier: Fr, aztecNode: AztecNode) { - return await findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier, aztecNode); -} - -async function findLeafIndex( - blockNumber: BlockParameter, - treeId: MerkleTreeId, - leafValue: Fr, - aztecNode: AztecNode, -): Promise { - const [leafIndex] = await aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]); - return leafIndex?.data; -} - export async function validateEnqueuedNotesAndEvents( contractAddress: AztecAddress, noteValidationRequestsArrayBaseSlot: Fr, 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 45493ceaad51..ea453a0dda2a 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 @@ -42,12 +42,7 @@ import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; -import { - assertCompatibleOracleVersion, - getNullifierIndex, - syncNoteNullifiers, - validateEnqueuedNotesAndEvents, -} from './common.js'; +import { assertCompatibleOracleVersion, syncNoteNullifiers, validateEnqueuedNotesAndEvents } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; @@ -375,10 +370,23 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra */ public async utilityCheckNullifierExists(innerNullifier: Fr) { const nullifier = await siloNullifier(this.contractAddress, innerNullifier!); - const index = await getNullifierIndex(nullifier, this.aztecNode); + const index = await this.getNullifierIndex(nullifier); return index !== undefined; } + protected async getNullifierIndex(nullifier: Fr) { + return await this.findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier); + } + + protected async findLeafIndex( + blockNumber: BlockParameter, + treeId: MerkleTreeId, + leafValue: Fr, + ): Promise { + const [leafIndex] = await this.aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]); + return leafIndex?.data; + } + /** * Fetches a message from the executionDataProvider, given its key. * @param contractAddress - Address of a contract by which the message was emitted. From 57362d2a004a7edd34855add55b0d5fa42d9b22e Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 10:15:57 +0000 Subject: [PATCH 045/140] introduce NoteSynchronizer --- .../oracle/common.test.ts | 127 +------------- .../oracle/common.ts | 58 +------ .../oracle/utility_execution.test.ts | 3 +- .../oracle/utility_execution_oracle.ts | 6 +- .../src/entrypoints/client/bundle/index.ts | 1 + .../pxe/src/entrypoints/client/lazy/index.ts | 1 + .../pxe/src/entrypoints/server/index.ts | 1 + yarn-project/pxe/src/notes/index.ts | 1 + .../pxe/src/notes/note_synchronizer.test.ts | 160 ++++++++++++++++++ .../pxe/src/notes/note_synchronizer.ts | 67 ++++++++ yarn-project/txe/src/txe_session.ts | 16 +- 11 files changed, 247 insertions(+), 194 deletions(-) create mode 100644 yarn-project/pxe/src/notes/index.ts create mode 100644 yarn-project/pxe/src/notes/note_synchronizer.test.ts create mode 100644 yarn-project/pxe/src/notes/note_synchronizer.ts diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index 9778a2142297..db207f0f1543 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -4,11 +4,10 @@ import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; +import { L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect, TxHash } from '@aztec/stdlib/tx'; @@ -19,11 +18,10 @@ import { AddressDataProvider, AnchorBlockDataProvider, ContractDataProvider, - NoteDao, NoteDataProvider, PrivateEventDataProvider, } from '../../storage/index.js'; -import { deliverEvent, deliverNote, syncNoteNullifiers } from './common.js'; +import { deliverEvent, deliverNote } from './common.js'; jest.setTimeout(30_000); @@ -382,127 +380,6 @@ describe('Common oracle functions', () => { }); }); - describe('syncNoteNullifiers', () => { - let recipient: AztecAddress; - - beforeEach(async () => { - // Check that there are no notes in the database - const notes = await noteDataProvider.getNotes({ contractAddress }); - expect(notes).toHaveLength(0); - - // Check that the expected number of accounts is present - const accounts = await keyStore.getAccounts(); - expect(accounts).toHaveLength(1); - - recipient = accounts[0]; - }); - - it('should remove notes that have been nullified', async () => { - // Set up initial state with a note - const noteDao = await NoteDao.random({ contractAddress }); - - // Spy on the noteDataProvider.applyNullifiers to later on have additional guarantee that we really removed - // the note. - jest.spyOn(noteDataProvider, 'applyNullifiers'); - - // Add the note to storage - await noteDataProvider.addNotes([noteDao], recipient); - - // Set up the nullifier in the merkle tree - const nullifierIndex = randomDataInBlock(123n); - aztecNode.findLeavesIndexes.mockResolvedValue([nullifierIndex]); - - // Call the function under test - await syncNoteNullifiers(contractAddress, anchorBlockDataProvider, noteDataProvider, aztecNode); - - // Verify the note was removed by checking storage - const remainingNotes = await noteDataProvider.getNotes({ - contractAddress, - status: NoteStatus.ACTIVE, - scopes: [recipient], - }); - expect(remainingNotes).toHaveLength(0); - - // Verify the note was removed by checking the spy - expect(noteDataProvider.applyNullifiers).toHaveBeenCalledTimes(1); - }); - - it('should keep notes that have not been nullified', async () => { - // Set up initial state with a note - const noteDao = await NoteDao.random({ contractAddress }); - - // Add the note to storage - await noteDataProvider.addNotes([noteDao], recipient); - - // No nullifier found in merkle tree - aztecNode.findLeavesIndexes.mockResolvedValue([undefined]); - - // Call the function under test - await syncNoteNullifiers(contractAddress, anchorBlockDataProvider, noteDataProvider, aztecNode); - - // Verify note still exists - const remainingNotes = await noteDataProvider.getNotes({ - contractAddress, - status: NoteStatus.ACTIVE, - scopes: [recipient], - }); - expect(remainingNotes).toHaveLength(1); - expect(remainingNotes[0]).toEqual(noteDao); - }); - - // Verifies that notes are not marked as nullified when their nullifier only exists in blocks that haven't been - // synced yet. We mock the nullifier to only exist in blocks beyond our current sync point, then verify the note - // is not removed by applyNullifiers. - it('should not remove notes if nullifier is in unsynced blocks', async () => { - // Set up initial state with a note - const noteDao = await NoteDao.random({ contractAddress }); - const syncedBlockNumber = 100; - await setSyncedBlockNumber(BlockNumber(syncedBlockNumber)); - - // Add the note to storage - await noteDataProvider.addNotes([noteDao], recipient); - - // Mock nullifier to only exist after synced block - aztecNode.findLeavesIndexes.mockImplementation(blockNum => { - if (typeof blockNum === 'number' && blockNum > syncedBlockNumber) { - return Promise.resolve([randomDataInBlock(0n)]); - } - return Promise.resolve([undefined]); - }); - - // Call the function under test - await syncNoteNullifiers(contractAddress, anchorBlockDataProvider, noteDataProvider, aztecNode); - // Verify note still exists - const remainingNotes = await noteDataProvider.getNotes({ - contractAddress, - status: NoteStatus.ACTIVE, - scopes: [recipient], - }); - expect(remainingNotes).toHaveLength(1); - expect(remainingNotes[0]).toEqual(noteDao); - }); - - it('should search for notes from all accounts', async () => { - // Add multiple accounts to keystore - await keyStore.addAccount(Fr.random(), Fr.random()); - await keyStore.addAccount(Fr.random(), Fr.random()); - - expect(await keyStore.getAccounts()).toHaveLength(3); - - // Spy on the noteDataProvider.getNotesSpy - const getNotesSpy = jest.spyOn(noteDataProvider, 'getNotes'); - - // Call the function under test - await syncNoteNullifiers(contractAddress, anchorBlockDataProvider, noteDataProvider, aztecNode); - - // Verify applyNullifiers was called once for all accounts - expect(getNotesSpy).toHaveBeenCalledTimes(1); - - // Verify getNotes was called with the correct contract address - expect(getNotesSpy).toHaveBeenCalledWith(expect.objectContaining({ contractAddress })); - }); - }); - const setSyncedBlockNumber = (blockNumber: BlockNumber) => { return anchorBlockDataProvider.setHeader( BlockHeader.empty({ diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index db5b535e482d..7a103643301c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,9 +1,8 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { DataInBlock } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; -import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; @@ -257,58 +256,3 @@ export async function deliverEvent( }, ); } - -/** - * Looks for nullifiers of active contract notes and marks them as nullified if a nullifier is found. - * - * Fetches notes from the NoteDataProvider and checks which nullifiers are present in the - * onchain nullifier Merkle tree - up to the latest locally synced block. We use the - * locally synced block instead of querying the chain's 'latest' block to ensure correctness: - * notes are only marked nullified once their corresponding nullifier has been included in a - * block up to which the PXE has synced. - * This allows recent nullifications to be processed even if the node is not an archive node. - * - * @param contractAddress - The contract whose notes should be checked and nullified. - */ -export async function syncNoteNullifiers( - contractAddress: AztecAddress, - anchorBlockDataProvider: AnchorBlockDataProvider, - noteDataProvider: NoteDataProvider, - aztecNode: AztecNode, -) { - const syncedBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - - const contractNotes = await noteDataProvider.getNotes({ contractAddress }); - - if (contractNotes.length === 0) { - return; - } - - const nullifiersToCheck = contractNotes.map(note => note.siloedNullifier); - const nullifierBatches = nullifiersToCheck.reduce( - (acc, nullifier) => { - if (acc[acc.length - 1].length < MAX_RPC_LEN) { - acc[acc.length - 1].push(nullifier); - } else { - acc.push([nullifier]); - } - return acc; - }, - [[]] as Fr[][], - ); - const nullifierIndexes = ( - await Promise.all( - nullifierBatches.map(batch => aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, batch)), - ) - ).flat(); - - const foundNullifiers = nullifiersToCheck - .map((nullifier, i) => { - if (nullifierIndexes[i] !== undefined) { - return { ...nullifierIndexes[i], ...{ data: nullifier } } as DataInBlock; - } - }) - .filter(nullifier => nullifier !== undefined) as DataInBlock[]; - - await noteDataProvider.applyNullifiers(foundNullifiers); -} 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 529fe9c3f331..931319211e06 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 @@ -45,6 +45,7 @@ describe('Utility Execution test suite', () => { let privateEventDataProvider: ReturnType>; let acirSimulator: ContractFunctionSimulator; let owner: AztecAddress; + let ownerCompleteAddress: CompleteAddress; let anchorBlockHeader: BlockHeader; const ownerSecretKey = Fr.fromHexString('2dcc5485a58316776299be08c78fa3788a1a7961ae30dc747fb1be17692a8d32'); @@ -95,7 +96,7 @@ describe('Utility Execution test suite', () => { simulator, ); - const ownerCompleteAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(ownerSecretKey, Fr.random()); + ownerCompleteAddress = await CompleteAddress.fromSecretKeyAndPartialAddress(ownerSecretKey, Fr.random()); owner = ownerCompleteAddress.address; keyStore.getAccounts.mockResolvedValue([owner]); 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 ea453a0dda2a..9adb2f60dfcf 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 @@ -27,6 +27,7 @@ import type { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; +import { NoteSynchronizer } from '../../notes/note_synchronizer.js'; import type { AddressDataProvider, AnchorBlockDataProvider, @@ -42,7 +43,7 @@ import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; -import { assertCompatibleOracleVersion, syncNoteNullifiers, validateEnqueuedNotesAndEvents } from './common.js'; +import { assertCompatibleOracleVersion, validateEnqueuedNotesAndEvents } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; @@ -460,7 +461,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra public async utilityFetchTaggedLogs(pendingTaggedLogArrayBaseSlot: Fr) { await this.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes); - await syncNoteNullifiers(this.contractAddress, this.anchorBlockDataProvider, this.noteDataProvider, this.aztecNode); + const noteSynchronizer = new NoteSynchronizer(this.noteDataProvider, this.aztecNode, this.anchorBlockDataProvider); + await noteSynchronizer.syncNoteNullifiers(this.contractAddress); } public async utilityValidateEnqueuedNotesAndEvents( diff --git a/yarn-project/pxe/src/entrypoints/client/bundle/index.ts b/yarn-project/pxe/src/entrypoints/client/bundle/index.ts index 418b4f0eba11..957ac7b742db 100644 --- a/yarn-project/pxe/src/entrypoints/client/bundle/index.ts +++ b/yarn-project/pxe/src/entrypoints/client/bundle/index.ts @@ -4,4 +4,5 @@ export * from '../../../error_enriching.js'; export * from '../../../storage/index.js'; export * from './utils.js'; export * from '../../../contract_function_simulator/oracle/common.js'; +export * from '../../../notes/index.js'; export type { PXECreationOptions } from '../../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/entrypoints/client/lazy/index.ts b/yarn-project/pxe/src/entrypoints/client/lazy/index.ts index 90daeea05941..bc9b7b2ddc82 100644 --- a/yarn-project/pxe/src/entrypoints/client/lazy/index.ts +++ b/yarn-project/pxe/src/entrypoints/client/lazy/index.ts @@ -4,4 +4,5 @@ export * from '../../../storage/index.js'; export * from '../../../error_enriching.js'; export * from './utils.js'; export * from '../../../contract_function_simulator/oracle/common.js'; +export * from '../../../notes/index.js'; export { type PXECreationOptions } from '../../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/entrypoints/server/index.ts b/yarn-project/pxe/src/entrypoints/server/index.ts index c0c687e17023..a7094cfb0969 100644 --- a/yarn-project/pxe/src/entrypoints/server/index.ts +++ b/yarn-project/pxe/src/entrypoints/server/index.ts @@ -4,5 +4,6 @@ export * from '../../error_enriching.js'; export * from '../../storage/index.js'; export * from './utils.js'; export * from '../../contract_function_simulator/oracle/common.js'; +export * from '../../notes/index.js'; export { ORACLE_VERSION } from '../../oracle_version.js'; export { type PXECreationOptions } from '../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/notes/index.ts b/yarn-project/pxe/src/notes/index.ts new file mode 100644 index 000000000000..b7ee1d2e3e0b --- /dev/null +++ b/yarn-project/pxe/src/notes/index.ts @@ -0,0 +1 @@ +export { NoteSynchronizer } from './note_synchronizer.js'; diff --git a/yarn-project/pxe/src/notes/note_synchronizer.test.ts b/yarn-project/pxe/src/notes/note_synchronizer.test.ts new file mode 100644 index 000000000000..868ea94517d8 --- /dev/null +++ b/yarn-project/pxe/src/notes/note_synchronizer.test.ts @@ -0,0 +1,160 @@ +import { BlockNumber } from '@aztec/foundation/branded-types'; +import { Fr } from '@aztec/foundation/curves/bn254'; +import { KeyStore } from '@aztec/key-store'; +import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { randomDataInBlock } from '@aztec/stdlib/block'; +import type { CompleteAddress } from '@aztec/stdlib/contract'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; +import { NoteDao, NoteStatus } from '@aztec/stdlib/note'; +import { BlockHeader, GlobalVariables } from '@aztec/stdlib/tx'; + +import { jest } from '@jest/globals'; +import { mock } from 'jest-mock-extended'; + +import { AnchorBlockDataProvider, NoteDataProvider } from '../storage/index.js'; +import { NoteSynchronizer } from './note_synchronizer.js'; + +describe('NoteSynchronizer', () => { + let anchorBlockDataProvider: AnchorBlockDataProvider; + let noteDataProvider: NoteDataProvider; + let keyStore: KeyStore; + let aztecNode: ReturnType>; + const syncedBlockNumber = 42; + let recipient: CompleteAddress; + let contractAddress: AztecAddress; + + let noteSynchronizer: NoteSynchronizer; + + beforeEach(async () => { + const store = await openTmpStore('test'); + keyStore = new KeyStore(store); + noteDataProvider = await NoteDataProvider.create(store); + noteDataProvider = await NoteDataProvider.create(store); + aztecNode = mock(); + anchorBlockDataProvider = new AnchorBlockDataProvider(store); + await anchorBlockDataProvider.setHeader( + BlockHeader.empty({ + globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(syncedBlockNumber) }), + }), + ); + + contractAddress = await AztecAddress.random(); + + // Check that there are no notes in the database + const notes = await noteDataProvider.getNotes({ contractAddress }); + expect(notes).toHaveLength(0); + + // Check that the expected number of accounts is present + const accounts = await keyStore.getAccounts(); + expect(accounts).toHaveLength(0); + + recipient = await keyStore.addAccount(new Fr(69), Fr.random()); + + noteSynchronizer = new NoteSynchronizer(noteDataProvider, aztecNode, anchorBlockDataProvider); + }); + + it('should remove notes that have been nullified', async () => { + // Set up initial state with a note + const noteDao = await NoteDao.random({ contractAddress }); + + // Spy on the noteDataProvider.applyNullifiers to later on have additional guarantee that we really removed + // the note. + jest.spyOn(noteDataProvider, 'applyNullifiers'); + + // Add the note to storage + await noteDataProvider.addNotes([noteDao], recipient.address); + + // Set up the nullifier in the merkle tree + const nullifierIndex = randomDataInBlock(123n); + aztecNode.findLeavesIndexes.mockResolvedValue([nullifierIndex]); + + // Call the function under test + await noteSynchronizer.syncNoteNullifiers(contractAddress); + + // Verify the note was removed by checking storage + const remainingNotes = await noteDataProvider.getNotes({ + contractAddress, + status: NoteStatus.ACTIVE, + scopes: [recipient.address], + }); + expect(remainingNotes).toHaveLength(0); + + // Verify the note was removed by checking the spy + expect(noteDataProvider.applyNullifiers).toHaveBeenCalledTimes(1); + }); + + it('should keep notes that have not been nullified', async () => { + // Set up initial state with a note + const noteDao = await NoteDao.random({ contractAddress }); + + // Add the note to storage + await noteDataProvider.addNotes([noteDao], recipient.address); + + // No nullifier found in merkle tree + aztecNode.findLeavesIndexes.mockResolvedValue([undefined]); + + // Call the function under test + await noteSynchronizer.syncNoteNullifiers(contractAddress); + + // Verify note still exists + const remainingNotes = await noteDataProvider.getNotes({ + contractAddress, + status: NoteStatus.ACTIVE, + scopes: [recipient.address], + }); + expect(remainingNotes).toHaveLength(1); + expect(remainingNotes[0]).toEqual(noteDao); + }); + + // Verifies that notes are not marked as nullified when their nullifier only exists in blocks that haven't been + // synced yet. We mock the nullifier to only exist in blocks beyond our current sync point, then verify the note + // is not removed by applyNullifiers. + it('should not remove notes if nullifier is in unsynced blocks', async () => { + // Set up initial state with a note + const noteDao = await NoteDao.random({ contractAddress }); + + // Add the note to storage + await noteDataProvider.addNotes([noteDao], recipient.address); + + // Mock nullifier to only exist after synced block + aztecNode.findLeavesIndexes.mockImplementation(blockNum => { + if (typeof blockNum === 'number' && blockNum > syncedBlockNumber) { + return Promise.resolve([randomDataInBlock(0n)]); + } + return Promise.resolve([undefined]); + }); + + // Call the function under test + await noteSynchronizer.syncNoteNullifiers(contractAddress); + + // Verify note still exists + const remainingNotes = await noteDataProvider.getNotes({ + contractAddress, + status: NoteStatus.ACTIVE, + scopes: [recipient.address], + }); + expect(remainingNotes).toHaveLength(1); + expect(remainingNotes[0]).toEqual(noteDao); + }); + + it('should search for notes from all accounts', async () => { + // Add multiple accounts to keystore + await keyStore.addAccount(Fr.random(), Fr.random()); + await keyStore.addAccount(Fr.random(), Fr.random()); + + expect(await keyStore.getAccounts()).toHaveLength(3); + + // Spy on the noteDataProvider.getNotesSpy + const getNotesSpy = jest.spyOn(noteDataProvider, 'getNotes'); + + // Call the function under test + await noteSynchronizer.syncNoteNullifiers(contractAddress); + + // Verify applyNullifiers was called once for all accounts + expect(getNotesSpy).toHaveBeenCalledTimes(1); + + // Verify getNotes was called with the correct contract address + expect(getNotesSpy).toHaveBeenCalledWith(expect.objectContaining({ contractAddress })); + }); +}); diff --git a/yarn-project/pxe/src/notes/note_synchronizer.ts b/yarn-project/pxe/src/notes/note_synchronizer.ts new file mode 100644 index 000000000000..93650b5288fd --- /dev/null +++ b/yarn-project/pxe/src/notes/note_synchronizer.ts @@ -0,0 +1,67 @@ +import { Fr } from '@aztec/foundation/curves/bn254'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import type { DataInBlock } from '@aztec/stdlib/block'; +import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; +import { MerkleTreeId } from '@aztec/stdlib/trees'; + +import type { AnchorBlockDataProvider, NoteDataProvider } from '../storage/index.js'; + +export class NoteSynchronizer { + constructor( + private readonly noteDataProvider: NoteDataProvider, + private readonly aztecNode: AztecNode, + private readonly anchorBlockDataProvider: AnchorBlockDataProvider, + ) {} + + /** + * Looks for nullifiers of active contract notes and marks them as nullified if a nullifier is found. + * + * Fetches notes from the NoteDataProvider and checks which nullifiers are present in the + * onchain nullifier Merkle tree - up to the latest locally synced block. We use the + * locally synced block instead of querying the chain's 'latest' block to ensure correctness: + * notes are only marked nullified once their corresponding nullifier has been included in a + * block up to which the PXE has synced. + * This allows recent nullifications to be processed even if the node is not an archive node. + * + * @param contractAddress - The contract whose notes should be checked and nullified. + */ + public async syncNoteNullifiers(contractAddress: AztecAddress): Promise { + const syncedBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + + const contractNotes = await this.noteDataProvider.getNotes({ contractAddress }); + + if (contractNotes.length === 0) { + return; + } + + const nullifiersToCheck = contractNotes.map(note => note.siloedNullifier); + const nullifierBatches = nullifiersToCheck.reduce( + (acc, nullifier) => { + if (acc[acc.length - 1].length < MAX_RPC_LEN) { + acc[acc.length - 1].push(nullifier); + } else { + acc.push([nullifier]); + } + return acc; + }, + [[]] as Fr[][], + ); + const nullifierIndexes = ( + await Promise.all( + nullifierBatches.map(batch => + this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, batch), + ), + ) + ).flat(); + + const foundNullifiers = nullifiersToCheck + .map((nullifier, i) => { + if (nullifierIndexes[i] !== undefined) { + return { ...nullifierIndexes[i], ...{ data: nullifier } } as DataInBlock; + } + }) + .filter(nullifier => nullifier !== undefined) as DataInBlock[]; + + await this.noteDataProvider.applyNullifiers(foundNullifiers); + } +} diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index c1587fa93aad..55182bb50b84 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -8,10 +8,10 @@ import { AddressDataProvider, CapsuleDataProvider, NoteDataProvider, + NoteSynchronizer, PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, - syncNoteNullifiers, } from '@aztec/pxe/server'; import { ExecutionNoteCache, @@ -281,12 +281,11 @@ export class TXESession implements TXESessionStateHandler { // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not // be removed from the database. // TODO(#12553): make the synchronizer sync here instead and remove this - await syncNoteNullifiers( - contractAddress, - this.stateMachine.anchorBlockDataProvider, + await new NoteSynchronizer( this.noteDataProvider, this.stateMachine.node, - ); + this.stateMachine.anchorBlockDataProvider, + ).syncNoteNullifiers(contractAddress); // Private execution has two associated block numbers: the anchor block (i.e. the historical block that is used to // build the proof), and the *next* block, i.e. the one we'll create once the execution ends, and which will contain @@ -371,12 +370,11 @@ export class TXESession implements TXESessionStateHandler { // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not // be removed from the database. // TODO(#12553): make the synchronizer sync here instead and remove this - await syncNoteNullifiers( - contractAddress, - this.stateMachine.anchorBlockDataProvider, + await new NoteSynchronizer( this.noteDataProvider, this.stateMachine.node, - ); + this.stateMachine.anchorBlockDataProvider, + ).syncNoteNullifiers(contractAddress); const anchorBlockHeader = await this.stateMachine.anchorBlockDataProvider.getBlockHeader(); From 66c78366b1163abfbec3c310a401c8e8c46f2465 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 10:33:07 +0000 Subject: [PATCH 046/140] deliverNote --- .../oracle/common.test.ts | 214 +----------------- .../oracle/common.ts | 104 +-------- .../pxe/src/notes/note_synchronizer.test.ts | 203 ++++++++++++++++- .../pxe/src/notes/note_synchronizer.ts | 95 +++++++- 4 files changed, 303 insertions(+), 313 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts index db207f0f1543..e45d9d981c72 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts @@ -6,10 +6,9 @@ import { EventSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; -import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; +import { siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { MerkleTreeId } from '@aztec/stdlib/trees'; -import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect, TxHash } from '@aztec/stdlib/tx'; +import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -18,10 +17,9 @@ import { AddressDataProvider, AnchorBlockDataProvider, ContractDataProvider, - NoteDataProvider, PrivateEventDataProvider, } from '../../storage/index.js'; -import { deliverEvent, deliverNote } from './common.js'; +import { deliverEvent } from './common.js'; jest.setTimeout(30_000); @@ -33,7 +31,6 @@ describe('Common oracle functions', () => { let anchorBlockDataProvider: AnchorBlockDataProvider; let keyStore: KeyStore; let privateEventDataProvider: PrivateEventDataProvider; - let noteDataProvider: NoteDataProvider; let recipient: CompleteAddress; let contractAddress: AztecAddress; @@ -51,7 +48,6 @@ describe('Common oracle functions', () => { anchorBlockDataProvider = new AnchorBlockDataProvider(store); keyStore = new KeyStore(store); privateEventDataProvider = new PrivateEventDataProvider(store); - noteDataProvider = await NoteDataProvider.create(store); // Set up recipient account recipient = await keyStore.addAccount(new Fr(69), Fr.random()); @@ -176,210 +172,6 @@ describe('Common oracle functions', () => { }); }); - describe('deliverNote', () => { - // Recipient is different from the owner because recipient refers to the - // recipient of the message containing the note, while owner refers to the - // owner of the note. - let owner: AztecAddress; - let storageSlot: Fr; - let randomness: Fr; - let noteNonce: Fr; - let content: Fr[]; - - let noteHash: Fr; - let uniqueNoteHash: Fr; - let nullifier: Fr; - let siloedNullifier: Fr; - - let txHash: TxHash; - let txEffect: TxEffect; - let indexedTxEffect: IndexedTxEffect; - let blockNumber: BlockNumber; - - let nullified = false; - - // beforeEach sets up the happy path case, so error modes are tested - // by minimally failing happy path conditions - beforeEach(async () => { - noteHash = Fr.random(); - nullifier = Fr.random(); - txHash = TxHash.random(); - owner = await AztecAddress.random(); - storageSlot = Fr.random(); - randomness = Fr.random(); - noteNonce = Fr.random(); - content = [Fr.random(), Fr.random()]; - - uniqueNoteHash = await computeUniqueNoteHash(noteNonce, await siloNoteHash(contractAddress, noteHash)); - siloedNullifier = await siloNullifier(contractAddress, nullifier); - - blockNumber = BlockNumber(42); - - txEffect = TxEffect.from({ - ...(await TxEffect.random()), - noteHashes: [uniqueNoteHash], - }); - - indexedTxEffect = { - l2BlockNumber: blockNumber, - l2BlockHash: L2BlockHash.random(), - data: txEffect, - txIndexInBlock: 0, - }; - - /* Happy path context conditions: - ** - PXE is sync'd to _at least_ block including tx - ** - Node knows tx effect - ** - Node knows unique note hash (and siloed nullifier if requested) - */ - await setSyncedBlockNumber(blockNumber); - - aztecNode.getTxEffect.mockImplementation(queryTxHash => - Promise.resolve(queryTxHash == txHash ? indexedTxEffect : undefined), - ); - - aztecNode.findLeavesIndexes.mockImplementation((queryBlockNum, treeId, leaves) => { - if (queryBlockNum != blockNumber) { - throw new Error(`Got a tree query for block ${queryBlockNum} but synced block is ${blockNumber}`); - } - - if (treeId == MerkleTreeId.NOTE_HASH_TREE && leaves[0].equals(uniqueNoteHash)) { - return Promise.resolve([ - { - data: BigInt(0), - l2BlockNumber: indexedTxEffect.l2BlockNumber, - l2BlockHash: indexedTxEffect.l2BlockHash, - }, - ]); - } else if (treeId == MerkleTreeId.NULLIFIER_TREE && leaves[0].equals(siloedNullifier)) { - // Note that returning undefined (i.e. the un-nullified case) covers both scenarios where the note has not - // been nullified and where the nullifier is in a block past the synced block. - return Promise.resolve([ - nullified - ? { - data: BigInt(0), - l2BlockNumber: indexedTxEffect.l2BlockNumber, - l2BlockHash: indexedTxEffect.l2BlockHash, - } - : undefined, - ]); - } else { - throw new Error(); - } - }); - }); - - it('should store note if it exists in note hash tree and is not nullified', async () => { - await deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - noteHash, - nullifier, - txHash, - recipient.address, - anchorBlockDataProvider, - aztecNode, - noteDataProvider, - ); - - // Verify note was stored - const notes = await noteDataProvider.getNotes({ contractAddress, scopes: [recipient.address] }); - - expect(notes).toHaveLength(1); - expect(notes[0].noteHash.equals(noteHash)).toBe(true); - }); - - it('should throw if tx hash does not exist', async () => { - await expect( - deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - noteHash, - nullifier, - TxHash.random(), - recipient.address, - anchorBlockDataProvider, - aztecNode, - noteDataProvider, - ), - ).rejects.toThrow(/Could not find tx effect/); - }); - - it('should throw if note was not emitted in the tx', async () => { - await expect( - deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - Fr.random(), // note hash - nullifier, - txHash, - recipient.address, - anchorBlockDataProvider, - aztecNode, - noteDataProvider, - ), - ).rejects.toThrow(/is not present in tx/); - }); - - it('should throw if tx was mined after synced block number', async () => { - await setSyncedBlockNumber(BlockNumber(blockNumber - 1)); - - await expect( - deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - noteHash, - nullifier, - txHash, - recipient.address, - anchorBlockDataProvider, - aztecNode, - noteDataProvider, - ), - ).rejects.toThrow(/as of block number/); - }); - - it('should store and immediately remove note if it is already nullified', async () => { - nullified = true; - - await deliverNote( - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - content, - noteHash, - nullifier, - txHash, - recipient.address, - anchorBlockDataProvider, - aztecNode, - noteDataProvider, - ); - - // Verify note was removed - const notes = await noteDataProvider.getNotes({ contractAddress, scopes: [recipient.address] }); - expect(notes).toHaveLength(0); - }); - }); - const setSyncedBlockNumber = (blockNumber: BlockNumber) => { return anchorBlockDataProvider.setHeader( BlockHeader.empty({ diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 7a103643301c..f6753bb6752b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,12 +1,12 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; +import { siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { Note, NoteDao } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; +import { NoteSynchronizer } from '../../notes/note_synchronizer.js'; import { ORACLE_VERSION } from '../../oracle_version.js'; import { AnchorBlockDataProvider, @@ -63,8 +63,10 @@ export async function validateEnqueuedNotesAndEvents( await capsuleDataProvider.readCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot) ).map(EventValidationRequest.fromFields); + const noteSynchronizer = new NoteSynchronizer(noteDataProvider, aztecNode, anchorBlockDataProvider); + const noteDeliveries = noteValidationRequests.map(request => - deliverNote( + noteSynchronizer.deliverNote( request.contractAddress, request.owner, request.storageSlot, @@ -75,9 +77,6 @@ export async function validateEnqueuedNotesAndEvents( request.nullifier, request.txHash, request.recipient, - anchorBlockDataProvider, - aztecNode, - noteDataProvider, ), ); @@ -102,99 +101,6 @@ export async function validateEnqueuedNotesAndEvents( await capsuleDataProvider.setCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot, []); } -export async function deliverNote( - contractAddress: AztecAddress, - owner: AztecAddress, - storageSlot: Fr, - randomness: Fr, - noteNonce: Fr, - content: Fr[], - noteHash: Fr, - nullifier: Fr, - txHash: TxHash, - recipient: AztecAddress, - anchorBlockDataProvider: AnchorBlockDataProvider, - aztecNode: AztecNode, - noteDataProvider: NoteDataProvider, -): Promise { - // We are going to store the new note in the NoteDataProvider, which will let us later return it via `getNotes`. - // There's two things we need to check before we do this however: - // - we must make sure the note does actually exist in the note hash tree - // - we need to check if the note has already been nullified - // - // Failing to do either of the above would result in circuits getting either non-existent notes and failing to - // produce inclusion proofs for them, or getting nullified notes and producing duplicate nullifiers, both of which - // are catastrophic failure modes. - // - // Note that adding a note and removing it is *not* equivalent to never adding it in the first place. A nullifier - // emitted in a block that comes after note creation might result in the note being de-nullified by a chain reorg, - // so we must store both the note hash and nullifier block information. - - // We avoid making node queries at 'latest' since we don't want to process notes or nullifiers that only exist ahead - // in time of the locally synced state. - // Note that while this technically results in historical queries, we perform it at the latest locally synced block - // number which *should* be recent enough to be available, even for non-archive nodes. - // Also note that the note should never be ahead of the synced block here since `fetchTaggedLogs` only processes - // logs up to the synced block making this only an additional safety check. - const syncedBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - - // By computing siloed and unique note hashes ourselves we prevent contracts from interfering with the note storage - // of other contracts, which would constitute a security breach. - const uniqueNoteHash = await computeUniqueNoteHash(noteNonce, await siloNoteHash(contractAddress, noteHash)); - const siloedNullifier = await siloNullifier(contractAddress, nullifier); - - const txEffect = await aztecNode.getTxEffect(txHash); - if (!txEffect) { - throw new Error(`Could not find tx effect for tx hash ${txHash}`); - } - - if (txEffect.l2BlockNumber > syncedBlockNumber) { - throw new Error(`Could not find tx effect for tx hash ${txHash} as of block number ${syncedBlockNumber}`); - } - - const noteInTx = txEffect.data.noteHashes.some(nh => nh.equals(uniqueNoteHash)); - if (!noteInTx) { - throw new Error(`Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present in tx ${txHash}`); - } - - // We store notes by their index in the global note hash tree, which has the convenient side effect of validating - // note existence in said tree. We concurrently also check if the note's nullifier exists, performing all node - // queries in a single round-trip. - const [[uniqueNoteHashTreeIndexInBlock], [nullifierIndex]] = await Promise.all([ - aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash]), - aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, [siloedNullifier]), - ]); - - if (uniqueNoteHashTreeIndexInBlock === undefined) { - throw new Error( - `Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present on the tree at block ${syncedBlockNumber} (from tx ${txHash})`, - ); - } - - const noteDao = new NoteDao( - new Note(content), - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - noteHash, - siloedNullifier, - txHash, - uniqueNoteHashTreeIndexInBlock.l2BlockNumber, - uniqueNoteHashTreeIndexInBlock.l2BlockHash.toString(), - uniqueNoteHashTreeIndexInBlock.data, - ); - - // The note was found by `recipient`, so we use that as the scope when storing the note. - await noteDataProvider.addNotes([noteDao], recipient); - - if (nullifierIndex !== undefined) { - const { data: _, ...blockHashAndNum } = nullifierIndex; - await noteDataProvider.applyNullifiers([{ data: siloedNullifier, ...blockHashAndNum }]); - } -} - export async function deliverEvent( contractAddress: AztecAddress, selector: EventSelector, diff --git a/yarn-project/pxe/src/notes/note_synchronizer.test.ts b/yarn-project/pxe/src/notes/note_synchronizer.test.ts index 868ea94517d8..397c519a4581 100644 --- a/yarn-project/pxe/src/notes/note_synchronizer.test.ts +++ b/yarn-project/pxe/src/notes/note_synchronizer.test.ts @@ -3,11 +3,13 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { randomDataInBlock } from '@aztec/stdlib/block'; +import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; import type { CompleteAddress } from '@aztec/stdlib/contract'; +import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { NoteDao, NoteStatus } from '@aztec/stdlib/note'; -import { BlockHeader, GlobalVariables } from '@aztec/stdlib/tx'; +import { MerkleTreeId } from '@aztec/stdlib/trees'; +import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect, TxHash } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; import { mock } from 'jest-mock-extended'; @@ -157,4 +159,201 @@ describe('NoteSynchronizer', () => { // Verify getNotes was called with the correct contract address expect(getNotesSpy).toHaveBeenCalledWith(expect.objectContaining({ contractAddress })); }); + + describe('deliverNote', () => { + // Recipient is different from the owner because recipient refers to the + // recipient of the message containing the note, while owner refers to the + // owner of the note. + let owner: AztecAddress; + let storageSlot: Fr; + let randomness: Fr; + let noteNonce: Fr; + let content: Fr[]; + + let noteHash: Fr; + let uniqueNoteHash: Fr; + let nullifier: Fr; + let siloedNullifier: Fr; + + let txHash: TxHash; + let txEffect: TxEffect; + let indexedTxEffect: IndexedTxEffect; + let blockNumber: BlockNumber; + + let nullified = false; + + const setSyncedBlockNumber = (blockNumber: BlockNumber) => { + return anchorBlockDataProvider.setHeader( + BlockHeader.empty({ + globalVariables: GlobalVariables.empty({ blockNumber }), + }), + ); + }; + + // beforeEach sets up the happy path case, so error modes are tested + // by minimally failing happy path conditions + beforeEach(async () => { + noteHash = Fr.random(); + nullifier = Fr.random(); + txHash = TxHash.random(); + owner = await AztecAddress.random(); + storageSlot = Fr.random(); + randomness = Fr.random(); + noteNonce = Fr.random(); + content = [Fr.random(), Fr.random()]; + + uniqueNoteHash = await computeUniqueNoteHash(noteNonce, await siloNoteHash(contractAddress, noteHash)); + siloedNullifier = await siloNullifier(contractAddress, nullifier); + + blockNumber = BlockNumber(42); + + txEffect = TxEffect.from({ + ...(await TxEffect.random()), + noteHashes: [uniqueNoteHash], + }); + + indexedTxEffect = { + l2BlockNumber: blockNumber, + l2BlockHash: L2BlockHash.random(), + data: txEffect, + txIndexInBlock: 0, + }; + + /* Happy path context conditions: + ** - PXE is sync'd to _at least_ block including tx + ** - Node knows tx effect + ** - Node knows unique note hash (and siloed nullifier if requested) + */ + await setSyncedBlockNumber(blockNumber); + + aztecNode.getTxEffect.mockImplementation(queryTxHash => + Promise.resolve(queryTxHash == txHash ? indexedTxEffect : undefined), + ); + + aztecNode.findLeavesIndexes.mockImplementation((queryBlockNum, treeId, leaves) => { + if (queryBlockNum != blockNumber) { + throw new Error(`Got a tree query for block ${queryBlockNum} but synced block is ${blockNumber}`); + } + + if (treeId == MerkleTreeId.NOTE_HASH_TREE && leaves[0].equals(uniqueNoteHash)) { + return Promise.resolve([ + { + data: BigInt(0), + l2BlockNumber: indexedTxEffect.l2BlockNumber, + l2BlockHash: indexedTxEffect.l2BlockHash, + }, + ]); + } else if (treeId == MerkleTreeId.NULLIFIER_TREE && leaves[0].equals(siloedNullifier)) { + // Note that returning undefined (i.e. the un-nullified case) covers both scenarios where the note has not + // been nullified and where the nullifier is in a block past the synced block. + return Promise.resolve([ + nullified + ? { + data: BigInt(0), + l2BlockNumber: indexedTxEffect.l2BlockNumber, + l2BlockHash: indexedTxEffect.l2BlockHash, + } + : undefined, + ]); + } else { + throw new Error(); + } + }); + }); + + it('should store note if it exists in note hash tree and is not nullified', async () => { + await noteSynchronizer.deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + noteHash, + nullifier, + txHash, + recipient.address, + ); + + // Verify note was stored + const notes = await noteDataProvider.getNotes({ contractAddress, scopes: [recipient.address] }); + + expect(notes).toHaveLength(1); + expect(notes[0].noteHash.equals(noteHash)).toBe(true); + }); + + it('should throw if tx hash does not exist', async () => { + await expect( + noteSynchronizer.deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + noteHash, + nullifier, + TxHash.random(), + recipient.address, + ), + ).rejects.toThrow(/Could not find tx effect/); + }); + + it('should throw if note was not emitted in the tx', async () => { + await expect( + noteSynchronizer.deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + Fr.random(), // note hash + nullifier, + txHash, + recipient.address, + ), + ).rejects.toThrow(/is not present in tx/); + }); + + it('should throw if tx was mined after synced block number', async () => { + await setSyncedBlockNumber(BlockNumber(blockNumber - 1)); + + await expect( + noteSynchronizer.deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + noteHash, + nullifier, + txHash, + recipient.address, + ), + ).rejects.toThrow(/as of block number/); + }); + + it('should store and immediately remove note if it is already nullified', async () => { + nullified = true; + + await noteSynchronizer.deliverNote( + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + content, + noteHash, + nullifier, + txHash, + recipient.address, + ); + + // Verify note was removed + const notes = await noteDataProvider.getNotes({ contractAddress, scopes: [recipient.address] }); + expect(notes).toHaveLength(0); + }); + }); }); diff --git a/yarn-project/pxe/src/notes/note_synchronizer.ts b/yarn-project/pxe/src/notes/note_synchronizer.ts index 93650b5288fd..65b552aac9da 100644 --- a/yarn-project/pxe/src/notes/note_synchronizer.ts +++ b/yarn-project/pxe/src/notes/note_synchronizer.ts @@ -1,10 +1,13 @@ import { Fr } from '@aztec/foundation/curves/bn254'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { DataInBlock } from '@aztec/stdlib/block'; +import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; +import { Note } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; +import type { TxHash } from '@aztec/stdlib/tx'; -import type { AnchorBlockDataProvider, NoteDataProvider } from '../storage/index.js'; +import { type AnchorBlockDataProvider, NoteDao, type NoteDataProvider } from '../storage/index.js'; export class NoteSynchronizer { constructor( @@ -64,4 +67,94 @@ export class NoteSynchronizer { await this.noteDataProvider.applyNullifiers(foundNullifiers); } + + public async deliverNote( + contractAddress: AztecAddress, + owner: AztecAddress, + storageSlot: Fr, + randomness: Fr, + noteNonce: Fr, + content: Fr[], + noteHash: Fr, + nullifier: Fr, + txHash: TxHash, + recipient: AztecAddress, + ): Promise { + // We are going to store the new note in the NoteDataProvider, which will let us later return it via `getNotes`. + // There's two things we need to check before we do this however: + // - we must make sure the note does actually exist in the note hash tree + // - we need to check if the note has already been nullified + // + // Failing to do either of the above would result in circuits getting either non-existent notes and failing to + // produce inclusion proofs for them, or getting nullified notes and producing duplicate nullifiers, both of which + // are catastrophic failure modes. + // + // Note that adding a note and removing it is *not* equivalent to never adding it in the first place. A nullifier + // emitted in a block that comes after note creation might result in the note being de-nullified by a chain reorg, + // so we must store both the note hash and nullifier block information. + + // We avoid making node queries at 'latest' since we don't want to process notes or nullifiers that only exist ahead + // in time of the locally synced state. + // Note that while this technically results in historical queries, we perform it at the latest locally synced block + // number which *should* be recent enough to be available, even for non-archive nodes. + // Also note that the note should never be ahead of the synced block here since `fetchTaggedLogs` only processes + // logs up to the synced block making this only an additional safety check. + const syncedBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + + // By computing siloed and unique note hashes ourselves we prevent contracts from interfering with the note storage + // of other contracts, which would constitute a security breach. + const uniqueNoteHash = await computeUniqueNoteHash(noteNonce, await siloNoteHash(contractAddress, noteHash)); + const siloedNullifier = await siloNullifier(contractAddress, nullifier); + + const txEffect = await this.aztecNode.getTxEffect(txHash); + if (!txEffect) { + throw new Error(`Could not find tx effect for tx hash ${txHash}`); + } + + if (txEffect.l2BlockNumber > syncedBlockNumber) { + throw new Error(`Could not find tx effect for tx hash ${txHash} as of block number ${syncedBlockNumber}`); + } + + const noteInTx = txEffect.data.noteHashes.some(nh => nh.equals(uniqueNoteHash)); + if (!noteInTx) { + throw new Error(`Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present in tx ${txHash}`); + } + + // We store notes by their index in the global note hash tree, which has the convenient side effect of validating + // note existence in said tree. We concurrently also check if the note's nullifier exists, performing all node + // queries in a single round-trip. + const [[uniqueNoteHashTreeIndexInBlock], [nullifierIndex]] = await Promise.all([ + this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash]), + this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, [siloedNullifier]), + ]); + + if (uniqueNoteHashTreeIndexInBlock === undefined) { + throw new Error( + `Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present on the tree at block ${syncedBlockNumber} (from tx ${txHash})`, + ); + } + + const noteDao = new NoteDao( + new Note(content), + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + noteHash, + siloedNullifier, + txHash, + uniqueNoteHashTreeIndexInBlock.l2BlockNumber, + uniqueNoteHashTreeIndexInBlock.l2BlockHash.toString(), + uniqueNoteHashTreeIndexInBlock.data, + ); + + // The note was found by `recipient`, so we use that as the scope when storing the note. + await this.noteDataProvider.addNotes([noteDao], recipient); + + if (nullifierIndex !== undefined) { + const { data: _, ...blockHashAndNum } = nullifierIndex; + await this.noteDataProvider.applyNullifiers([{ data: siloedNullifier, ...blockHashAndNum }]); + } + } } From eec0dd3905c056b2b6dd4d270edcf3cdad6ae665 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 10:39:16 +0000 Subject: [PATCH 047/140] rename NoteSynchronizer => NoteService --- .../oracle/common.ts | 6 ++--- .../oracle/utility_execution_oracle.ts | 6 ++--- yarn-project/pxe/src/notes/index.ts | 2 +- ...chronizer.test.ts => note_service.test.ts} | 26 +++++++++---------- .../{note_synchronizer.ts => note_service.ts} | 2 +- yarn-project/txe/src/txe_session.ts | 6 ++--- 6 files changed, 24 insertions(+), 24 deletions(-) rename yarn-project/pxe/src/notes/{note_synchronizer.test.ts => note_service.test.ts} (94%) rename yarn-project/pxe/src/notes/{note_synchronizer.ts => note_service.ts} (99%) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index f6753bb6752b..e6e9bece2cf8 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -6,7 +6,7 @@ import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; -import { NoteSynchronizer } from '../../notes/note_synchronizer.js'; +import { NoteService } from '../../notes/note_service.js'; import { ORACLE_VERSION } from '../../oracle_version.js'; import { AnchorBlockDataProvider, @@ -63,10 +63,10 @@ export async function validateEnqueuedNotesAndEvents( await capsuleDataProvider.readCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot) ).map(EventValidationRequest.fromFields); - const noteSynchronizer = new NoteSynchronizer(noteDataProvider, aztecNode, anchorBlockDataProvider); + const noteService = new NoteService(noteDataProvider, aztecNode, anchorBlockDataProvider); const noteDeliveries = noteValidationRequests.map(request => - noteSynchronizer.deliverNote( + noteService.deliverNote( request.contractAddress, request.owner, request.storageSlot, 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 9adb2f60dfcf..783a1523227f 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 @@ -27,7 +27,7 @@ import type { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; -import { NoteSynchronizer } from '../../notes/note_synchronizer.js'; +import { NoteService } from '../../notes/note_service.js'; import type { AddressDataProvider, AnchorBlockDataProvider, @@ -461,8 +461,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra public async utilityFetchTaggedLogs(pendingTaggedLogArrayBaseSlot: Fr) { await this.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes); - const noteSynchronizer = new NoteSynchronizer(this.noteDataProvider, this.aztecNode, this.anchorBlockDataProvider); - await noteSynchronizer.syncNoteNullifiers(this.contractAddress); + const noteService = new NoteService(this.noteDataProvider, this.aztecNode, this.anchorBlockDataProvider); + await noteService.syncNoteNullifiers(this.contractAddress); } public async utilityValidateEnqueuedNotesAndEvents( diff --git a/yarn-project/pxe/src/notes/index.ts b/yarn-project/pxe/src/notes/index.ts index b7ee1d2e3e0b..3628cce2e8f7 100644 --- a/yarn-project/pxe/src/notes/index.ts +++ b/yarn-project/pxe/src/notes/index.ts @@ -1 +1 @@ -export { NoteSynchronizer } from './note_synchronizer.js'; +export { NoteService } from './note_service.js'; diff --git a/yarn-project/pxe/src/notes/note_synchronizer.test.ts b/yarn-project/pxe/src/notes/note_service.test.ts similarity index 94% rename from yarn-project/pxe/src/notes/note_synchronizer.test.ts rename to yarn-project/pxe/src/notes/note_service.test.ts index 397c519a4581..7f3d9b6fd185 100644 --- a/yarn-project/pxe/src/notes/note_synchronizer.test.ts +++ b/yarn-project/pxe/src/notes/note_service.test.ts @@ -15,9 +15,9 @@ import { jest } from '@jest/globals'; import { mock } from 'jest-mock-extended'; import { AnchorBlockDataProvider, NoteDataProvider } from '../storage/index.js'; -import { NoteSynchronizer } from './note_synchronizer.js'; +import { NoteService } from './note_service.js'; -describe('NoteSynchronizer', () => { +describe('NoteService', () => { let anchorBlockDataProvider: AnchorBlockDataProvider; let noteDataProvider: NoteDataProvider; let keyStore: KeyStore; @@ -26,7 +26,7 @@ describe('NoteSynchronizer', () => { let recipient: CompleteAddress; let contractAddress: AztecAddress; - let noteSynchronizer: NoteSynchronizer; + let noteService: NoteService; beforeEach(async () => { const store = await openTmpStore('test'); @@ -53,7 +53,7 @@ describe('NoteSynchronizer', () => { recipient = await keyStore.addAccount(new Fr(69), Fr.random()); - noteSynchronizer = new NoteSynchronizer(noteDataProvider, aztecNode, anchorBlockDataProvider); + noteService = new NoteService(noteDataProvider, aztecNode, anchorBlockDataProvider); }); it('should remove notes that have been nullified', async () => { @@ -72,7 +72,7 @@ describe('NoteSynchronizer', () => { aztecNode.findLeavesIndexes.mockResolvedValue([nullifierIndex]); // Call the function under test - await noteSynchronizer.syncNoteNullifiers(contractAddress); + await noteService.syncNoteNullifiers(contractAddress); // Verify the note was removed by checking storage const remainingNotes = await noteDataProvider.getNotes({ @@ -97,7 +97,7 @@ describe('NoteSynchronizer', () => { aztecNode.findLeavesIndexes.mockResolvedValue([undefined]); // Call the function under test - await noteSynchronizer.syncNoteNullifiers(contractAddress); + await noteService.syncNoteNullifiers(contractAddress); // Verify note still exists const remainingNotes = await noteDataProvider.getNotes({ @@ -128,7 +128,7 @@ describe('NoteSynchronizer', () => { }); // Call the function under test - await noteSynchronizer.syncNoteNullifiers(contractAddress); + await noteService.syncNoteNullifiers(contractAddress); // Verify note still exists const remainingNotes = await noteDataProvider.getNotes({ @@ -151,7 +151,7 @@ describe('NoteSynchronizer', () => { const getNotesSpy = jest.spyOn(noteDataProvider, 'getNotes'); // Call the function under test - await noteSynchronizer.syncNoteNullifiers(contractAddress); + await noteService.syncNoteNullifiers(contractAddress); // Verify applyNullifiers was called once for all accounts expect(getNotesSpy).toHaveBeenCalledTimes(1); @@ -262,7 +262,7 @@ describe('NoteSynchronizer', () => { }); it('should store note if it exists in note hash tree and is not nullified', async () => { - await noteSynchronizer.deliverNote( + await noteService.deliverNote( contractAddress, owner, storageSlot, @@ -284,7 +284,7 @@ describe('NoteSynchronizer', () => { it('should throw if tx hash does not exist', async () => { await expect( - noteSynchronizer.deliverNote( + noteService.deliverNote( contractAddress, owner, storageSlot, @@ -301,7 +301,7 @@ describe('NoteSynchronizer', () => { it('should throw if note was not emitted in the tx', async () => { await expect( - noteSynchronizer.deliverNote( + noteService.deliverNote( contractAddress, owner, storageSlot, @@ -320,7 +320,7 @@ describe('NoteSynchronizer', () => { await setSyncedBlockNumber(BlockNumber(blockNumber - 1)); await expect( - noteSynchronizer.deliverNote( + noteService.deliverNote( contractAddress, owner, storageSlot, @@ -338,7 +338,7 @@ describe('NoteSynchronizer', () => { it('should store and immediately remove note if it is already nullified', async () => { nullified = true; - await noteSynchronizer.deliverNote( + await noteService.deliverNote( contractAddress, owner, storageSlot, diff --git a/yarn-project/pxe/src/notes/note_synchronizer.ts b/yarn-project/pxe/src/notes/note_service.ts similarity index 99% rename from yarn-project/pxe/src/notes/note_synchronizer.ts rename to yarn-project/pxe/src/notes/note_service.ts index 65b552aac9da..0f18138d2b68 100644 --- a/yarn-project/pxe/src/notes/note_synchronizer.ts +++ b/yarn-project/pxe/src/notes/note_service.ts @@ -9,7 +9,7 @@ import type { TxHash } from '@aztec/stdlib/tx'; import { type AnchorBlockDataProvider, NoteDao, type NoteDataProvider } from '../storage/index.js'; -export class NoteSynchronizer { +export class NoteService { constructor( private readonly noteDataProvider: NoteDataProvider, private readonly aztecNode: AztecNode, diff --git a/yarn-project/txe/src/txe_session.ts b/yarn-project/txe/src/txe_session.ts index 55182bb50b84..c77acdf60fe7 100644 --- a/yarn-project/txe/src/txe_session.ts +++ b/yarn-project/txe/src/txe_session.ts @@ -8,7 +8,7 @@ import { AddressDataProvider, CapsuleDataProvider, NoteDataProvider, - NoteSynchronizer, + NoteService, PrivateEventDataProvider, RecipientTaggingDataProvider, SenderTaggingDataProvider, @@ -281,7 +281,7 @@ export class TXESession implements TXESessionStateHandler { // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not // be removed from the database. // TODO(#12553): make the synchronizer sync here instead and remove this - await new NoteSynchronizer( + await new NoteService( this.noteDataProvider, this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, @@ -370,7 +370,7 @@ export class TXESession implements TXESessionStateHandler { // we perform this. We therefore search for known nullifiers now, as otherwise notes that were nullified would not // be removed from the database. // TODO(#12553): make the synchronizer sync here instead and remove this - await new NoteSynchronizer( + await new NoteService( this.noteDataProvider, this.stateMachine.node, this.stateMachine.anchorBlockDataProvider, From 375364b8919bcb335a61632934e514ac2542fbe6 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 11:00:39 +0000 Subject: [PATCH 048/140] EventService --- .../oracle/common.test.ts | 182 ------------------ .../oracle/common.ts | 75 +------- .../pxe/src/events/event_service.test.ts | 150 +++++++++++++++ yarn-project/pxe/src/events/event_service.ts | 76 ++++++++ 4 files changed, 231 insertions(+), 252 deletions(-) delete mode 100644 yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts create mode 100644 yarn-project/pxe/src/events/event_service.test.ts create mode 100644 yarn-project/pxe/src/events/event_service.ts diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts deleted file mode 100644 index e45d9d981c72..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { BlockNumber } from '@aztec/foundation/branded-types'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import { KeyStore } from '@aztec/key-store'; -import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; -import { EventSelector } from '@aztec/stdlib/abi'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { L2BlockHash } from '@aztec/stdlib/block'; -import { CompleteAddress } from '@aztec/stdlib/contract'; -import { siloNullifier } from '@aztec/stdlib/hash'; -import type { AztecNode } from '@aztec/stdlib/interfaces/client'; -import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect } from '@aztec/stdlib/tx'; - -import { jest } from '@jest/globals'; -import { type MockProxy, mock } from 'jest-mock-extended'; - -import { - AddressDataProvider, - AnchorBlockDataProvider, - ContractDataProvider, - PrivateEventDataProvider, -} from '../../storage/index.js'; -import { deliverEvent } from './common.js'; - -jest.setTimeout(30_000); - -describe('Common oracle functions', () => { - let aztecNode: MockProxy; - - let addressDataProvider: AddressDataProvider; - let contractDataProvider: ContractDataProvider; - let anchorBlockDataProvider: AnchorBlockDataProvider; - let keyStore: KeyStore; - let privateEventDataProvider: PrivateEventDataProvider; - - let recipient: CompleteAddress; - let contractAddress: AztecAddress; - - // The block number of the last log to be emitted. - const MAX_BLOCK_NUMBER_OF_A_LOG = BlockNumber(3); - - beforeEach(async () => { - const store = await openTmpStore('test'); - aztecNode = mock(); - contractDataProvider = new ContractDataProvider(store); - jest.spyOn(contractDataProvider, 'getDebugContractName').mockImplementation(() => Promise.resolve('TestContract')); - - addressDataProvider = new AddressDataProvider(store); - anchorBlockDataProvider = new AnchorBlockDataProvider(store); - keyStore = new KeyStore(store); - privateEventDataProvider = new PrivateEventDataProvider(store); - - // Set up recipient account - recipient = await keyStore.addAccount(new Fr(69), Fr.random()); - await addressDataProvider.addCompleteAddress(recipient); - - // PXEOracleInterface.syncTaggedLogs(...) function syncs logs up to the block number up to which PXE synced. We set - // the synced block number to that of the last emitted log to receive all the logs by default. - await setSyncedBlockNumber(MAX_BLOCK_NUMBER_OF_A_LOG); - - contractAddress = await AztecAddress.random(); - }); - - describe('deliverEvent', () => { - let blockNumber: BlockNumber; - let eventSelector: EventSelector; - let eventContent: Fr[]; - let eventCommitment: Fr; - let eventNullifier: Fr; - let txEffect: TxEffect; - let indexedTxEffect: IndexedTxEffect; - - // beforeEach sets up the happy path case, so error modes are tested - // by minimally failing happy path conditions - beforeEach(async () => { - blockNumber = BlockNumber(42); - eventSelector = EventSelector.random(); - eventContent = [Fr.random(), Fr.random()]; - - eventCommitment = Fr.random(); - eventNullifier = await siloNullifier(contractAddress, eventCommitment); - - txEffect = TxEffect.from({ - ...(await TxEffect.random()), - nullifiers: [eventNullifier], - }); - - indexedTxEffect = { - l2BlockNumber: blockNumber, - l2BlockHash: L2BlockHash.random(), - data: txEffect, - txIndexInBlock: 0, - }; - - /* Happy path context conditions: - ** - PXE is sync'd to _at least_ block including tx - ** - Node knows tx effect - ** - Node knows siloed event commitment - */ - await setSyncedBlockNumber(blockNumber); - - aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(indexedTxEffect)); - - aztecNode.findLeavesIndexes.mockImplementation(() => - Promise.resolve([ - { - data: BigInt(0), - l2BlockNumber: indexedTxEffect.l2BlockNumber, - l2BlockHash: indexedTxEffect.l2BlockHash, - }, - ]), - ); - }); - - function runDeliverEvent( - overrides: { - eventCommitment?: Fr; - } = {}, - ) { - return deliverEvent( - contractAddress, - eventSelector, - eventContent, - overrides.eventCommitment || eventCommitment, - txEffect.txHash, - recipient.address, - anchorBlockDataProvider, - aztecNode, - privateEventDataProvider, - ); - } - - it('should throw when tx does not exist or has no effects', async () => { - aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(undefined)); - await expect(runDeliverEvent).rejects.toThrow(/Could not find tx effect for tx hash/); - }); - - it('should throw when tx block has not yet been synchronized', async () => { - indexedTxEffect = { - ...indexedTxEffect, - l2BlockNumber: BlockNumber(blockNumber + 1), - }; - aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(indexedTxEffect)); - - await expect(runDeliverEvent).rejects.toThrow(/Could not find tx effect for tx hash .* as of block number/); - }); - - it('should throw if event is not in tx effects', async () => { - await expect(runDeliverEvent({ eventCommitment: Fr.random() })).rejects.toThrow( - /Event commitment .* is not present in tx/, - ); - }); - - it('should throw if event is not in nullifiers', async () => { - aztecNode.findLeavesIndexes.mockImplementation(() => Promise.resolve([])); - - await expect(runDeliverEvent).rejects.toThrow(/Event commitment .* is not present on the nullifier tree/); - }); - - it('should store event for later retrieval', async () => { - await runDeliverEvent(); - - // I should be able to retrieve the private event I just saved using getPrivateEvents - const result = await privateEventDataProvider.getPrivateEvents(eventSelector, { - contractAddress, - fromBlock: blockNumber, - toBlock: blockNumber + 1, - scopes: [recipient.address], - }); - - expect(result.length).toEqual(1); - expect(result[0].packedEvent).toEqual(eventContent); - }); - }); - - const setSyncedBlockNumber = (blockNumber: BlockNumber) => { - return anchorBlockDataProvider.setHeader( - BlockHeader.empty({ - globalVariables: GlobalVariables.empty({ blockNumber }), - }), - ); - }; -}); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index e6e9bece2cf8..f007d42751d2 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,11 +1,9 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; -import type { EventSelector, FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; +import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { MerkleTreeId } from '@aztec/stdlib/trees'; -import type { TxHash } from '@aztec/stdlib/tx'; +import { EventService } from '../../events/event_service.js'; import { NoteService } from '../../notes/note_service.js'; import { ORACLE_VERSION } from '../../oracle_version.js'; import { @@ -80,17 +78,16 @@ export async function validateEnqueuedNotesAndEvents( ), ); + const eventService = new EventService(anchorBlockDataProvider, aztecNode, privateEventDataProvider); + const eventDeliveries = eventValidationRequests.map(request => - deliverEvent( + eventService.deliverEvent( request.contractAddress, request.eventTypeId, request.serializedEvent, request.eventCommitment, request.txHash, request.recipient, - anchorBlockDataProvider, - aztecNode, - privateEventDataProvider, ), ); @@ -100,65 +97,3 @@ export async function validateEnqueuedNotesAndEvents( await capsuleDataProvider.setCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot, []); await capsuleDataProvider.setCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot, []); } - -export async function deliverEvent( - contractAddress: AztecAddress, - selector: EventSelector, - content: Fr[], - eventCommitment: Fr, - txHash: TxHash, - scope: AztecAddress, - anchorBlockDataProvider: AnchorBlockDataProvider, - aztecNode: AztecNode, - privateEventDataProvider: PrivateEventDataProvider, -): Promise { - // While using 'latest' block number would be fine for private events since they cannot be accessed from Aztec.nr - // (and thus we're less concerned about being ahead of the synced block), we use the synced block number to - // maintain consistent behavior in the PXE. Additionally, events should never be ahead of the synced block here - // since `fetchTaggedLogs` only processes logs up to the synced block. - const [syncedBlockHeader, siloedEventCommitment, txEffect] = await Promise.all([ - anchorBlockDataProvider.getBlockHeader(), - siloNullifier(contractAddress, eventCommitment), - aztecNode.getTxEffect(txHash), - ]); - - const syncedBlockNumber = syncedBlockHeader.getBlockNumber(); - - if (!txEffect) { - throw new Error(`Could not find tx effect for tx hash ${txHash}`); - } - - if (txEffect.l2BlockNumber > syncedBlockNumber) { - throw new Error(`Could not find tx effect for tx hash ${txHash} as of block number ${syncedBlockNumber}`); - } - - const eventInTx = txEffect.data.nullifiers.some(n => n.equals(siloedEventCommitment)); - if (!eventInTx) { - throw new Error( - `Event commitment ${eventCommitment} (siloed as ${siloedEventCommitment}) is not present in tx ${txHash}`, - ); - } - - const [nullifierIndex] = await aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, [ - siloedEventCommitment, - ]); - - if (nullifierIndex === undefined) { - throw new Error( - `Event commitment ${eventCommitment} (siloed as ${siloedEventCommitment}) is not present on the nullifier tree at block ${syncedBlockNumber} (from tx ${txHash})`, - ); - } - - return privateEventDataProvider.storePrivateEventLog( - selector, - content, - Number(nullifierIndex.data), // Index of the event commitment in the nullifier tree - { - contractAddress, - scope, - txHash, - l2BlockNumber: nullifierIndex.l2BlockNumber, // Block number in which the event was emitted - l2BlockHash: nullifierIndex.l2BlockHash, // Block hash in which the event was emitted - }, - ); -} diff --git a/yarn-project/pxe/src/events/event_service.test.ts b/yarn-project/pxe/src/events/event_service.test.ts new file mode 100644 index 000000000000..2cb5a7e2dd35 --- /dev/null +++ b/yarn-project/pxe/src/events/event_service.test.ts @@ -0,0 +1,150 @@ +import { BlockNumber } from '@aztec/foundation/branded-types'; +import { Fr } from '@aztec/foundation/curves/bn254'; +import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; +import { EventSelector } from '@aztec/stdlib/abi'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { L2BlockHash } from '@aztec/stdlib/block'; +import { siloNullifier } from '@aztec/stdlib/hash'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; +import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect } from '@aztec/stdlib/tx'; + +import { mock } from 'jest-mock-extended'; + +import { AnchorBlockDataProvider, PrivateEventDataProvider } from '../storage/index.js'; +import { EventService } from './event_service.js'; + +describe('deliverEvent', () => { + let blockNumber: BlockNumber; + let eventSelector: EventSelector; + let eventContent: Fr[]; + let eventCommitment: Fr; + let eventNullifier: Fr; + let txEffect: TxEffect; + let indexedTxEffect: IndexedTxEffect; + let contractAddress: AztecAddress; + let recipient: AztecAddress; + + let anchorBlockDataProvider: AnchorBlockDataProvider; + let privateEventDataProvider: PrivateEventDataProvider; + let aztecNode: ReturnType>; + + let eventService: EventService; + + const setSyncedBlockNumber = (blockNumber: BlockNumber) => { + return anchorBlockDataProvider.setHeader( + BlockHeader.empty({ + globalVariables: GlobalVariables.empty({ blockNumber }), + }), + ); + }; + + // beforeEach sets up the happy path case, so error modes are tested + // by minimally failing happy path conditions + beforeEach(async () => { + const store = await openTmpStore('test'); + anchorBlockDataProvider = new AnchorBlockDataProvider(store); + privateEventDataProvider = new PrivateEventDataProvider(store); + + aztecNode = mock(); + + contractAddress = await AztecAddress.random(); + recipient = await AztecAddress.random(); + + blockNumber = BlockNumber(42); + eventSelector = EventSelector.random(); + eventContent = [Fr.random(), Fr.random()]; + + eventCommitment = Fr.random(); + eventNullifier = await siloNullifier(contractAddress, eventCommitment); + + txEffect = TxEffect.from({ + ...(await TxEffect.random()), + nullifiers: [eventNullifier], + }); + + indexedTxEffect = { + l2BlockNumber: blockNumber, + l2BlockHash: L2BlockHash.random(), + data: txEffect, + txIndexInBlock: 0, + }; + + /* Happy path context conditions: + ** - PXE is sync'd to _at least_ block including tx + ** - Node knows tx effect + ** - Node knows siloed event commitment + */ + await setSyncedBlockNumber(blockNumber); + + aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(indexedTxEffect)); + + aztecNode.findLeavesIndexes.mockImplementation(() => + Promise.resolve([ + { + data: BigInt(0), + l2BlockNumber: indexedTxEffect.l2BlockNumber, + l2BlockHash: indexedTxEffect.l2BlockHash, + }, + ]), + ); + + eventService = new EventService(anchorBlockDataProvider, aztecNode, privateEventDataProvider); + }); + + function runDeliverEvent( + overrides: { + eventCommitment?: Fr; + } = {}, + ) { + return eventService.deliverEvent( + contractAddress, + eventSelector, + eventContent, + overrides.eventCommitment || eventCommitment, + txEffect.txHash, + recipient, + ); + } + + it('should throw when tx does not exist or has no effects', async () => { + aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(undefined)); + await expect(runDeliverEvent).rejects.toThrow(/Could not find tx effect for tx hash/); + }); + + it('should throw when tx block has not yet been synchronized', async () => { + indexedTxEffect = { + ...indexedTxEffect, + l2BlockNumber: BlockNumber(blockNumber + 1), + }; + aztecNode.getTxEffect.mockImplementation(() => Promise.resolve(indexedTxEffect)); + + await expect(runDeliverEvent).rejects.toThrow(/Could not find tx effect for tx hash .* as of block number/); + }); + + it('should throw if event is not in tx effects', async () => { + await expect(runDeliverEvent({ eventCommitment: Fr.random() })).rejects.toThrow( + /Event commitment .* is not present in tx/, + ); + }); + + it('should throw if event is not in nullifiers', async () => { + aztecNode.findLeavesIndexes.mockImplementation(() => Promise.resolve([])); + + await expect(runDeliverEvent).rejects.toThrow(/Event commitment .* is not present on the nullifier tree/); + }); + + it('should store event for later retrieval', async () => { + await runDeliverEvent(); + + // I should be able to retrieve the private event I just saved using getPrivateEvents + const result = await privateEventDataProvider.getPrivateEvents(eventSelector, { + contractAddress, + fromBlock: blockNumber, + toBlock: blockNumber + 1, + scopes: [recipient], + }); + + expect(result.length).toEqual(1); + expect(result[0].packedEvent).toEqual(eventContent); + }); +}); diff --git a/yarn-project/pxe/src/events/event_service.ts b/yarn-project/pxe/src/events/event_service.ts new file mode 100644 index 000000000000..eaf3a8a1ca5f --- /dev/null +++ b/yarn-project/pxe/src/events/event_service.ts @@ -0,0 +1,76 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; +import type { EventSelector } from '@aztec/stdlib/abi'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { siloNullifier } from '@aztec/stdlib/hash'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; +import { MerkleTreeId } from '@aztec/stdlib/trees'; +import type { TxHash } from '@aztec/stdlib/tx'; + +import type { AnchorBlockDataProvider, PrivateEventDataProvider } from '../storage/index.js'; + +export class EventService { + constructor( + private readonly anchorBlockDataProvider: AnchorBlockDataProvider, + private readonly aztecNode: AztecNode, + private readonly privateEventDataProvider: PrivateEventDataProvider, + ) {} + + public async deliverEvent( + contractAddress: AztecAddress, + selector: EventSelector, + content: Fr[], + eventCommitment: Fr, + txHash: TxHash, + scope: AztecAddress, + ): Promise { + // While using 'latest' block number would be fine for private events since they cannot be accessed from Aztec.nr + // (and thus we're less concerned about being ahead of the synced block), we use the synced block number to + // maintain consistent behavior in the PXE. Additionally, events should never be ahead of the synced block here + // since `fetchTaggedLogs` only processes logs up to the synced block. + const [syncedBlockHeader, siloedEventCommitment, txEffect] = await Promise.all([ + this.anchorBlockDataProvider.getBlockHeader(), + siloNullifier(contractAddress, eventCommitment), + this.aztecNode.getTxEffect(txHash), + ]); + + const syncedBlockNumber = syncedBlockHeader.getBlockNumber(); + + if (!txEffect) { + throw new Error(`Could not find tx effect for tx hash ${txHash}`); + } + + if (txEffect.l2BlockNumber > syncedBlockNumber) { + throw new Error(`Could not find tx effect for tx hash ${txHash} as of block number ${syncedBlockNumber}`); + } + + const eventInTx = txEffect.data.nullifiers.some(n => n.equals(siloedEventCommitment)); + if (!eventInTx) { + throw new Error( + `Event commitment ${eventCommitment} (siloed as ${siloedEventCommitment}) is not present in tx ${txHash}`, + ); + } + + const [nullifierIndex] = await this.aztecNode.findLeavesIndexes(syncedBlockNumber, MerkleTreeId.NULLIFIER_TREE, [ + siloedEventCommitment, + ]); + + if (nullifierIndex === undefined) { + throw new Error( + `Event commitment ${eventCommitment} (siloed as ${siloedEventCommitment}) is not present on the nullifier tree at block ${syncedBlockNumber} (from tx ${txHash})`, + ); + } + + return this.privateEventDataProvider.storePrivateEventLog( + selector, + content, + Number(nullifierIndex.data), // Index of the event commitment in the nullifier tree + { + contractAddress, + scope, + txHash, + l2BlockNumber: nullifierIndex.l2BlockNumber, // Block number in which the event was emitted + l2BlockHash: nullifierIndex.l2BlockHash, // Block hash in which the event was emitted + }, + ); + } +} From 6ac2b9c86a221a70f66f12855113a3199ab01ffd Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 11:06:48 +0000 Subject: [PATCH 049/140] refactor validateEnqueuedNotesAndEvents --- .../oracle/common.ts | 71 +------------------ .../oracle/utility_execution_oracle.ts | 56 ++++++++++++--- 2 files changed, 47 insertions(+), 80 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index f007d42751d2..94fb93aad74b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,20 +1,8 @@ -import type { Fr } from '@aztec/foundation/curves/bn254'; import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { EventService } from '../../events/event_service.js'; -import { NoteService } from '../../notes/note_service.js'; import { ORACLE_VERSION } from '../../oracle_version.js'; -import { - AnchorBlockDataProvider, - type CapsuleDataProvider, - type ContractDataProvider, - type NoteDataProvider, - PrivateEventDataProvider, -} from '../../storage/index.js'; -import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; -import { NoteValidationRequest } from '../noir-structs/note_validation_request.js'; +import type { ContractDataProvider } from '../../storage/index.js'; // TODO: this might not be the final home for these functions, // it's just a way of starting to dissolve PXEOracleInterface @@ -40,60 +28,3 @@ export function assertCompatibleOracleVersion(version: number): void { throw new Error(`Incompatible oracle version. Expected version ${ORACLE_VERSION}, got ${version}.`); } } - -export async function validateEnqueuedNotesAndEvents( - contractAddress: AztecAddress, - noteValidationRequestsArrayBaseSlot: Fr, - eventValidationRequestsArrayBaseSlot: Fr, - capsuleDataProvider: CapsuleDataProvider, - anchorBlockDataProvider: AnchorBlockDataProvider, - aztecNode: AztecNode, - noteDataProvider: NoteDataProvider, - privateEventDataProvider: PrivateEventDataProvider, -): Promise { - // We read all note and event validation requests and process them all concurrently. This makes the process much - // faster as we don't need to wait for the network round-trip. - const noteValidationRequests = ( - await capsuleDataProvider.readCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot) - ).map(NoteValidationRequest.fromFields); - - const eventValidationRequests = ( - await capsuleDataProvider.readCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot) - ).map(EventValidationRequest.fromFields); - - const noteService = new NoteService(noteDataProvider, aztecNode, anchorBlockDataProvider); - - const noteDeliveries = noteValidationRequests.map(request => - noteService.deliverNote( - request.contractAddress, - request.owner, - request.storageSlot, - request.randomness, - request.noteNonce, - request.content, - request.noteHash, - request.nullifier, - request.txHash, - request.recipient, - ), - ); - - const eventService = new EventService(anchorBlockDataProvider, aztecNode, privateEventDataProvider); - - const eventDeliveries = eventValidationRequests.map(request => - eventService.deliverEvent( - request.contractAddress, - request.eventTypeId, - request.serializedEvent, - request.eventCommitment, - request.txHash, - request.recipient, - ), - ); - - await Promise.all([...noteDeliveries, ...eventDeliveries]); - - // Requests are cleared once we're done. - await capsuleDataProvider.setCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot, []); - await capsuleDataProvider.setCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot, []); -} 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 783a1523227f..aa9bef766bdb 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 @@ -27,6 +27,7 @@ import type { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; +import { EventService } from '../../events/event_service.js'; import { NoteService } from '../../notes/note_service.js'; import type { AddressDataProvider, @@ -39,11 +40,13 @@ import type { SenderTaggingDataProvider, } from '../../storage/index.js'; import { SiloedTag, Tag, WINDOW_HALF_SIZE, getInitialIndexesMap, getPreTagsForTheWindow } from '../../tagging/index.js'; +import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; +import { NoteValidationRequest } from '../noir-structs/note_validation_request.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; -import { assertCompatibleOracleVersion, validateEnqueuedNotesAndEvents } from './common.js'; +import { assertCompatibleOracleVersion } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; @@ -475,16 +478,49 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra throw new Error(`Got a note validation request from ${contractAddress}, expected ${this.contractAddress}`); } - await validateEnqueuedNotesAndEvents( - contractAddress, - noteValidationRequestsArrayBaseSlot, - eventValidationRequestsArrayBaseSlot, - this.capsuleDataProvider, - this.anchorBlockDataProvider, - this.aztecNode, - this.noteDataProvider, - this.privateEventDataProvider, + // We read all note and event validation requests and process them all concurrently. This makes the process much + // faster as we don't need to wait for the network round-trip. + const noteValidationRequests = ( + await this.capsuleDataProvider.readCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot) + ).map(NoteValidationRequest.fromFields); + + const eventValidationRequests = ( + await this.capsuleDataProvider.readCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot) + ).map(EventValidationRequest.fromFields); + + const noteService = new NoteService(this.noteDataProvider, this.aztecNode, this.anchorBlockDataProvider); + const noteDeliveries = noteValidationRequests.map(request => + noteService.deliverNote( + request.contractAddress, + request.owner, + request.storageSlot, + request.randomness, + request.noteNonce, + request.content, + request.noteHash, + request.nullifier, + request.txHash, + request.recipient, + ), ); + + const eventService = new EventService(this.anchorBlockDataProvider, this.aztecNode, this.privateEventDataProvider); + const eventDeliveries = eventValidationRequests.map(request => + eventService.deliverEvent( + request.contractAddress, + request.eventTypeId, + request.serializedEvent, + request.eventCommitment, + request.txHash, + request.recipient, + ), + ); + + await Promise.all([...noteDeliveries, ...eventDeliveries]); + + // Requests are cleared once we're done. + await this.capsuleDataProvider.setCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot, []); + await this.capsuleDataProvider.setCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot, []); } public async utilityBulkRetrieveLogs( From 4a0d9d4a9d78bb7f6f5a064093868d8820b07221 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 11:30:46 +0000 Subject: [PATCH 050/140] getFunctionArtifactWithDebugMetadata --- .../contract_function_simulator.ts | 11 ++++++--- .../oracle/common.ts | 23 ------------------- .../oracle/oracle_version_is_checked.test.ts | 7 ++++++ .../oracle/private_execution.test.ts | 7 ++++++ .../oracle/private_execution_oracle.ts | 4 +--- .../oracle/utility_execution.test.ts | 7 ++++++ .../proxied_contract_data_source.ts | 17 ++++++++++++++ .../contract_data_provider.ts | 15 ++++++++++++ .../oracle/txe_oracle_top_level_context.ts | 6 +++-- 9 files changed, 66 insertions(+), 31 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index f01e33945c50..712deed77a2c 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -84,7 +84,6 @@ import type { import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; import { HashedValuesCache } from './hashed_values_cache.js'; -import { getFunctionArtifact } from './oracle/common.js'; import { Oracle } from './oracle/oracle.js'; import { executePrivateFunction, verifyCurrentClassId } from './oracle/private_execution.js'; import { PrivateExecutionOracle } from './oracle/private_execution_oracle.js'; @@ -145,7 +144,10 @@ export class ContractFunctionSimulator { anchorBlockHeader, ); - const entryPointArtifact = await getFunctionArtifact(contractAddress, selector, this.contractDataProvider); + const entryPointArtifact = await this.contractDataProvider.getFunctionArtifactWithDebugMetadata( + contractAddress, + selector, + ); if (entryPointArtifact.functionType !== FunctionType.PRIVATE) { throw new Error(`Cannot run ${entryPointArtifact.functionType} function as private`); @@ -270,7 +272,10 @@ export class ContractFunctionSimulator { anchorBlockHeader, ); - const entryPointArtifact = await getFunctionArtifact(call.to, call.selector, this.contractDataProvider); + const entryPointArtifact = await this.contractDataProvider.getFunctionArtifactWithDebugMetadata( + call.to, + call.selector, + ); if (entryPointArtifact.functionType !== FunctionType.UTILITY) { throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts index 94fb93aad74b..29ae076a273d 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts @@ -1,27 +1,4 @@ -import type { FunctionArtifactWithContractName, FunctionSelector } from '@aztec/stdlib/abi'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; - import { ORACLE_VERSION } from '../../oracle_version.js'; -import type { ContractDataProvider } from '../../storage/index.js'; - -// TODO: this might not be the final home for these functions, -// it's just a way of starting to dissolve PXEOracleInterface - -export async function getFunctionArtifact( - contractAddress: AztecAddress, - selector: FunctionSelector, - contractDataProvider: ContractDataProvider, -): Promise { - const artifact = await contractDataProvider.getFunctionArtifact(contractAddress, selector); - if (!artifact) { - throw new Error(`Function artifact not found for contract ${contractAddress} and selector ${selector}.`); - } - const debug = await contractDataProvider.getFunctionDebugMetadata(contractAddress, selector); - return { - ...artifact, - debug, - }; -} export function assertCompatibleOracleVersion(version: number): void { if (version !== ORACLE_VERSION) { diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index b0f77470b015..cb74f0a3d558 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -86,6 +86,13 @@ describe('Oracle Version Check test suite', () => { originalContractClassId: new Fr(42), address: contractAddress, } as ContractInstanceWithAddress); + contractDataProvider.getFunctionArtifactWithDebugMetadata.mockImplementation(async (address, selector) => { + const artifact = await contractDataProvider.getFunctionArtifact(address, selector); + if (!artifact) { + throw new Error(`Function not found: ${selector.toString()} in contract ${address}`); + } + return { ...artifact, debug: undefined }; + }); acirSimulator = new ContractFunctionSimulator( contractDataProvider, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index dc39c8c5bade..9a2299841e19 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -410,6 +410,13 @@ describe('Private Execution test suite', () => { } return Promise.resolve(artifact); }); + contractDataProvider.getFunctionArtifactWithDebugMetadata.mockImplementation(async (address, selector) => { + const artifact = await contractDataProvider.getFunctionArtifact(address, selector); + if (!artifact) { + throw new Error(`Function not found: ${selector.toString()} in contract ${address}`); + } + return { ...artifact, debug: undefined }; + }); capsuleDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 6c12fd5afb30..4c100094b5d6 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -44,7 +44,6 @@ import type { ExecutionNoteCache } from '../execution_note_cache.js'; import { ExecutionTaggingIndexCache } from '../execution_tagging_index_cache.js'; import type { HashedValuesCache } from '../hashed_values_cache.js'; import { pickNotes } from '../pick_notes.js'; -import { getFunctionArtifact } from './common.js'; import type { IPrivateExecutionOracle, NoteData } from './interfaces.js'; import { executePrivateFunction, verifyCurrentClassId } from './private_execution.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; @@ -548,10 +547,9 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP this.anchorBlockHeader, ); - const targetArtifact = await getFunctionArtifact( + const targetArtifact = await this.contractDataProvider.getFunctionArtifactWithDebugMetadata( targetContractAddress, functionSelector, - this.contractDataProvider, ); const derivedTxContext = this.txContext.clone(); 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 931319211e06..09f33b952269 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 @@ -124,6 +124,13 @@ describe('Utility Execution test suite', () => { originalContractClassId: new Fr(42), address: contractAddress, } as ContractInstanceWithAddress); + contractDataProvider.getFunctionArtifactWithDebugMetadata.mockImplementation(async (address, selector) => { + const artifact = await contractDataProvider.getFunctionArtifact(address, selector); + if (!artifact) { + throw new Error(`Function not found: ${selector.toString()} in contract ${address}`); + } + return { ...artifact, debug: undefined }; + }); noteDataProvider.getNotes.mockResolvedValue( notes.map( (note, index) => diff --git a/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts b/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts index 8b725416259d..ceb673ad7c41 100644 --- a/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts +++ b/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts @@ -52,6 +52,23 @@ export class ProxiedContractDataProviderFactory { } }; } + case 'getFunctionArtifactWithDebugMetadata': { + return async (contractAddress: AztecAddress, selector: FunctionSelector) => { + if (overrides[contractAddress.toString()]) { + const { artifact } = overrides[contractAddress.toString()]!; + const functions = artifact.functions; + for (let i = 0; i < functions.length; i++) { + const fn = functions[i]; + const fnSelector = await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters); + if (fnSelector.equals(selector)) { + return fn; + } + } + } else { + return target.getFunctionArtifactWithDebugMetadata(contractAddress, selector); + } + }; + } default: { const value = Reflect.get(target, prop); if (typeof value === 'function') { diff --git a/yarn-project/pxe/src/storage/contract_data_provider/contract_data_provider.ts b/yarn-project/pxe/src/storage/contract_data_provider/contract_data_provider.ts index 1cfe78f10d4a..225625afe729 100644 --- a/yarn-project/pxe/src/storage/contract_data_provider/contract_data_provider.ts +++ b/yarn-project/pxe/src/storage/contract_data_provider/contract_data_provider.ts @@ -178,6 +178,21 @@ export class ContractDataProvider { return fnArtifact && { ...fnArtifact, contractName: artifact.name }; } + public async getFunctionArtifactWithDebugMetadata( + contractAddress: AztecAddress, + selector: FunctionSelector, + ): Promise { + const artifact = await this.getFunctionArtifact(contractAddress, selector); + if (!artifact) { + throw new Error(`Function artifact not found for contract ${contractAddress} and selector ${selector}.`); + } + const debug = await this.getFunctionDebugMetadata(contractAddress, selector); + return { + ...artifact, + debug, + }; + } + public async getPublicFunctionArtifact( contractAddress: AztecAddress, ): Promise { 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 e7e7cff7f265..6a95576d8318 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 @@ -21,7 +21,6 @@ import { RecipientTaggingDataProvider, SenderTaggingDataProvider, enrichPublicSimulationError, - getFunctionArtifact, } from '@aztec/pxe/server'; import { ExecutionNoteCache, @@ -616,7 +615,10 @@ export class TXEOracleTopLevelContext implements IMiscOracle, ITxeExecutionOracl to: targetContractAddress, }; - const entryPointArtifact = await getFunctionArtifact(call.to, call.selector, this.contractDataProvider); + const entryPointArtifact = await this.contractDataProvider.getFunctionArtifactWithDebugMetadata( + call.to, + call.selector, + ); if (entryPointArtifact.functionType !== FunctionType.UTILITY) { throw new Error(`Cannot run ${entryPointArtifact.functionType} function as utility`); } From 88e3f337ca284b3b6e7f290f1e90e613a6ae649f Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 13:32:21 +0000 Subject: [PATCH 051/140] good bye common --- .../contract_function_simulator/oracle/common.ts | 7 ------- .../contract_function_simulator/oracle/index.ts | 1 - .../oracle/oracle_version_is_checked.test.ts | 14 +++++++------- .../oracle/utility_execution_oracle.ts | 6 ++++-- .../pxe/src/entrypoints/client/bundle/index.ts | 1 - .../pxe/src/entrypoints/client/lazy/index.ts | 1 - yarn-project/pxe/src/entrypoints/server/index.ts | 1 - 7 files changed, 11 insertions(+), 20 deletions(-) delete mode 100644 yarn-project/pxe/src/contract_function_simulator/oracle/common.ts diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts deleted file mode 100644 index 29ae076a273d..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/common.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ORACLE_VERSION } from '../../oracle_version.js'; - -export function assertCompatibleOracleVersion(version: number): void { - if (version !== ORACLE_VERSION) { - throw new Error(`Incompatible oracle version. Expected version ${ORACLE_VERSION}, got ${version}.`); - } -} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/index.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/index.ts index 63e4c7aadf6c..10156a3340bc 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/index.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/index.ts @@ -2,7 +2,6 @@ import type { Oracle } from './oracle.js'; export * from './oracle.js'; export * from './interfaces.js'; -export * from './common.js'; /** * A conditional type that takes a type `T` and returns a union of its method names. diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index cb74f0a3d558..b9d8a8150943 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -41,7 +41,7 @@ describe('Oracle Version Check test suite', () => { let acirSimulator: ContractFunctionSimulator; let contractAddress: AztecAddress; let anchorBlockHeader: BlockHeader; - let assertCompatibleOracleVersionSpy: jest.SpiedFunction< + let utilityAssertCompatibleOracleVersionSpy: jest.SpiedFunction< typeof UtilityExecutionOracle.prototype.utilityAssertCompatibleOracleVersion >; @@ -56,11 +56,11 @@ describe('Oracle Version Check test suite', () => { recipientTaggingDataProvider = mock(); capsuleDataProvider = mock(); privateEventDataProvider = mock(); - assertCompatibleOracleVersionSpy = jest.spyOn( + utilityAssertCompatibleOracleVersionSpy = jest.spyOn( UtilityExecutionOracle.prototype, 'utilityAssertCompatibleOracleVersion', ); - assertCompatibleOracleVersionSpy.mockClear(); + utilityAssertCompatibleOracleVersionSpy.mockClear(); // Mock basic oracle responses aztecNode.getPublicStorageAt.mockResolvedValue(Fr.ZERO); @@ -110,7 +110,7 @@ describe('Oracle Version Check test suite', () => { }); describe('private function execution', () => { - it('should call assertCompatibleOracleVersion oracle when private function is called', async () => { + it('should call utilityAssertCompatibleOracleVersion oracle when private function is called', async () => { // Load the artifact of the OracleVersionCheck::private_function contract function and set up the relevant oracle handler const privateFunctionArtifact = { ...OracleVersionCheckContractArtifact.functions.find(f => f.name === 'private_function')!, @@ -144,12 +144,12 @@ describe('Oracle Version Check test suite', () => { const senderForTags = await AztecAddress.random(); await acirSimulator.run(txRequest, contractAddress, selector, msgSender, anchorBlockHeader, senderForTags); - expect(assertCompatibleOracleVersionSpy).toHaveBeenCalledTimes(1); + expect(utilityAssertCompatibleOracleVersionSpy).toHaveBeenCalledTimes(1); }, 30_000); }); describe('utility function execution', () => { - it('should call assertCompatibleOracleVersion oracle when utility function is called', async () => { + it('should call utilityAssertCompatibleOracleVersion oracle when utility function is called', async () => { // Load the artifact of the OracleVersionCheck::utility_function contract function and set up the relevant oracle // handler const utilityFunctionArtifact = { @@ -173,7 +173,7 @@ describe('Oracle Version Check test suite', () => { // Call the utility function await acirSimulator.runUtility(execRequest, [], anchorBlockHeader, []); - expect(assertCompatibleOracleVersionSpy).toHaveBeenCalledTimes(1); + expect(utilityAssertCompatibleOracleVersionSpy).toHaveBeenCalledTimes(1); }, 30_000); }); }); 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 aa9bef766bdb..ee8c2c25f418 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 @@ -29,6 +29,7 @@ import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; import { EventService } from '../../events/event_service.js'; import { NoteService } from '../../notes/note_service.js'; +import { ORACLE_VERSION } from '../../oracle_version.js'; import type { AddressDataProvider, AnchorBlockDataProvider, @@ -46,7 +47,6 @@ import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js' import { NoteValidationRequest } from '../noir-structs/note_validation_request.js'; import { UtilityContext } from '../noir-structs/utility_context.js'; import { pickNotes } from '../pick_notes.js'; -import { assertCompatibleOracleVersion } from './common.js'; import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js'; import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; @@ -80,7 +80,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra ) {} public utilityAssertCompatibleOracleVersion(version: number): void { - assertCompatibleOracleVersion(version); + if (version !== ORACLE_VERSION) { + throw new Error(`Incompatible oracle version. Expected version ${ORACLE_VERSION}, got ${version}.`); + } } public utilityGetRandomField(): Fr { diff --git a/yarn-project/pxe/src/entrypoints/client/bundle/index.ts b/yarn-project/pxe/src/entrypoints/client/bundle/index.ts index 957ac7b742db..6e5fd553f972 100644 --- a/yarn-project/pxe/src/entrypoints/client/bundle/index.ts +++ b/yarn-project/pxe/src/entrypoints/client/bundle/index.ts @@ -3,6 +3,5 @@ export * from '../../../config/index.js'; export * from '../../../error_enriching.js'; export * from '../../../storage/index.js'; export * from './utils.js'; -export * from '../../../contract_function_simulator/oracle/common.js'; export * from '../../../notes/index.js'; export type { PXECreationOptions } from '../../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/entrypoints/client/lazy/index.ts b/yarn-project/pxe/src/entrypoints/client/lazy/index.ts index bc9b7b2ddc82..ba9e41912bf2 100644 --- a/yarn-project/pxe/src/entrypoints/client/lazy/index.ts +++ b/yarn-project/pxe/src/entrypoints/client/lazy/index.ts @@ -3,6 +3,5 @@ export * from '../../../config/index.js'; export * from '../../../storage/index.js'; export * from '../../../error_enriching.js'; export * from './utils.js'; -export * from '../../../contract_function_simulator/oracle/common.js'; export * from '../../../notes/index.js'; export { type PXECreationOptions } from '../../pxe_creation_options.js'; diff --git a/yarn-project/pxe/src/entrypoints/server/index.ts b/yarn-project/pxe/src/entrypoints/server/index.ts index a7094cfb0969..9d4a4f3e4cf8 100644 --- a/yarn-project/pxe/src/entrypoints/server/index.ts +++ b/yarn-project/pxe/src/entrypoints/server/index.ts @@ -3,7 +3,6 @@ export * from '../../config/index.js'; export * from '../../error_enriching.js'; export * from '../../storage/index.js'; export * from './utils.js'; -export * from '../../contract_function_simulator/oracle/common.js'; export * from '../../notes/index.js'; export { ORACLE_VERSION } from '../../oracle_version.js'; export { type PXECreationOptions } from '../pxe_creation_options.js'; From 9a6bcbdfd0c57ab778dad190162225d832285753 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 13:58:58 +0000 Subject: [PATCH 052/140] simplify readCurrentClassId --- .../contract_function_simulator.ts | 16 +------ .../oracle/oracle_version_is_checked.test.ts | 1 - .../oracle/private_execution.ts | 43 ++----------------- .../oracle/private_execution_oracle.ts | 1 - yarn-project/pxe/src/pxe.ts | 14 +++++- 5 files changed, 17 insertions(+), 58 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 712deed77a2c..2936986cba77 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -136,13 +136,7 @@ export class ContractFunctionSimulator { ): Promise { const simulatorSetupTimer = new Timer(); - await verifyCurrentClassId( - contractAddress, - this.anchorBlockDataProvider, - this.aztecNode, - this.contractDataProvider, - anchorBlockHeader, - ); + await verifyCurrentClassId(contractAddress, this.aztecNode, this.contractDataProvider, anchorBlockHeader); const entryPointArtifact = await this.contractDataProvider.getFunctionArtifactWithDebugMetadata( contractAddress, @@ -264,13 +258,7 @@ export class ContractFunctionSimulator { anchorBlockHeader: BlockHeader, scopes?: AztecAddress[], ): Promise { - await verifyCurrentClassId( - call.to, - this.anchorBlockDataProvider, - this.aztecNode, - this.contractDataProvider, - anchorBlockHeader, - ); + await verifyCurrentClassId(call.to, this.aztecNode, this.contractDataProvider, anchorBlockHeader); const entryPointArtifact = await this.contractDataProvider.getFunctionArtifactWithDebugMetadata( call.to, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index b9d8a8150943..772ec4a4ba1b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -62,7 +62,6 @@ describe('Oracle Version Check test suite', () => { ); utilityAssertCompatibleOracleVersionSpy.mockClear(); - // Mock basic oracle responses aztecNode.getPublicStorageAt.mockResolvedValue(Fr.ZERO); anchorBlockHeader = BlockHeader.random(); anchorBlockDataProvider.getBlockHeader.mockResolvedValue(anchorBlockHeader); diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts index ab2324f019de..92bd13b1d881 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts @@ -27,7 +27,7 @@ import type { CircuitWitnessGenerationStats } from '@aztec/stdlib/stats'; import { BlockHeader, PrivateCallExecutionResult } from '@aztec/stdlib/tx'; import type { UInt64 } from '@aztec/stdlib/types'; -import { AnchorBlockDataProvider, ContractDataProvider } from '../../storage/index.js'; +import { ContractDataProvider } from '../../storage/index.js'; import { Oracle } from './oracle.js'; import type { PrivateExecutionOracle } from './private_execution_oracle.js'; @@ -159,42 +159,7 @@ export function extractPrivateCircuitPublicInputs( * @param timestamp - The timestamp at which to obtain the class id from the DelayedPublicMutable. * @returns The current class id. */ -export async function readCurrentClassIdFromCurrentBlockAnchor( - contractAddress: AztecAddress, - instance: ContractInstance, - anchorBlockDataProvider: AnchorBlockDataProvider, - aztecNode: AztecNode, - blockNumber: BlockNumber, - timestamp: UInt64, -) { - const anchorBlockNumber = (await anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - - const { delayedPublicMutableSlot } = await DelayedPublicMutableValuesWithHash.getContractUpdateSlots(contractAddress); - const delayedPublicMutableValues = await DelayedPublicMutableValues.readFromTree(delayedPublicMutableSlot, slot => { - return aztecNode.getPublicStorageAt(blockNumber, ProtocolContractAddress.ContractInstanceRegistry, slot); - }); - - let currentClassId = delayedPublicMutableValues.svc.getCurrentAt(timestamp)[0]; - if (currentClassId.isZero()) { - currentClassId = instance.originalContractClassId; - } - return currentClassId; -} - -/** - * Read the current class id of a contract from the execution data provider or AztecNode. If not found, class id - * from the instance is used. - * @param contractAddress - The address of the contract to read the class id for. - * @param instance - The instance of the contract. - * @param executionDataProvider - The execution data provider. - * @param blockNumber - The block number at which to load the DelayedPublicMutable storing the class id. - * @param timestamp - The timestamp at which to obtain the class id from the DelayedPublicMutable. - * @returns The current class id. - */ -export async function readCurrentClassIdFromNode( +export async function readCurrentClassId( contractAddress: AztecAddress, instance: ContractInstance, aztecNode: AztecNode, @@ -222,7 +187,6 @@ export async function readCurrentClassIdFromNode( */ export async function verifyCurrentClassId( contractAddress: AztecAddress, - anchorBlockDataProvider: AnchorBlockDataProvider, aztecNode: AztecNode, contractDataProvider: ContractDataProvider, header: BlockHeader, @@ -232,10 +196,9 @@ export async function verifyCurrentClassId( throw new Error(`No contract instance found for address ${contractAddress.toString()}`); } - const currentClassId = await readCurrentClassIdFromCurrentBlockAnchor( + const currentClassId = await readCurrentClassId( contractAddress, instance, - anchorBlockDataProvider, aztecNode, header.globalVariables.blockNumber, header.globalVariables.timestamp, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 4c100094b5d6..865dd3f29ee1 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -541,7 +541,6 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP await verifyCurrentClassId( targetContractAddress, - this.anchorBlockDataProvider, this.aztecNode, this.contractDataProvider, this.anchorBlockHeader, diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 28adf3acb740..125d729a9b24 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -1,4 +1,5 @@ import type { PrivateEventFilter } from '@aztec/aztec.js/wallet'; +import type { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { SerialQueue } from '@aztec/foundation/queue'; @@ -58,7 +59,7 @@ import { ContractFunctionSimulator, generateSimulatedProvingResult, } from './contract_function_simulator/contract_function_simulator.js'; -import { readCurrentClassIdFromNode } from './contract_function_simulator/oracle/private_execution.js'; +import { readCurrentClassId } from './contract_function_simulator/oracle/private_execution.js'; import { ProxiedContractDataProviderFactory } from './contract_function_simulator/proxied_contract_data_source.js'; import { PXEDebugUtils } from './debug/pxe_debug_utils.js'; import { enrichPublicSimulationError, enrichSimulationError } from './error_enriching.js'; @@ -618,7 +619,7 @@ export class PXE { const header = await this.anchorBlockDataProvider.getBlockHeader(); - const currentClassId = await readCurrentClassIdFromNode( + const currentClassId = await readCurrentClassId( contractAddress, currentInstance, this.node, @@ -1061,3 +1062,12 @@ export class PXE { return this.jobQueue.end(); } } +function readCurrentClassIdFromNode( + contractAddress: AztecAddress, + currentInstance: ContractInstanceWithAddress, + node: AztecNode, + blockNumber: BlockNumber, + timestamp: bigint, +) { + throw new Error('Function not implemented.'); +} From 455db799f822785f059fb6d2b59f4eee674eab2c Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 14:02:34 +0000 Subject: [PATCH 053/140] help shaking trees --- .../src/contract_function_simulator/oracle/private_execution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts index 92bd13b1d881..f9365938dd20 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.ts @@ -27,7 +27,7 @@ import type { CircuitWitnessGenerationStats } from '@aztec/stdlib/stats'; import { BlockHeader, PrivateCallExecutionResult } from '@aztec/stdlib/tx'; import type { UInt64 } from '@aztec/stdlib/types'; -import { ContractDataProvider } from '../../storage/index.js'; +import { ContractDataProvider } from '../../storage/contract_data_provider/index.js'; import { Oracle } from './oracle.js'; import type { PrivateExecutionOracle } from './private_execution_oracle.js'; From 80708ce03c46c9bf340b87a55752e71318a561df Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 14:13:15 +0000 Subject: [PATCH 054/140] fix weird lint issue --- yarn-project/pxe/src/pxe.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/yarn-project/pxe/src/pxe.ts b/yarn-project/pxe/src/pxe.ts index 125d729a9b24..0201037f88fd 100644 --- a/yarn-project/pxe/src/pxe.ts +++ b/yarn-project/pxe/src/pxe.ts @@ -1,5 +1,4 @@ import type { PrivateEventFilter } from '@aztec/aztec.js/wallet'; -import type { BlockNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { SerialQueue } from '@aztec/foundation/queue'; @@ -1062,12 +1061,3 @@ export class PXE { return this.jobQueue.end(); } } -function readCurrentClassIdFromNode( - contractAddress: AztecAddress, - currentInstance: ContractInstanceWithAddress, - node: AztecNode, - blockNumber: BlockNumber, - timestamp: bigint, -) { - throw new Error('Function not implemented.'); -} From 2fe98d589275a592bb7b2cc35f33b832f13737f1 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 15:33:03 +0000 Subject: [PATCH 055/140] LogService --- .../oracle/utility_execution_oracle.ts | 342 +------------- yarn-project/pxe/src/logs/log_service.test.ts | 424 ++++++++++++++++++ yarn-project/pxe/src/logs/log_service.ts | 350 +++++++++++++++ 3 files changed, 797 insertions(+), 319 deletions(-) create mode 100644 yarn-project/pxe/src/logs/log_service.test.ts create mode 100644 yarn-project/pxe/src/logs/log_service.ts 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 ee8c2c25f418..e1ca615d5040 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 @@ -13,21 +13,14 @@ import { siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import { computeAddressSecret } from '@aztec/stdlib/keys'; -import { - DirectionalAppTaggingSecret, - PendingTaggedLog, - PrivateLogWithTxData, - PublicLog, - PublicLogWithTxData, - TxScopedL2Log, - deriveEcdhSharedSecret, -} from '@aztec/stdlib/logs'; +import { deriveEcdhSharedSecret } from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; import { EventService } from '../../events/event_service.js'; +import { LogService } from '../../logs/log_service.js'; import { NoteService } from '../../notes/note_service.js'; import { ORACLE_VERSION } from '../../oracle_version.js'; import type { @@ -40,7 +33,6 @@ import type { RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; -import { SiloedTag, Tag, WINDOW_HALF_SIZE, getInitialIndexesMap, getPreTagsForTheWindow } from '../../tagging/index.js'; import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; @@ -464,7 +456,16 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra } public async utilityFetchTaggedLogs(pendingTaggedLogArrayBaseSlot: Fr) { - await this.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes); + const logService = new LogService( + this.aztecNode, + this.anchorBlockDataProvider, + this.keyStore, + this.capsuleDataProvider, + this.recipientTaggingDataProvider, + this.addressDataProvider, + ); + + await logService.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes); const noteService = new NoteService(this.noteDataProvider, this.aztecNode, this.anchorBlockDataProvider); await noteService.syncNoteNullifiers(this.contractAddress); @@ -549,12 +550,21 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra await this.capsuleDataProvider.readCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot) ).map(LogRetrievalRequest.fromFields); + const logService = new LogService( + this.aztecNode, + this.anchorBlockDataProvider, + this.keyStore, + this.capsuleDataProvider, + this.recipientTaggingDataProvider, + this.addressDataProvider, + ); + const maybeLogRetrievalResponses = await Promise.all( logRetrievalRequests.map(async request => { // TODO(#14555): remove these internal functions and have node endpoints that do this instead const [publicLog, privateLog] = await Promise.all([ - this.getPublicLogByTag(request.unsiloedTag, request.contractAddress), - this.getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag)), + logService.getPublicLogByTag(request.unsiloedTag, request.contractAddress), + logService.getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag)), ]); if (publicLog !== null) { @@ -654,310 +664,4 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM); return deriveEcdhSharedSecret(addressSecret, ephPk); } - - // TODO(#17775): Replace this implementation of this function with one implementing an approach similar - // to syncSenderTaggingIndexes. Not done yet due to re-prioritization to devex and this doesn't directly affect - // devex. - protected async syncTaggedLogs( - contractAddress: AztecAddress, - pendingTaggedLogArrayBaseSlot: Fr, - scopes?: AztecAddress[], - ) { - const maxBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - - // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles. - // However it is impossible at the moment due to the language not supporting nested slices. - // This nesting is necessary because for a given set of tags we don't - // know how many logs we will get back. Furthermore, these logs are of undetermined - // length, since we don't really know the note they correspond to until we decrypt them. - const recipients = scopes ? scopes : await this.keyStore.getAccounts(); - for (const recipient of recipients) { - // Get all the secrets for the recipient and sender pairs (#9365) - const indexedSecrets = await this.getLastUsedTaggingIndexesForSenders(contractAddress, recipient); - - // We fetch logs for a window of indexes in a range: - // . - // - // We use this window approach because it could happen that a sender might have messed up and inadvertently - // incremented their index without us getting any logs (for example, in case of a revert). If we stopped looking - // for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again. - // Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed - // some logs. For these reasons, we have to look both back and ahead of the stored index. - let secretsAndWindows = indexedSecrets.map(indexedSecret => { - if (indexedSecret.index === undefined) { - return { - secret: indexedSecret.secret, - leftMostIndex: 0, - rightMostIndex: WINDOW_HALF_SIZE, - }; - } else { - return { - secret: indexedSecret.secret, - leftMostIndex: Math.max(0, indexedSecret.index - WINDOW_HALF_SIZE), - rightMostIndex: indexedSecret.index + WINDOW_HALF_SIZE, - }; - } - }); - - // As we iterate we store the largest index we have seen for a given secret to later on store it in the db. - const newLargestIndexMapToStore: { [k: string]: number } = {}; - - // The initial/unmodified indexes of the secrets stored in a key-value map where key is the directional app - // tagging secret. - const initialIndexesMap = getInitialIndexesMap(indexedSecrets); - - while (secretsAndWindows.length > 0) { - const preTagsForTheWholeWindow = getPreTagsForTheWindow(secretsAndWindows); - const tagsForTheWholeWindow = await Promise.all( - preTagsForTheWholeWindow.map(async preTag => { - return SiloedTag.compute(await Tag.compute(preTag), contractAddress); - }), - ); - - // We store the new largest indexes we find in the iteration in the following map to later on construct - // a new set of secrets and windows to fetch logs for. - const newLargestIndexMapForIteration: { [k: string]: number } = {}; - - // Fetch the private logs for the tags and iterate over them - // TODO: The following conversion is unfortunate and we should most likely just type the #getPrivateLogsByTags - // to accept SiloedTag[] instead of Fr[]. That would result in a large change so I didn't do it yet. - const tagsForTheWholeWindowAsFr = tagsForTheWholeWindow.map(tag => tag.value); - const logsByTags = await this.internalGetPrivateLogsByTags(tagsForTheWholeWindowAsFr); - - for (let logIndex = 0; logIndex < logsByTags.length; logIndex++) { - const logsByTag = logsByTags[logIndex]; - if (logsByTag.length > 0) { - // We filter out the logs that are newer than the anchor block number of the tx currently being constructed - const filteredLogsByBlockNumber = logsByTag.filter(l => l.blockNumber <= maxBlockNumber); - - // We store the logs in capsules (to later be obtained in Noir) - await this.storePendingTaggedLogs( - contractAddress, - pendingTaggedLogArrayBaseSlot, - recipient, - filteredLogsByBlockNumber, - ); - - // We retrieve the pre-tag corresponding to the log as I need that to evaluate whether - // a new largest index have been found. - const preTagCorrespondingToLog = preTagsForTheWholeWindow[logIndex]; - const initialIndex = initialIndexesMap[preTagCorrespondingToLog.secret.toString()]; - - if ( - preTagCorrespondingToLog.index >= initialIndex && - (newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] === undefined || - preTagCorrespondingToLog.index >= - newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()]) - ) { - // We have found a new largest index so we store it for later processing (storing it in the db + fetching - // the difference of the window sets of current and the next iteration) - newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] = - preTagCorrespondingToLog.index + 1; - } - } - } - - // Now based on the new largest indexes we found, we will construct a new secrets and windows set to fetch logs - // for. Note that it's very unlikely that a new log from the current window would appear between the iterations - // so we fetch the logs only for the difference of the window sets. - const newSecretsAndWindows = []; - for (const [directionalAppTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration)) { - const maybeIndexedSecret = indexedSecrets.find( - indexedSecret => indexedSecret.secret.toString() === directionalAppTaggingSecret, - ); - if (maybeIndexedSecret) { - newSecretsAndWindows.push({ - secret: maybeIndexedSecret.secret, - // We set the left most index to the new index to avoid fetching the same logs again - leftMostIndex: newIndex, - rightMostIndex: newIndex + WINDOW_HALF_SIZE, - }); - - // We store the new largest index in the map to later store it in the db. - newLargestIndexMapToStore[directionalAppTaggingSecret] = newIndex; - } else { - throw new Error( - `Secret not found for directionalAppTaggingSecret ${directionalAppTaggingSecret}. This is a bug as it should never happen!`, - ); - } - } - - // Now we set the new secrets and windows and proceed to the next iteration. - secretsAndWindows = newSecretsAndWindows; - } - - // At this point we have processed all the logs for the recipient so we store the last used indexes in the db. - // newLargestIndexMapToStore contains "next" indexes to look for (one past the last found), so subtract 1 to get - // last used. - await this.recipientTaggingDataProvider.setLastUsedIndexes( - Object.entries(newLargestIndexMapToStore).map(([directionalAppTaggingSecret, index]) => ({ - secret: DirectionalAppTaggingSecret.fromString(directionalAppTaggingSecret), - index: index - 1, - })), - ); - } - } - - protected async storePendingTaggedLogs( - contractAddress: AztecAddress, - capsuleArrayBaseSlot: Fr, - recipient: AztecAddress, - privateLogs: TxScopedL2Log[], - ) { - // Build all pending tagged logs upfront with their tx effects - const pendingTaggedLogs = await Promise.all( - privateLogs.map(async scopedLog => { - // TODO(#9789): get these effects along with the log - const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); - if (!txEffect) { - throw new Error(`Could not find tx effect for tx hash ${scopedLog.txHash}`); - } - - const pendingTaggedLog = new PendingTaggedLog( - scopedLog.log.fields, - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - recipient, - ); - - return pendingTaggedLog.toFields(); - }), - ); - - return this.capsuleDataProvider.appendToCapsuleArray(contractAddress, capsuleArrayBaseSlot, pendingTaggedLogs); - } - - /** - * Returns the last used tagging indexes along with the directional app tagging secrets for a given recipient and all - * the senders in the address book. - * This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration - * of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment, - * so we're keeping it private for now. - * @param contractAddress - The contract address to silo the secret for - * @param recipient - The address receiving the notes - * @returns A list of directional app tagging secrets along with the last used tagging indexes. If the corresponding - * secret was never used, the index is undefined. - * TODO(#17775): The naming here is broken as the function name does not reflect the return type. Make sure this gets - * fixed when implementing the linked issue. - */ - protected async getLastUsedTaggingIndexesForSenders( - contractAddress: AztecAddress, - recipient: AztecAddress, - ): Promise<{ secret: DirectionalAppTaggingSecret; index: number | undefined }[]> { - const recipientCompleteAddress = await this.getCompleteAddress(recipient); - const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); - - // We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves - // (recipient = us, sender = us) - const senders = [ - ...(await this.recipientTaggingDataProvider.getSenderAddresses()), - ...(await this.keyStore.getAccounts()), - ].filter((address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address))); - const secrets = await Promise.all( - senders.map(contact => { - return DirectionalAppTaggingSecret.compute( - recipientCompleteAddress, - recipientIvsk, - contact, - contractAddress, - recipient, - ); - }), - ); - const indexes = await this.recipientTaggingDataProvider.getLastUsedIndexes(secrets); - if (indexes.length !== secrets.length) { - throw new Error('Indexes and directional app tagging secrets have different lengths'); - } - - return secrets.map((secret, i) => ({ - secret, - index: indexes[i], - })); - } - - // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This - // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. - protected async internalGetPrivateLogsByTags(tags: Fr[]): Promise { - const allLogs = await this.aztecNode.getLogsByTags(tags); - return allLogs.map(logs => logs.filter(log => !log.isFromPublic)); - } - - // TODO(#14555): delete this function and implement this behavior in the node instead - protected async getPublicLogByTag(tag: Fr, contractAddress: AztecAddress): Promise { - const logs = await this.internalGetPublicLogsByTagsFromContract([tag], contractAddress, this.aztecNode); - const logsForTag = logs[0]; - - if (logsForTag.length == 0) { - return null; - } else if (logsForTag.length > 1) { - // TODO(#11627): handle this case - throw new Error( - `Got ${logsForTag.length} logs for tag ${tag} and contract ${contractAddress.toString()}. getPublicLogByTag currently only supports a single log per tag`, - ); - } - - const scopedLog = logsForTag[0]; - - // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so - // we need to make a second call to the node for `getTxEffect`. - // TODO(#9789): bundle this information in the `getLogsByTag` call. - const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); - if (txEffect == undefined) { - throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); - } - - return new PublicLogWithTxData( - scopedLog.log.getEmittedFieldsWithoutTag(), - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - ); - } - - // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This - // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. - protected async internalGetPublicLogsByTagsFromContract( - tags: Fr[], - contractAddress: AztecAddress, - aztecNode: AztecNode, - ): Promise { - const allLogs = await aztecNode.getLogsByTags(tags); - const allPublicLogs = allLogs.map(logs => logs.filter(log => log.isFromPublic)); - return allPublicLogs.map(logs => - logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress)), - ); - } - - // TODO(#14555): delete this function and implement this behavior in the node instead - protected async getPrivateLogByTag(siloedTag: Fr): Promise { - const logs = await this.internalGetPrivateLogsByTags([siloedTag]); - const logsForTag = logs[0]; - - if (logsForTag.length == 0) { - return null; - } else if (logsForTag.length > 1) { - // TODO(#11627): handle this case - throw new Error( - `Got ${logsForTag.length} logs for tag ${siloedTag}. getPrivateLogByTag currently only supports a single log per tag`, - ); - } - - const scopedLog = logsForTag[0]; - - // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so - // we need to make a second call to the node for `getTxEffect`. - // TODO(#9789): bundle this information in the `getLogsByTag` call. - const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); - if (txEffect == undefined) { - throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); - } - - return new PrivateLogWithTxData( - scopedLog.log.getEmittedFieldsWithoutTag(), - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - ); - } } diff --git a/yarn-project/pxe/src/logs/log_service.test.ts b/yarn-project/pxe/src/logs/log_service.test.ts new file mode 100644 index 000000000000..bece0cf00048 --- /dev/null +++ b/yarn-project/pxe/src/logs/log_service.test.ts @@ -0,0 +1,424 @@ +import { BlockNumber } from '@aztec/foundation/branded-types'; +import { timesParallel } from '@aztec/foundation/collection'; +import { type Fq, Fr } from '@aztec/foundation/curves/bn254'; +import { KeyStore } from '@aztec/key-store'; +import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; +import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; +import { CompleteAddress } from '@aztec/stdlib/contract'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; +import { computeAddress, deriveKeys } from '@aztec/stdlib/keys'; +import { DirectionalAppTaggingSecret, PrivateLog, TxScopedL2Log } from '@aztec/stdlib/logs'; +import { BlockHeader, GlobalVariables, TxEffect, TxHash } from '@aztec/stdlib/tx'; + +import { type MockProxy, mock } from 'jest-mock-extended'; + +import { AddressDataProvider, AnchorBlockDataProvider, CapsuleDataProvider } from '../storage/index.js'; +import { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js'; +import { WINDOW_HALF_SIZE } from '../tagging/constants.js'; +import { SiloedTag } from '../tagging/siloed_tag.js'; +import { Tag } from '../tagging/tag.js'; +import { LogService } from './log_service.js'; + +async function computeSiloedTagForIndex( + sender: { completeAddress: CompleteAddress; ivsk: Fq }, + recipient: AztecAddress, + contractAddress: AztecAddress, + index: number, +) { + const secret = await DirectionalAppTaggingSecret.compute( + sender.completeAddress, + sender.ivsk, + recipient, + contractAddress, + recipient, + ); + const tag = await Tag.compute({ secret, index }); + return SiloedTag.compute(tag, contractAddress); +} + +describe('LogService', () => { + let contractAddress: AztecAddress; + let recipient: CompleteAddress; + let keyStore: KeyStore; + let aztecNode: MockProxy; + let recipientTaggingDataProvider: RecipientTaggingDataProvider; + let anchorBlockDataProvider: AnchorBlockDataProvider; + let capsuleDataProvider: CapsuleDataProvider; + let addressDataProvider: AddressDataProvider; + + let logService: LogService; + + describe('sync tagged logs', () => { + const NUM_SENDERS = 10; + + let senders: { completeAddress: CompleteAddress; ivsk: Fq; secretKey: Fr }[]; + + // The block number of the first log to be emitted. + const MIN_BLOCK_NUMBER_OF_A_LOG = BlockNumber(1); + // The block number of the last log to be emitted. + const MAX_BLOCK_NUMBER_OF_A_LOG = BlockNumber(3); + + const setSyncedBlockNumber = (blockNumber: BlockNumber) => { + return anchorBlockDataProvider.setHeader( + BlockHeader.empty({ + globalVariables: GlobalVariables.empty({ blockNumber }), + }), + ); + }; + + async function generateMockLogs(tagIndex: number) { + const logs: { [k: string]: TxScopedL2Log[] } = {}; + + // Add a random note from every address in the address book for our account with index tagIndex + // Compute the tag as sender (knowledge of preaddress and ivsk) + for (const sender of senders) { + const tag = await computeSiloedTagForIndex(sender, recipient.address, contractAddress, tagIndex); + const log = new TxScopedL2Log( + TxHash.random(), + 0, + 0, + MIN_BLOCK_NUMBER_OF_A_LOG, + L2BlockHash.random(), + PrivateLog.random(tag.value), + ); + logs[tag.toString()] = [log]; + } + // Accumulated logs intended for recipient: NUM_SENDERS + + // Add a random note from the first sender in the address book, repeating the tag + // Compute the tag as sender (knowledge of preaddress and ivsk) + const firstSender = senders[0]; + const tag = await computeSiloedTagForIndex(firstSender, recipient.address, contractAddress, tagIndex); + const log = new TxScopedL2Log( + TxHash.random(), + 1, + 0, + BlockNumber.ZERO, + L2BlockHash.random(), + PrivateLog.random(tag.value), + ); + logs[tag.toString()].push(log); + // Accumulated logs intended for recipient: NUM_SENDERS + 1 + + // Add a random note from half the address book for our account with index tagIndex + 1 + // Compute the tag as sender (knowledge of preaddress and ivsk) + for (let i = NUM_SENDERS / 2; i < NUM_SENDERS; i++) { + const sender = senders[i]; + const tag = await computeSiloedTagForIndex(sender, recipient.address, contractAddress, tagIndex + 1); + const blockNumber = BlockNumber(2); + const log = new TxScopedL2Log( + TxHash.random(), + 0, + 0, + blockNumber, + L2BlockHash.random(), + PrivateLog.random(tag.value), + ); + logs[tag.toString()] = [log]; + } + // Accumulated logs intended for recipient: NUM_SENDERS + 1 + NUM_SENDERS / 2 + + // Add a random note from every address in the address book for a random recipient with index tagIndex + // Compute the tag as sender (knowledge of preaddress and ivsk) + for (const sender of senders) { + const keys = await deriveKeys(Fr.random()); + const partialAddress = Fr.random(); + const randomRecipient = await computeAddress(keys.publicKeys, partialAddress); + const tag = await computeSiloedTagForIndex(sender, randomRecipient, contractAddress, tagIndex); + const log = new TxScopedL2Log( + TxHash.random(), + 0, + 0, + MAX_BLOCK_NUMBER_OF_A_LOG, + L2BlockHash.random(), + PrivateLog.random(tag.value), + ); + logs[tag.toString()] = [log]; + } + // Accumulated logs intended for recipient: NUM_SENDERS + 1 + NUM_SENDERS / 2 + + // Set up the getPrivateLogsByTags mock + aztecNode.getLogsByTags.mockImplementation(tags => { + return Promise.resolve(tags.map(tag => logs[tag.toString()] ?? [])); + }); + } + + // Set to a random value in this test we don't care about Noir loading the logs from the capsule array. + const PENDING_TAGGED_LOG_ARRAY_BASE_SLOT = Fr.random(); + + beforeEach(async () => { + // Set up contract address + contractAddress = await AztecAddress.random(); + + aztecNode = mock(); + + const store = await openTmpStore('test'); + + keyStore = new KeyStore(store); + recipientTaggingDataProvider = new RecipientTaggingDataProvider(store); + anchorBlockDataProvider = new AnchorBlockDataProvider(store); + capsuleDataProvider = new CapsuleDataProvider(store); + addressDataProvider = new AddressDataProvider(store); + await setSyncedBlockNumber(MAX_BLOCK_NUMBER_OF_A_LOG); + + // Set up recipient account + recipient = await keyStore.addAccount(new Fr(69), Fr.random()); + await addressDataProvider.addCompleteAddress(recipient); + // Set up the address book + senders = await timesParallel(NUM_SENDERS, async index => { + const keys = await deriveKeys(new Fr(index)); + const partialAddress = Fr.random(); + const address = await computeAddress(keys.publicKeys, partialAddress); + const completeAddress = await CompleteAddress.create(address, keys.publicKeys, partialAddress); + return { completeAddress, ivsk: keys.masterIncomingViewingSecretKey, secretKey: new Fr(index) }; + }); + for (const sender of senders) { + await recipientTaggingDataProvider.addSenderAddress(sender.completeAddress.address); + } + aztecNode.getLogsByTags.mockReset(); + aztecNode.getTxEffect.mockResolvedValue({ + ...randomDataInBlock(await TxEffect.random({ numNullifiers: 1 })), + txIndexInBlock: 0, + }); + + logService = new LogService( + aztecNode, + anchorBlockDataProvider, + keyStore, + capsuleDataProvider, + recipientTaggingDataProvider, + addressDataProvider, + ); + }); + + it('should sync tagged logs', async () => { + const tagIndex = 0; + await generateMockLogs(tagIndex); + await logService.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // We expect to have all logs intended for the recipient synced (and hence stored in the capsule for later + // processing), one per sender + 1 with a duplicated tag for the first sender + half of the logs for the second + // index + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1 + NUM_SENDERS / 2); + + // Recompute the secrets (as recipient) to ensure indexes are updated + const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); + const secrets = await Promise.all( + senders.map(sender => + DirectionalAppTaggingSecret.compute( + recipient, + ivsk, + sender.completeAddress.address, + contractAddress, + recipient.address, + ), + ), + ); + + // First sender should have 2 logs, but keep index 0 since they were built using the same tag + // Next 4 senders should also have index 0 = offset + 0 + // Last 5 senders should have index 1 = offset + 1 + const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); + + expect(indexes).toHaveLength(NUM_SENDERS); + expect(indexes).toEqual([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]); + + // We should have called the node 2 times: + // 2 times: first time during initial request, second time after pushing the edge of the window once + expect(aztecNode.getLogsByTags.mock.calls.length).toBe(2); + }); + + it('should sync tagged logs with a sender index offset', async () => { + const tagIndex = 5; + await generateMockLogs(tagIndex); + await logService.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // We expect to have all logs intended for the recipient, one per sender + 1 with a duplicated tag for the first + // one + half of the logs for the second index + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1 + NUM_SENDERS / 2); + + // Recompute the secrets (as recipient) to ensure indexes are updated + const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); + const secrets = await Promise.all( + senders.map(sender => + DirectionalAppTaggingSecret.compute( + recipient, + ivsk, + sender.completeAddress.address, + contractAddress, + recipient.address, + ), + ), + ); + + // First sender should have 2 logs, but keep index 5 since they were built using the same tag + // Next 4 senders should also have index 5 = offset + // Last 5 senders should have index 6 = offset + 1 + const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); + + expect(indexes).toHaveLength(NUM_SENDERS); + expect(indexes).toEqual([5, 5, 5, 5, 5, 6, 6, 6, 6, 6]); + + // We should have called the node 2 times: + // 2 times: first time during initial request, second time after pushing the edge of the window once + expect(aztecNode.getLogsByTags.mock.calls.length).toBe(2); + }); + + it("should sync tagged logs for which indexes are not updated if they're inside the window", async () => { + const tagIndex = 1; + await generateMockLogs(tagIndex); + + // Recompute the secrets (as recipient) to update indexes + const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); + const secrets = await Promise.all( + senders.map(sender => + DirectionalAppTaggingSecret.compute( + recipient, + ivsk, + sender.completeAddress.address, + contractAddress, + recipient.address, + ), + ), + ); + + // Set last used indexes to 1 (so next scan starts at 2) + await recipientTaggingDataProvider.setLastUsedIndexes(secrets.map(secret => ({ secret, index: 1 }))); + + await logService.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // Even if our index as recipient is higher than what the sender sent, we should be able to find the logs + // since the window starts at Math.max(0, 2 - window_size) = 0 + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1 + NUM_SENDERS / 2); + + // First sender should have 2 logs, but keep index 1 since they were built using the same tag + // Next 4 senders should also have index 1 = tagIndex + // Last 5 senders should have index 2 = tagIndex + 1 + const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); + + expect(indexes).toHaveLength(NUM_SENDERS); + expect(indexes).toEqual([1, 1, 1, 1, 1, 2, 2, 2, 2, 2]); + + // We should have called the node 2 times: + // first time during initial request, second time after pushing the edge of the window once + expect(aztecNode.getLogsByTags.mock.calls.length).toBe(2); + }); + + it("should not sync tagged logs for which indexes are not updated if they're outside the window", async () => { + const tagIndex = 0; + await generateMockLogs(tagIndex); + + // Recompute the secrets (as recipient) to update indexes + const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); + const secrets = await Promise.all( + senders.map(sender => + DirectionalAppTaggingSecret.compute( + recipient, + ivsk, + sender.completeAddress.address, + contractAddress, + recipient.address, + ), + ), + ); + + // We set the last used indexes to WINDOW_HALF_SIZE so that next scan starts at WINDOW_HALF_SIZE + 1, + // which is outside the window, and for this reason no updates should be triggered. + const index = WINDOW_HALF_SIZE + 1; + await recipientTaggingDataProvider.setLastUsedIndexes(secrets.map(secret => ({ secret, index }))); + + await logService.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // Only half of the logs should be synced since we start from index 1 = (11 - window_size), the other half should + // be skipped + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS / 2); + + // Indexes should remain where we set them (window_size) + const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); + + expect(indexes).toHaveLength(NUM_SENDERS); + expect(indexes).toEqual([index, index, index, index, index, index, index, index, index, index]); + + // We should have called the node once and that is only for the first window + expect(aztecNode.getLogsByTags.mock.calls.length).toBe(1); + }); + + it('should sync tagged logs from scratch after a DB wipe', async () => { + const tagIndex = 0; + await generateMockLogs(tagIndex); + + // Recompute the secrets (as recipient) to update indexes + const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); + const secrets = await Promise.all( + senders.map(sender => + DirectionalAppTaggingSecret.compute( + recipient, + ivsk, + sender.completeAddress.address, + contractAddress, + recipient.address, + ), + ), + ); + + await recipientTaggingDataProvider.setLastUsedIndexes( + secrets.map(secret => ({ secret, index: WINDOW_HALF_SIZE + 2 })), + ); + + await logService.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // No logs should be synced (and hence no capsules stored) since we start from index 2 = 12 - window_size + await expectPendingTaggedLogArrayLengthToBe(contractAddress, 0); + + // Since no logs were synced, window edge hash not been pushed and for this reason we should have called + // the node only once for the initial window + expect(aztecNode.getLogsByTags.mock.calls.length).toBe(1); + + aztecNode.getLogsByTags.mockClear(); + + // Wipe the database + await recipientTaggingDataProvider.resetNoteSyncData(); + + await logService.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // First sender should have 2 logs, but keep index 0 since they were built using the same tag + // Next 4 senders should also have index 0 = offset + // Last 5 senders should have index 1 = offset + 1 + const indexes = await recipientTaggingDataProvider.getLastUsedIndexes(secrets); + + expect(indexes).toHaveLength(NUM_SENDERS); + expect(indexes).toEqual([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]); + + // We should have called the node 2 times: + // first time during initial request, second time after pushing the edge of the window once + expect(aztecNode.getLogsByTags.mock.calls.length).toBe(2); + }); + + it('should not sync tagged logs with a blockNumber larger than the block number to which PXE is synced', async () => { + // We set the block number to which PXE is synced to a block number in which only the first batch of logs was + // emitted and then we check that we receive logs only from this batch. + await setSyncedBlockNumber(MIN_BLOCK_NUMBER_OF_A_LOG); + + const tagIndex = 0; + await generateMockLogs(tagIndex); + await logService.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // Only NUM_SENDERS + 1 logs should be synched, since the rest have blockNumber > 1 + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1); + }); + + const expectPendingTaggedLogArrayLengthToBe = async (contractAddress: AztecAddress, expectedLength: number) => { + // Capsule array length is stored in the array base slot. + const capsule = await capsuleDataProvider.loadCapsule(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + if (expectedLength === 0 && capsule === null) { + // If expected length is 0 we are fine with the capsule not existing since the array might not have been + // initialized yet. + return; + } + expect(capsule).toBeDefined(); + expect(capsule!.length).toBe(1); + expect(capsule![0].toNumber()).toBe(expectedLength); + }; + }); +}); diff --git a/yarn-project/pxe/src/logs/log_service.ts b/yarn-project/pxe/src/logs/log_service.ts new file mode 100644 index 000000000000..86e626417d98 --- /dev/null +++ b/yarn-project/pxe/src/logs/log_service.ts @@ -0,0 +1,350 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; +import type { KeyStore } from '@aztec/key-store'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import type { CompleteAddress } from '@aztec/stdlib/contract'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; +import { + DirectionalAppTaggingSecret, + PendingTaggedLog, + PrivateLogWithTxData, + PublicLog, + PublicLogWithTxData, + TxScopedL2Log, +} from '@aztec/stdlib/logs'; + +import type { + AddressDataProvider, + AnchorBlockDataProvider, + CapsuleDataProvider, + RecipientTaggingDataProvider, +} from '../storage/index.js'; +import { WINDOW_HALF_SIZE } from '../tagging/constants.js'; +import { SiloedTag } from '../tagging/siloed_tag.js'; +import { Tag } from '../tagging/tag.js'; +import { getInitialIndexesMap, getPreTagsForTheWindow } from '../tagging/utils.js'; + +// TODO: good names wanted +export class LogService { + constructor( + private readonly aztecNode: AztecNode, + private readonly anchorBlockDataProvider: AnchorBlockDataProvider, + private readonly keyStore: KeyStore, + private readonly capsuleDataProvider: CapsuleDataProvider, + private readonly recipientTaggingDataProvider: RecipientTaggingDataProvider, + private readonly addressDataProvider: AddressDataProvider, + ) {} + + // TODO(#14555): delete this function and implement this behavior in the node instead + public async getPublicLogByTag(tag: Fr, contractAddress: AztecAddress): Promise { + const logs = await this.#getPublicLogsByTagsFromContract([tag], contractAddress); + const logsForTag = logs[0]; + + if (logsForTag.length == 0) { + return null; + } else if (logsForTag.length > 1) { + // TODO(#11627): handle this case + throw new Error( + `Got ${logsForTag.length} logs for tag ${tag} and contract ${contractAddress.toString()}. getPublicLogByTag currently only supports a single log per tag`, + ); + } + + const scopedLog = logsForTag[0]; + + // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so + // we need to make a second call to the node for `getTxEffect`. + // TODO(#9789): bundle this information in the `getLogsByTag` call. + const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); + if (txEffect == undefined) { + throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); + } + + return new PublicLogWithTxData( + scopedLog.log.getEmittedFieldsWithoutTag(), + scopedLog.txHash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + ); + } + + // TODO(#14555): delete this function and implement this behavior in the node instead + public async getPrivateLogByTag(siloedTag: Fr): Promise { + const logs = await this.#getPrivateLogsByTags([siloedTag]); + const logsForTag = logs[0]; + + if (logsForTag.length == 0) { + return null; + } else if (logsForTag.length > 1) { + // TODO(#11627): handle this case + throw new Error( + `Got ${logsForTag.length} logs for tag ${siloedTag}. getPrivateLogByTag currently only supports a single log per tag`, + ); + } + + const scopedLog = logsForTag[0]; + + // getLogsByTag doesn't have all of the information that we need (notably note hashes and the first nullifier), so + // we need to make a second call to the node for `getTxEffect`. + // TODO(#9789): bundle this information in the `getLogsByTag` call. + const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); + if (txEffect == undefined) { + throw new Error(`Unexpected: failed to retrieve tx effects for tx ${scopedLog.txHash} which is known to exist`); + } + + return new PrivateLogWithTxData( + scopedLog.log.getEmittedFieldsWithoutTag(), + scopedLog.txHash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + ); + } + + // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This + // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. + async #getPublicLogsByTagsFromContract(tags: Fr[], contractAddress: AztecAddress): Promise { + const allLogs = await this.aztecNode.getLogsByTags(tags); + const allPublicLogs = allLogs.map(logs => logs.filter(log => log.isFromPublic)); + return allPublicLogs.map(logs => + logs.filter(log => (log.log as PublicLog).contractAddress.equals(contractAddress)), + ); + } + + // TODO(#12656): Make this a public function on the AztecNode interface and remove the original getLogsByTags. This + // was not done yet as we were unsure about the API and we didn't want to introduce a breaking change. + async #getPrivateLogsByTags(tags: Fr[]): Promise { + const allLogs = await this.aztecNode.getLogsByTags(tags); + return allLogs.map(logs => logs.filter(log => !log.isFromPublic)); + } + + // TODO(#17775): Replace this implementation of this function with one implementing an approach similar + // to syncSenderTaggingIndexes. Not done yet due to re-prioritization to devex and this doesn't directly affect + // devex. + public async syncTaggedLogs( + contractAddress: AztecAddress, + pendingTaggedLogArrayBaseSlot: Fr, + scopes?: AztecAddress[], + ) { + const maxBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + + // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles. + // However it is impossible at the moment due to the language not supporting nested slices. + // This nesting is necessary because for a given set of tags we don't + // know how many logs we will get back. Furthermore, these logs are of undetermined + // length, since we don't really know the note they correspond to until we decrypt them. + const recipients = scopes ? scopes : await this.keyStore.getAccounts(); + for (const recipient of recipients) { + // Get all the secrets for the recipient and sender pairs (#9365) + const indexedSecrets = await this.getLastUsedTaggingIndexesForSenders(contractAddress, recipient); + + // We fetch logs for a window of indexes in a range: + // . + // + // We use this window approach because it could happen that a sender might have messed up and inadvertently + // incremented their index without us getting any logs (for example, in case of a revert). If we stopped looking + // for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again. + // Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed + // some logs. For these reasons, we have to look both back and ahead of the stored index. + let secretsAndWindows = indexedSecrets.map(indexedSecret => { + if (indexedSecret.index === undefined) { + return { + secret: indexedSecret.secret, + leftMostIndex: 0, + rightMostIndex: WINDOW_HALF_SIZE, + }; + } else { + return { + secret: indexedSecret.secret, + leftMostIndex: Math.max(0, indexedSecret.index - WINDOW_HALF_SIZE), + rightMostIndex: indexedSecret.index + WINDOW_HALF_SIZE, + }; + } + }); + + // As we iterate we store the largest index we have seen for a given secret to later on store it in the db. + const newLargestIndexMapToStore: { [k: string]: number } = {}; + + // The initial/unmodified indexes of the secrets stored in a key-value map where key is the directional app + // tagging secret. + const initialIndexesMap = getInitialIndexesMap(indexedSecrets); + + while (secretsAndWindows.length > 0) { + const preTagsForTheWholeWindow = getPreTagsForTheWindow(secretsAndWindows); + const tagsForTheWholeWindow = await Promise.all( + preTagsForTheWholeWindow.map(async preTag => { + return SiloedTag.compute(await Tag.compute(preTag), contractAddress); + }), + ); + + // We store the new largest indexes we find in the iteration in the following map to later on construct + // a new set of secrets and windows to fetch logs for. + const newLargestIndexMapForIteration: { [k: string]: number } = {}; + + // Fetch the private logs for the tags and iterate over them + // TODO: The following conversion is unfortunate and we should most likely just type the #getPrivateLogsByTags + // to accept SiloedTag[] instead of Fr[]. That would result in a large change so I didn't do it yet. + const tagsForTheWholeWindowAsFr = tagsForTheWholeWindow.map(tag => tag.value); + const logsByTags = await this.#getPrivateLogsByTags(tagsForTheWholeWindowAsFr); + + for (let logIndex = 0; logIndex < logsByTags.length; logIndex++) { + const logsByTag = logsByTags[logIndex]; + if (logsByTag.length > 0) { + // We filter out the logs that are newer than the anchor block number of the tx currently being constructed + const filteredLogsByBlockNumber = logsByTag.filter(l => l.blockNumber <= maxBlockNumber); + + // We store the logs in capsules (to later be obtained in Noir) + await this.#storePendingTaggedLogs( + contractAddress, + pendingTaggedLogArrayBaseSlot, + recipient, + filteredLogsByBlockNumber, + ); + + // We retrieve the pre-tag corresponding to the log as I need that to evaluate whether + // a new largest index have been found. + const preTagCorrespondingToLog = preTagsForTheWholeWindow[logIndex]; + const initialIndex = initialIndexesMap[preTagCorrespondingToLog.secret.toString()]; + + if ( + preTagCorrespondingToLog.index >= initialIndex && + (newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] === undefined || + preTagCorrespondingToLog.index >= + newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()]) + ) { + // We have found a new largest index so we store it for later processing (storing it in the db + fetching + // the difference of the window sets of current and the next iteration) + newLargestIndexMapForIteration[preTagCorrespondingToLog.secret.toString()] = + preTagCorrespondingToLog.index + 1; + } + } + } + + // Now based on the new largest indexes we found, we will construct a new secrets and windows set to fetch logs + // for. Note that it's very unlikely that a new log from the current window would appear between the iterations + // so we fetch the logs only for the difference of the window sets. + const newSecretsAndWindows = []; + for (const [directionalAppTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration)) { + const maybeIndexedSecret = indexedSecrets.find( + indexedSecret => indexedSecret.secret.toString() === directionalAppTaggingSecret, + ); + if (maybeIndexedSecret) { + newSecretsAndWindows.push({ + secret: maybeIndexedSecret.secret, + // We set the left most index to the new index to avoid fetching the same logs again + leftMostIndex: newIndex, + rightMostIndex: newIndex + WINDOW_HALF_SIZE, + }); + + // We store the new largest index in the map to later store it in the db. + newLargestIndexMapToStore[directionalAppTaggingSecret] = newIndex; + } else { + throw new Error( + `Secret not found for directionalAppTaggingSecret ${directionalAppTaggingSecret}. This is a bug as it should never happen!`, + ); + } + } + + // Now we set the new secrets and windows and proceed to the next iteration. + secretsAndWindows = newSecretsAndWindows; + } + + // At this point we have processed all the logs for the recipient so we store the last used indexes in the db. + // newLargestIndexMapToStore contains "next" indexes to look for (one past the last found), so subtract 1 to get + // last used. + await this.recipientTaggingDataProvider.setLastUsedIndexes( + Object.entries(newLargestIndexMapToStore).map(([directionalAppTaggingSecret, index]) => ({ + secret: DirectionalAppTaggingSecret.fromString(directionalAppTaggingSecret), + index: index - 1, + })), + ); + } + } + + /** + * Returns the last used tagging indexes along with the directional app tagging secrets for a given recipient and all + * the senders in the address book. + * This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration + * of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment, + * so we're keeping it private for now. + * @param contractAddress - The contract address to silo the secret for + * @param recipient - The address receiving the notes + * @returns A list of directional app tagging secrets along with the last used tagging indexes. If the corresponding + * secret was never used, the index is undefined. + * TODO(#17775): The naming here is broken as the function name does not reflect the return type. Make sure this gets + * fixed when implementing the linked issue. + */ + protected async getLastUsedTaggingIndexesForSenders( + contractAddress: AztecAddress, + recipient: AztecAddress, + ): Promise<{ secret: DirectionalAppTaggingSecret; index: number | undefined }[]> { + const recipientCompleteAddress = await this.#getCompleteAddress(recipient); + const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); + + // We implicitly add all PXE accounts as senders, this helps us decrypt tags on notes that we send to ourselves + // (recipient = us, sender = us) + const senders = [ + ...(await this.recipientTaggingDataProvider.getSenderAddresses()), + ...(await this.keyStore.getAccounts()), + ].filter((address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address))); + const secrets = await Promise.all( + senders.map(contact => { + return DirectionalAppTaggingSecret.compute( + recipientCompleteAddress, + recipientIvsk, + contact, + contractAddress, + recipient, + ); + }), + ); + const indexes = await this.recipientTaggingDataProvider.getLastUsedIndexes(secrets); + if (indexes.length !== secrets.length) { + throw new Error('Indexes and directional app tagging secrets have different lengths'); + } + + return secrets.map((secret, i) => ({ + secret, + index: indexes[i], + })); + } + + async #storePendingTaggedLogs( + contractAddress: AztecAddress, + capsuleArrayBaseSlot: Fr, + recipient: AztecAddress, + privateLogs: TxScopedL2Log[], + ) { + // Build all pending tagged logs upfront with their tx effects + const pendingTaggedLogs = await Promise.all( + privateLogs.map(async scopedLog => { + // TODO(#9789): get these effects along with the log + const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); + if (!txEffect) { + throw new Error(`Could not find tx effect for tx hash ${scopedLog.txHash}`); + } + + const pendingTaggedLog = new PendingTaggedLog( + scopedLog.log.fields, + scopedLog.txHash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + recipient, + ); + + return pendingTaggedLog.toFields(); + }), + ); + + // TODO: This looks like it could belong more at the oracle interface level + return this.capsuleDataProvider.appendToCapsuleArray(contractAddress, capsuleArrayBaseSlot, pendingTaggedLogs); + } + + async #getCompleteAddress(account: AztecAddress): Promise { + const completeAddress = await this.addressDataProvider.getCompleteAddress(account); + if (!completeAddress) { + throw new Error( + `No public key registered for address ${account}. + Register it by calling pxe.addAccount(...).\nSee docs for context: https://docs.aztec.network/developers/resources/debugging/aztecnr-errors#simulation-error-no-public-key-registered-for-address-0x0-register-it-by-calling-pxeregisterrecipient-or-pxeregisteraccount`, + ); + } + return completeAddress; + } +} From 0b97781367d470030ecc69214c4a9ee4f50cb4b5 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 16:00:23 +0000 Subject: [PATCH 056/140] bulkRetrieveLogs --- .../oracle/utility_execution.test.ts | 84 +--------- .../oracle/utility_execution_oracle.ts | 44 +----- yarn-project/pxe/src/logs/log_service.test.ts | 145 +++++++++++++++++- yarn-project/pxe/src/logs/log_service.ts | 39 +++++ 4 files changed, 185 insertions(+), 127 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 09f33b952269..480b418d425f 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 @@ -7,11 +7,9 @@ import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { L2BlockHash } from '@aztec/stdlib/block'; import { CompleteAddress, type ContractInstanceWithAddress } from '@aztec/stdlib/contract'; -import { siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; import { Note, NoteDao } from '@aztec/stdlib/note'; -import { BlockHeader, GlobalVariables, TxHash, randomIndexedTxEffect } from '@aztec/stdlib/tx'; +import { BlockHeader, GlobalVariables, TxHash } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; import type { _MockProxy } from 'jest-mock-extended/lib/Mock.js'; @@ -27,7 +25,6 @@ import { SenderTaggingDataProvider, } from '../../storage/index.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; -import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; describe('Utility Execution test suite', () => { @@ -199,85 +196,6 @@ describe('Utility Execution test suite', () => { ); }); - describe('utilityBulkRetrieveLogs', () => { - const unsiloedTag = Fr.random(); - const REQUEST_SLOT = Fr.random(); - const RESPONSE_SLOT = Fr.random(); - - beforeEach(() => { - aztecNode.getLogsByTags.mockReset(); - aztecNode.getTxEffect.mockReset(); - }); - - it('returns no logs if none are found', async () => { - aztecNode.getLogsByTags.mockResolvedValue([[]]); - - const request = new LogRetrievalRequest(contractAddress, unsiloedTag); - - await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); - await utilityExecutionOracle.utilityBulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT); - - expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); - - const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); - expect(responses.length).toEqual(1); - - // Check Option::none - expect(responses[0][0]).toEqual(new Fr(0)); // TODO: deserialize into option and check properly - }); - - it('returns a public log if one is found', async () => { - const scopedLog = await TxScopedL2Log.random(true); - (scopedLog.log as PublicLog).contractAddress = contractAddress; - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - - aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => - txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), - ); - - const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); - - await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); - await utilityExecutionOracle.utilityBulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT); - - expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); - - const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); - expect(responses.length).toEqual(1); - - // Check Option::some - expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly - }); - - it('returns a private log if one is found', async () => { - const scopedLog = await TxScopedL2Log.random(false); - scopedLog.log.fields[0] = await siloPrivateLog(contractAddress, Fr.random()); - - aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); - const indexedTxEffect = await randomIndexedTxEffect(); - aztecNode.getTxEffect.mockResolvedValue(indexedTxEffect); - - aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => - txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), - ); - - const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); - - await capsuleDataProvider.setCapsuleArray(contractAddress, REQUEST_SLOT, [request.toFields()]); - await utilityExecutionOracle.utilityBulkRetrieveLogs(contractAddress, REQUEST_SLOT, RESPONSE_SLOT); - - expect((await capsuleDataProvider.readCapsuleArray(contractAddress, REQUEST_SLOT)).length).toEqual(0); - - const responses = await capsuleDataProvider.readCapsuleArray(contractAddress, RESPONSE_SLOT); - expect(responses.length).toEqual(1); - - // Check Option::some - expect(responses[0][0]).toEqual(new Fr(1)); // TODO: deserialize into option and check properly - }); - }); - describe('Respects synced block number', () => { let nullifier: Fr; let leafSlot: Fr; 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 e1ca615d5040..d1ced4b92c72 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 @@ -9,7 +9,7 @@ import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter, L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; -import { siloNullifier, siloPrivateLog } from '@aztec/stdlib/hash'; +import { siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import { computeAddressSecret } from '@aztec/stdlib/keys'; @@ -536,14 +536,6 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra throw new Error(`Got a note validation request from ${contractAddress}, expected ${this.contractAddress}`); } - await this.bulkRetrieveLogs(contractAddress, logRetrievalRequestsArrayBaseSlot, logRetrievalResponsesArrayBaseSlot); - } - - protected async bulkRetrieveLogs( - contractAddress: AztecAddress, - logRetrievalRequestsArrayBaseSlot: Fr, - logRetrievalResponsesArrayBaseSlot: Fr, - ) { // We read all log retrieval requests and process them all concurrently. This makes the process much faster as we // don't need to wait for the network round-trip. const logRetrievalRequests = ( @@ -559,39 +551,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra this.addressDataProvider, ); - const maybeLogRetrievalResponses = await Promise.all( - logRetrievalRequests.map(async request => { - // TODO(#14555): remove these internal functions and have node endpoints that do this instead - const [publicLog, privateLog] = await Promise.all([ - logService.getPublicLogByTag(request.unsiloedTag, request.contractAddress), - logService.getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag)), - ]); - - if (publicLog !== null) { - if (privateLog !== null) { - throw new Error( - `Found both a public and private log when searching for tag ${request.unsiloedTag} from contract ${request.contractAddress}`, - ); - } - - return new LogRetrievalResponse( - publicLog.logPayload, - publicLog.txHash, - publicLog.uniqueNoteHashesInTx, - publicLog.firstNullifierInTx, - ); - } else if (privateLog !== null) { - return new LogRetrievalResponse( - privateLog.logPayload, - privateLog.txHash, - privateLog.uniqueNoteHashesInTx, - privateLog.firstNullifierInTx, - ); - } else { - return null; - } - }), - ); + const maybeLogRetrievalResponses = await logService.bulkRetrieveLogs(logRetrievalRequests); // Requests are cleared once we're done. await this.capsuleDataProvider.setCapsuleArray(contractAddress, logRetrievalRequestsArrayBaseSlot, []); diff --git a/yarn-project/pxe/src/logs/log_service.test.ts b/yarn-project/pxe/src/logs/log_service.test.ts index bece0cf00048..74a573e5c923 100644 --- a/yarn-project/pxe/src/logs/log_service.test.ts +++ b/yarn-project/pxe/src/logs/log_service.test.ts @@ -1,18 +1,21 @@ import { BlockNumber } from '@aztec/foundation/branded-types'; import { timesParallel } from '@aztec/foundation/collection'; +import { randomInt } from '@aztec/foundation/crypto/random'; import { type Fq, Fr } from '@aztec/foundation/curves/bn254'; import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { L2BlockHash, randomDataInBlock } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; +import { siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { computeAddress, deriveKeys } from '@aztec/stdlib/keys'; -import { DirectionalAppTaggingSecret, PrivateLog, TxScopedL2Log } from '@aztec/stdlib/logs'; -import { BlockHeader, GlobalVariables, TxEffect, TxHash } from '@aztec/stdlib/tx'; +import { DirectionalAppTaggingSecret, PrivateLog, PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; +import { BlockHeader, GlobalVariables, TxEffect, TxHash, randomIndexedTxEffect } from '@aztec/stdlib/tx'; import { type MockProxy, mock } from 'jest-mock-extended'; +import { LogRetrievalRequest } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; import { AddressDataProvider, AnchorBlockDataProvider, CapsuleDataProvider } from '../storage/index.js'; import { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js'; import { WINDOW_HALF_SIZE } from '../tagging/constants.js'; @@ -421,4 +424,142 @@ describe('LogService', () => { expect(capsule![0].toNumber()).toBe(expectedLength); }; }); + + describe('bulkRetrieveLogs', () => { + const unsiloedTag = Fr.random(); + + beforeEach(() => { + aztecNode.getLogsByTags.mockReset(); + aztecNode.getTxEffect.mockReset(); + }); + + it('returns no logs if none are found', async () => { + aztecNode.getLogsByTags.mockResolvedValue([[]]); + const request = new LogRetrievalRequest(contractAddress, unsiloedTag); + const responses = await logService.bulkRetrieveLogs([request]); + expect(responses.length).toEqual(1); + expect(responses[0]).toBeNull(); + }); + + it('returns a public log if one is found', async () => { + const scopedLog = await TxScopedL2Log.random(true); + (scopedLog.log as PublicLog).contractAddress = contractAddress; + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + const indexedTxEffect = await randomIndexedTxEffect(); + + aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => + txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), + ); + + const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); + + const responses = await logService.bulkRetrieveLogs([request]); + + expect(responses.length).toEqual(1); + expect(responses[0]).not.toBeNull(); + }); + + it('returns a private log if one is found', async () => { + const scopedLog = await TxScopedL2Log.random(false); + scopedLog.log.fields[0] = await siloPrivateLog(contractAddress, Fr.random()); + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + const indexedTxEffect = await randomIndexedTxEffect(); + aztecNode.getTxEffect.mockResolvedValue(indexedTxEffect); + + aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => + txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), + ); + + const request = new LogRetrievalRequest(contractAddress, scopedLog.log.fields[0]); + + const responses = await logService.bulkRetrieveLogs([request]); + + expect(responses.length).toEqual(1); + expect(responses[0]).not.toBeNull(); + }); + }); + + describe('getPublicLogByTag', () => { + const tag = Fr.random(); + + beforeEach(() => { + aztecNode.getLogsByTags.mockReset(); + aztecNode.getTxEffect.mockReset(); + }); + + it('returns null if no logs found for tag', async () => { + aztecNode.getLogsByTags.mockResolvedValue([[]]); + + const result = await logService.getPublicLogByTag(tag, contractAddress); + expect(result).toBeNull(); + }); + + it('returns log data when single log found', async () => { + const scopedLog = await TxScopedL2Log.random(true); + const logContractAddress = (scopedLog.log as PublicLog).contractAddress; + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + const indexedTxEffect = await randomIndexedTxEffect(); + aztecNode.getTxEffect.mockImplementation((txHash: TxHash) => + txHash.equals(scopedLog.txHash) ? Promise.resolve(indexedTxEffect) : Promise.resolve(undefined), + ); + + const result = (await logService.getPublicLogByTag(tag, logContractAddress))!; + + expect(result.logPayload).toEqual(scopedLog.log.getEmittedFieldsWithoutTag()); + expect(result.uniqueNoteHashesInTx).toEqual(indexedTxEffect.data.noteHashes); + expect(result.txHash).toEqual(scopedLog.txHash); + expect(result.firstNullifierInTx).toEqual(indexedTxEffect.data.nullifiers[0]); + + expect(aztecNode.getLogsByTags).toHaveBeenCalledWith([tag]); + expect(aztecNode.getTxEffect).toHaveBeenCalledWith(scopedLog.txHash); + }); + + it('throws if multiple logs found for tag', async () => { + const scopedLog = await TxScopedL2Log.random(true); + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog, scopedLog]]); + const logContractAddress = (scopedLog.log as PublicLog).contractAddress; + + await expect(logService.getPublicLogByTag(tag, logContractAddress)).rejects.toThrow(/Got 2 logs for tag/); + }); + + it('throws if tx effect not found', async () => { + const scopedLog = await TxScopedL2Log.random(true); + aztecNode.getLogsByTags.mockResolvedValue([[scopedLog]]); + aztecNode.getTxEffect.mockResolvedValue(undefined); + const logContractAddress = (scopedLog.log as PublicLog).contractAddress; + + await expect(logService.getPublicLogByTag(tag, logContractAddress)).rejects.toThrow( + /failed to retrieve tx effects/, + ); + }); + + it('returns log fields that are actually emitted', async () => { + const logContractAddress = await AztecAddress.random(); + const logPlaintext = [Fr.random()]; + const logContent = [tag, ...logPlaintext]; + + const log = PublicLog.from({ + contractAddress: logContractAddress, + fields: logContent, + }); + const scopedLogWithPadding = new TxScopedL2Log( + TxHash.random(), + randomInt(100), + randomInt(100), + BlockNumber(randomInt(100)), + L2BlockHash.random(), + log, + ); + + aztecNode.getLogsByTags.mockResolvedValue([[scopedLogWithPadding]]); + aztecNode.getTxEffect.mockResolvedValue(await randomIndexedTxEffect()); + + const result = await logService.getPublicLogByTag(tag, logContractAddress); + + expect(result?.logPayload).toEqual(logPlaintext); + }); + }); }); diff --git a/yarn-project/pxe/src/logs/log_service.ts b/yarn-project/pxe/src/logs/log_service.ts index 86e626417d98..67f7bba87b8b 100644 --- a/yarn-project/pxe/src/logs/log_service.ts +++ b/yarn-project/pxe/src/logs/log_service.ts @@ -2,6 +2,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { KeyStore } from '@aztec/key-store'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { CompleteAddress } from '@aztec/stdlib/contract'; +import { siloPrivateLog } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { DirectionalAppTaggingSecret, @@ -12,6 +13,8 @@ import { TxScopedL2Log, } from '@aztec/stdlib/logs'; +import type { LogRetrievalRequest } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; +import { LogRetrievalResponse } from '../contract_function_simulator/noir-structs/log_retrieval_response.js'; import type { AddressDataProvider, AnchorBlockDataProvider, @@ -34,6 +37,42 @@ export class LogService { private readonly addressDataProvider: AddressDataProvider, ) {} + public async bulkRetrieveLogs(logRetrievalRequests: LogRetrievalRequest[]): Promise<(LogRetrievalResponse | null)[]> { + return await Promise.all( + logRetrievalRequests.map(async request => { + // TODO(#14555): remove these internal functions and have node endpoints that do this instead + const [publicLog, privateLog] = await Promise.all([ + this.getPublicLogByTag(request.unsiloedTag, request.contractAddress), + this.getPrivateLogByTag(await siloPrivateLog(request.contractAddress, request.unsiloedTag)), + ]); + + if (publicLog !== null) { + if (privateLog !== null) { + throw new Error( + `Found both a public and private log when searching for tag ${request.unsiloedTag} from contract ${request.contractAddress}`, + ); + } + + return new LogRetrievalResponse( + publicLog.logPayload, + publicLog.txHash, + publicLog.uniqueNoteHashesInTx, + publicLog.firstNullifierInTx, + ); + } else if (privateLog !== null) { + return new LogRetrievalResponse( + privateLog.logPayload, + privateLog.txHash, + privateLog.uniqueNoteHashesInTx, + privateLog.firstNullifierInTx, + ); + } else { + return null; + } + }), + ); + } + // TODO(#14555): delete this function and implement this behavior in the node instead public async getPublicLogByTag(tag: Fr, contractAddress: AztecAddress): Promise { const logs = await this.#getPublicLogsByTagsFromContract([tag], contractAddress); From 343adc2f3fe17bcb998c03537931094127fe4df1 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 16:16:10 +0000 Subject: [PATCH 057/140] MembershipWitnessService --- .../oracle/utility_execution_oracle.ts | 49 ++---------------- .../membership_witness_service.ts | 51 +++++++++++++++++++ 2 files changed, 56 insertions(+), 44 deletions(-) create mode 100644 yarn-project/pxe/src/membership_witness/membership_witness_service.ts 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 d1ced4b92c72..061ecabeee12 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 @@ -21,6 +21,7 @@ import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; import { EventService } from '../../events/event_service.js'; import { LogService } from '../../logs/log_service.js'; +import { MembershipWitnessService } from '../../membership_witness/membership_witness_service.js'; import { NoteService } from '../../notes/note_service.js'; import { ORACLE_VERSION } from '../../oracle_version.js'; import type { @@ -109,38 +110,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns The index and sibling path concatenated [index, sibling_path] */ public utilityGetMembershipWitness(blockNumber: BlockNumber, treeId: MerkleTreeId, leafValue: Fr): Promise { - return this.getMembershipWitness(blockNumber, treeId, leafValue); - } - - protected async getMembershipWitness( - blockNumber: BlockParameter, - treeId: MerkleTreeId, - leafValue: Fr, - ): Promise { - const witness = await this.tryGetMembershipWitness(blockNumber, treeId, leafValue); - if (!witness) { - throw new Error(`Leaf value ${leafValue} not found in tree ${MerkleTreeId[treeId]} at block ${blockNumber}`); - } - return witness; - } - - protected async tryGetMembershipWitness( - blockNumber: BlockParameter, - treeId: MerkleTreeId, - value: Fr, - ): Promise { - switch (treeId) { - case MerkleTreeId.NULLIFIER_TREE: - return (await this.aztecNode.getNullifierMembershipWitness(blockNumber, value))?.withoutPreimage().toFields(); - case MerkleTreeId.NOTE_HASH_TREE: - return (await this.aztecNode.getNoteHashMembershipWitness(blockNumber, value))?.toFields(); - case MerkleTreeId.PUBLIC_DATA_TREE: - return (await this.aztecNode.getPublicDataWitness(blockNumber, value))?.withoutPreimage().toFields(); - case MerkleTreeId.ARCHIVE: - return (await this.aztecNode.getArchiveMembershipWitness(blockNumber, value))?.toFields(); - default: - throw new Error('Not implemented'); - } + const membershipWitnessService = new MembershipWitnessService(this.aztecNode, this.anchorBlockDataProvider); + return membershipWitnessService.getMembershipWitness(blockNumber, treeId, leafValue); } /** @@ -169,18 +140,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, nullifier: Fr, ): Promise { - return await this.getLowNullifierMembershipWitness(blockNumber, nullifier); - } - - protected async getLowNullifierMembershipWitness( - blockNumber: BlockParameter, - nullifier: Fr, - ): Promise { - const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return this.aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier); + const membershipWitnessService = new MembershipWitnessService(this.aztecNode, this.anchorBlockDataProvider); + return await membershipWitnessService.getLowNullifierMembershipWitness(blockNumber, nullifier); } /** diff --git a/yarn-project/pxe/src/membership_witness/membership_witness_service.ts b/yarn-project/pxe/src/membership_witness/membership_witness_service.ts new file mode 100644 index 000000000000..decafe6afe0b --- /dev/null +++ b/yarn-project/pxe/src/membership_witness/membership_witness_service.ts @@ -0,0 +1,51 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; +import type { BlockParameter } from '@aztec/stdlib/block'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; +import { MerkleTreeId, NullifierMembershipWitness } from '@aztec/stdlib/trees'; + +import type { AnchorBlockDataProvider } from '../storage/index.js'; + +export class MembershipWitnessService { + constructor( + private readonly aztecNode: AztecNode, + private readonly anchorBlockDataProvider: AnchorBlockDataProvider, + ) {} + + public async getMembershipWitness(blockNumber: BlockParameter, treeId: MerkleTreeId, leafValue: Fr): Promise { + const witness = await this.#tryGetMembershipWitness(blockNumber, treeId, leafValue); + if (!witness) { + throw new Error(`Leaf value ${leafValue} not found in tree ${MerkleTreeId[treeId]} at block ${blockNumber}`); + } + return witness; + } + + public async getLowNullifierMembershipWitness( + blockNumber: BlockParameter, + nullifier: Fr, + ): Promise { + const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return this.aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier); + } + + async #tryGetMembershipWitness( + blockNumber: BlockParameter, + treeId: MerkleTreeId, + value: Fr, + ): Promise { + switch (treeId) { + case MerkleTreeId.NULLIFIER_TREE: + return (await this.aztecNode.getNullifierMembershipWitness(blockNumber, value))?.withoutPreimage().toFields(); + case MerkleTreeId.NOTE_HASH_TREE: + return (await this.aztecNode.getNoteHashMembershipWitness(blockNumber, value))?.toFields(); + case MerkleTreeId.PUBLIC_DATA_TREE: + return (await this.aztecNode.getPublicDataWitness(blockNumber, value))?.withoutPreimage().toFields(); + case MerkleTreeId.ARCHIVE: + return (await this.aztecNode.getArchiveMembershipWitness(blockNumber, value))?.toFields(); + default: + throw new Error('Not implemented'); + } + } +} From 3e4cca44d1015a8db99929f1d8a76948118db69d Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 16:20:01 +0000 Subject: [PATCH 058/140] more MembershipWitnessService --- .../oracle/utility_execution_oracle.ts | 14 ++------------ .../membership_witness_service.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 13 deletions(-) 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 061ecabeee12..39e0ab046490 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 @@ -154,18 +154,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, leafSlot: Fr, ): Promise { - return await this.getPublicDataWitness(blockNumber, leafSlot); - } - - protected async getPublicDataWitness( - blockNumber: BlockParameter, - leafSlot: Fr, - ): Promise { - const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return await this.aztecNode.getPublicDataWitness(blockNumber, leafSlot); + const membershipWitnessService = new MembershipWitnessService(this.aztecNode, this.anchorBlockDataProvider); + return await membershipWitnessService.getPublicDataWitness(blockNumber, leafSlot); } /** diff --git a/yarn-project/pxe/src/membership_witness/membership_witness_service.ts b/yarn-project/pxe/src/membership_witness/membership_witness_service.ts index decafe6afe0b..3c408b600ddd 100644 --- a/yarn-project/pxe/src/membership_witness/membership_witness_service.ts +++ b/yarn-project/pxe/src/membership_witness/membership_witness_service.ts @@ -1,7 +1,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254'; import type { BlockParameter } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import { MerkleTreeId, NullifierMembershipWitness } from '@aztec/stdlib/trees'; +import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { AnchorBlockDataProvider } from '../storage/index.js'; @@ -30,6 +30,14 @@ export class MembershipWitnessService { return this.aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier); } + public async getPublicDataWitness(blockNumber: BlockParameter, leafSlot: Fr): Promise { + const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return await this.aztecNode.getPublicDataWitness(blockNumber, leafSlot); + } + async #tryGetMembershipWitness( blockNumber: BlockParameter, treeId: MerkleTreeId, From 20b47fff9e84829ce43e710f6461306fdf28e542 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 16:29:28 +0000 Subject: [PATCH 059/140] getBlock --- .../oracle/utility_execution_oracle.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) 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 39e0ab046490..6e80944a88c5 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 @@ -7,7 +7,7 @@ import { LogLevels, applyStringFormatting, createLogger } from '@aztec/foundatio import type { KeyStore } from '@aztec/key-store'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter, L2Block } from '@aztec/stdlib/block'; +import type { BlockParameter } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import { siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; @@ -164,19 +164,13 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns Block extracted from a block with block number `blockNumber`. */ public async utilityGetBlockHeader(blockNumber: BlockNumber): Promise { - const block = await this.getBlock(blockNumber); - if (!block) { - return undefined; - } - return block.getBlockHeader(); - } - - protected async getBlock(blockNumber: BlockParameter): Promise { const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + if (blockNumber > anchorBlockNumber) { throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); } - return await this.aztecNode.getBlock(blockNumber); + + const block = await this.aztecNode.getBlock(blockNumber); + return block?.getBlockHeader() || undefined; } /** From 5f9a2e9b34b33ed3833ceccac8b0f62926352ce9 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 16:39:25 +0000 Subject: [PATCH 060/140] getNotes --- .../oracle/private_execution_oracle.ts | 11 +++++- .../oracle/utility_execution_oracle.ts | 34 ++----------------- yarn-project/pxe/src/notes/note_service.ts | 32 ++++++++++++++++- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index 865dd3f29ee1..f984d61aa5d1 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -28,6 +28,7 @@ import { type TxContext, } from '@aztec/stdlib/tx'; +import { NoteService } from '../../notes/note_service.js'; import type { AddressDataProvider, AnchorBlockDataProvider, @@ -367,7 +368,15 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP const pendingNotes = this.noteCache.getNotes(this.callContext.contractAddress, owner, storageSlot); const pendingNullifiers = this.noteCache.getNullifiers(this.callContext.contractAddress); - const dbNotes = await this.getNotes(this.callContext.contractAddress, owner, storageSlot, status, this.scopes); + + const noteService = new NoteService(this.noteDataProvider, this.aztecNode, this.anchorBlockDataProvider); + const dbNotes = await noteService.getNotes( + this.callContext.contractAddress, + owner, + storageSlot, + status, + this.scopes, + ); const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value)); const notes = pickNotes([...dbNotesFiltered, ...pendingNotes], { 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 6e80944a88c5..d4970389e0a3 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 @@ -260,7 +260,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra offset: number, status: NoteStatus, ): Promise { - const dbNotes = await this.getNotes(this.contractAddress, owner, storageSlot, status, this.scopes); + const noteService = new NoteService(this.noteDataProvider, this.aztecNode, this.anchorBlockDataProvider); + + const dbNotes = await noteService.getNotes(this.contractAddress, owner, storageSlot, status, this.scopes); return pickNotes(dbNotes, { selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({ selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] }, @@ -276,36 +278,6 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra }); } - protected async getNotes( - contractAddress: AztecAddress, - owner: AztecAddress | undefined, - storageSlot: Fr, - status: NoteStatus, - scopes?: AztecAddress[], - ) { - const noteDaos = await this.noteDataProvider.getNotes({ - contractAddress, - owner, - storageSlot, - status, - scopes, - }); - return noteDaos.map( - ({ contractAddress, owner, storageSlot, randomness, noteNonce, note, noteHash, siloedNullifier, index }) => ({ - contractAddress, - owner, - storageSlot, - randomness, - noteNonce, - note, - noteHash, - siloedNullifier, - // PXE can use this index to get full MembershipWitness - index, - }), - ); - } - /** * Check if a nullifier exists in the nullifier tree. * @param innerNullifier - The inner nullifier. diff --git a/yarn-project/pxe/src/notes/note_service.ts b/yarn-project/pxe/src/notes/note_service.ts index 0f18138d2b68..64cebcb6cac6 100644 --- a/yarn-project/pxe/src/notes/note_service.ts +++ b/yarn-project/pxe/src/notes/note_service.ts @@ -3,7 +3,7 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { DataInBlock } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; -import { Note } from '@aztec/stdlib/note'; +import { Note, NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; @@ -16,6 +16,36 @@ export class NoteService { private readonly anchorBlockDataProvider: AnchorBlockDataProvider, ) {} + public async getNotes( + contractAddress: AztecAddress, + owner: AztecAddress | undefined, + storageSlot: Fr, + status: NoteStatus, + scopes?: AztecAddress[], + ) { + const noteDaos = await this.noteDataProvider.getNotes({ + contractAddress, + owner, + storageSlot, + status, + scopes, + }); + return noteDaos.map( + ({ contractAddress, owner, storageSlot, randomness, noteNonce, note, noteHash, siloedNullifier, index }) => ({ + contractAddress, + owner, + storageSlot, + randomness, + noteNonce, + note, + noteHash, + siloedNullifier, + // PXE can use this index to get full MembershipWitness + index, + }), + ); + } + /** * Looks for nullifiers of active contract notes and marks them as nullified if a nullifier is found. * From dbc48fce37c2dbd4e166a70dfa7bfd92ca70566d Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 16:58:33 +0000 Subject: [PATCH 061/140] TreeMembershipService --- .../oracle/utility_execution_oracle.ts | 44 +++++-------------- .../tree_membership_service.ts} | 23 +++++++++- 2 files changed, 33 insertions(+), 34 deletions(-) rename yarn-project/pxe/src/{membership_witness/membership_witness_service.ts => tree_membership/tree_membership_service.ts} (73%) 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 d4970389e0a3..27a3de851b26 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,3 @@ -import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { BlockNumber } from '@aztec/foundation/branded-types'; import { Aes128 } from '@aztec/foundation/crypto/aes128'; import { Fr } from '@aztec/foundation/curves/bn254'; @@ -14,14 +13,12 @@ import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import { computeAddressSecret } from '@aztec/stdlib/keys'; import { deriveEcdhSharedSecret } from '@aztec/stdlib/logs'; -import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, Capsule } from '@aztec/stdlib/tx'; import { EventService } from '../../events/event_service.js'; import { LogService } from '../../logs/log_service.js'; -import { MembershipWitnessService } from '../../membership_witness/membership_witness_service.js'; import { NoteService } from '../../notes/note_service.js'; import { ORACLE_VERSION } from '../../oracle_version.js'; import type { @@ -34,6 +31,7 @@ import type { RecipientTaggingDataProvider, SenderTaggingDataProvider, } from '../../storage/index.js'; +import { TreeMembershipService } from '../../tree_membership/tree_membership_service.js'; import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js'; @@ -110,8 +108,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns The index and sibling path concatenated [index, sibling_path] */ public utilityGetMembershipWitness(blockNumber: BlockNumber, treeId: MerkleTreeId, leafValue: Fr): Promise { - const membershipWitnessService = new MembershipWitnessService(this.aztecNode, this.anchorBlockDataProvider); - return membershipWitnessService.getMembershipWitness(blockNumber, treeId, leafValue); + const treeMembershipService = new TreeMembershipService(this.aztecNode, this.anchorBlockDataProvider); + return treeMembershipService.getMembershipWitness(blockNumber, treeId, leafValue); } /** @@ -140,8 +138,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, nullifier: Fr, ): Promise { - const membershipWitnessService = new MembershipWitnessService(this.aztecNode, this.anchorBlockDataProvider); - return await membershipWitnessService.getLowNullifierMembershipWitness(blockNumber, nullifier); + const treeMembershipService = new TreeMembershipService(this.aztecNode, this.anchorBlockDataProvider); + return await treeMembershipService.getLowNullifierMembershipWitness(blockNumber, nullifier); } /** @@ -154,8 +152,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra blockNumber: BlockNumber, leafSlot: Fr, ): Promise { - const membershipWitnessService = new MembershipWitnessService(this.aztecNode, this.anchorBlockDataProvider); - return await membershipWitnessService.getPublicDataWitness(blockNumber, leafSlot); + const treeMembershipService = new TreeMembershipService(this.aztecNode, this.anchorBlockDataProvider); + return await treeMembershipService.getPublicDataWitness(blockNumber, leafSlot); } /** @@ -285,23 +283,11 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra */ public async utilityCheckNullifierExists(innerNullifier: Fr) { const nullifier = await siloNullifier(this.contractAddress, innerNullifier!); - const index = await this.getNullifierIndex(nullifier); + const treeMembershipService = new TreeMembershipService(this.aztecNode, this.anchorBlockDataProvider); + const index = await treeMembershipService.getNullifierIndex(nullifier); return index !== undefined; } - protected async getNullifierIndex(nullifier: Fr) { - return await this.findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier); - } - - protected async findLeafIndex( - blockNumber: BlockParameter, - treeId: MerkleTreeId, - leafValue: Fr, - ): Promise { - const [leafIndex] = await this.aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]); - return leafIndex?.data; - } - /** * Fetches a message from the executionDataProvider, given its key. * @param contractAddress - Address of a contract by which the message was emitted. @@ -311,16 +297,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra * @returns The l1 to l2 membership witness (index of message in the tree and sibling path). */ public async utilityGetL1ToL2MembershipWitness(contractAddress: AztecAddress, messageHash: Fr, secret: Fr) { - return await this.getL1ToL2MembershipWitness(contractAddress, messageHash, secret); - } - - protected async getL1ToL2MembershipWitness( - contractAddress: AztecAddress, - messageHash: Fr, - secret: Fr, - ): Promise> { - const [messageIndex, siblingPath] = await getNonNullifiedL1ToL2MessageWitness( - this.aztecNode, + const treeMembershipService = new TreeMembershipService(this.aztecNode, this.anchorBlockDataProvider); + const [messageIndex, siblingPath] = await treeMembershipService.getL1ToL2MembershipWitness( contractAddress, messageHash, secret, diff --git a/yarn-project/pxe/src/membership_witness/membership_witness_service.ts b/yarn-project/pxe/src/tree_membership/tree_membership_service.ts similarity index 73% rename from yarn-project/pxe/src/membership_witness/membership_witness_service.ts rename to yarn-project/pxe/src/tree_membership/tree_membership_service.ts index 3c408b600ddd..bba8bce37c1c 100644 --- a/yarn-project/pxe/src/membership_witness/membership_witness_service.ts +++ b/yarn-project/pxe/src/tree_membership/tree_membership_service.ts @@ -1,16 +1,24 @@ +import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import type { Fr } from '@aztec/foundation/curves/bn254'; +import type { SiblingPath } from '@aztec/foundation/trees'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; +import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { AnchorBlockDataProvider } from '../storage/index.js'; -export class MembershipWitnessService { +export class TreeMembershipService { constructor( private readonly aztecNode: AztecNode, private readonly anchorBlockDataProvider: AnchorBlockDataProvider, ) {} + public getNullifierIndex(nullifier: Fr) { + return this.#findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier); + } + public async getMembershipWitness(blockNumber: BlockParameter, treeId: MerkleTreeId, leafValue: Fr): Promise { const witness = await this.#tryGetMembershipWitness(blockNumber, treeId, leafValue); if (!witness) { @@ -38,6 +46,14 @@ export class MembershipWitnessService { return await this.aztecNode.getPublicDataWitness(blockNumber, leafSlot); } + public getL1ToL2MembershipWitness( + contractAddress: AztecAddress, + messageHash: Fr, + secret: Fr, + ): Promise<[bigint, SiblingPath]> { + return getNonNullifiedL1ToL2MessageWitness(this.aztecNode, contractAddress, messageHash, secret); + } + async #tryGetMembershipWitness( blockNumber: BlockParameter, treeId: MerkleTreeId, @@ -56,4 +72,9 @@ export class MembershipWitnessService { throw new Error('Not implemented'); } } + + async #findLeafIndex(blockNumber: BlockParameter, treeId: MerkleTreeId, leafValue: Fr): Promise { + const [leafIndex] = await this.aztecNode.findLeavesIndexes(blockNumber, treeId, [leafValue]); + return leafIndex?.data; + } } From 9fd681e96272779371391e2978ff0c792676cbbf Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 17:08:34 +0000 Subject: [PATCH 062/140] PublicStorageService --- .../oracle/utility_execution_oracle.ts | 16 ++++++-------- .../public_storage/public_storage_service.ts | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 yarn-project/pxe/src/public_storage/public_storage_service.ts 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 27a3de851b26..470e1210d8be 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 @@ -6,7 +6,6 @@ import { LogLevels, applyStringFormatting, createLogger } from '@aztec/foundatio import type { KeyStore } from '@aztec/key-store'; import type { AuthWitness } from '@aztec/stdlib/auth-witness'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { BlockParameter } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import { siloNullifier } from '@aztec/stdlib/hash'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; @@ -21,6 +20,7 @@ import { EventService } from '../../events/event_service.js'; import { LogService } from '../../logs/log_service.js'; import { NoteService } from '../../notes/note_service.js'; import { ORACLE_VERSION } from '../../oracle_version.js'; +import { PublicStorageService } from '../../public_storage/public_storage_service.js'; import type { AddressDataProvider, AnchorBlockDataProvider, @@ -322,9 +322,13 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra numberOfElements: number, ) { const values = []; + const publicStorageService = new PublicStorageService(this.anchorBlockDataProvider, this.aztecNode); + + // TODO: why do we serialize these requests? This should probably a single call + // Privacy considerations? for (let i = 0n; i < numberOfElements; i++) { const storageSlot = new Fr(startStorageSlot.value + i); - const value = await this.getPublicStorageAt(blockNumber, contractAddress, storageSlot); + const value = await publicStorageService.getPublicStorageAt(blockNumber, contractAddress, storageSlot); this.log.debug( `Oracle storage read: slot=${storageSlot.toString()} address-${contractAddress.toString()} value=${value}`, @@ -334,14 +338,6 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra return values; } - protected async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise { - const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); - if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { - throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); - } - return await this.aztecNode.getPublicStorageAt(blockNumber, contract, slot); - } - public utilityDebugLog(level: number, message: string, fields: Fr[]): void { if (!LogLevels[level]) { throw new Error(`Invalid debug log level: ${level}`); diff --git a/yarn-project/pxe/src/public_storage/public_storage_service.ts b/yarn-project/pxe/src/public_storage/public_storage_service.ts new file mode 100644 index 000000000000..967c051c30e7 --- /dev/null +++ b/yarn-project/pxe/src/public_storage/public_storage_service.ts @@ -0,0 +1,21 @@ +import type { Fr } from '@aztec/foundation/curves/bn254'; +import type { AztecAddress } from '@aztec/stdlib/aztec-address'; +import type { BlockParameter } from '@aztec/stdlib/block'; +import type { AztecNode } from '@aztec/stdlib/interfaces/server'; + +import type { AnchorBlockDataProvider } from '../storage/index.js'; + +export class PublicStorageService { + constructor( + private readonly anchorBlockDataProvider: AnchorBlockDataProvider, + private readonly aztecNode: AztecNode, + ) {} + + public async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise { + const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); + if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { + throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`); + } + return await this.aztecNode.getPublicStorageAt(blockNumber, contract, slot); + } +} From 719255e3e6c874da77ab0a7d58036d0ee657abcc Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 17:43:18 +0000 Subject: [PATCH 063/140] port docs --- .../contract_function_simulator.ts | 4 +++ .../oracle/utility_execution_oracle.ts | 16 ++++++++++ yarn-project/pxe/src/notes/note_service.ts | 9 ++++++ .../public_storage/public_storage_service.ts | 12 +++++++ .../capsule_data_provider.ts | 32 +++++++++++++++++++ .../tree_membership_service.ts | 32 +++++++++++++++++++ 6 files changed, 105 insertions(+) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 2936986cba77..6c60094edecd 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -318,6 +318,10 @@ export class ContractFunctionSimulator { } // docs:end:execute_utility_function + /** + * Returns the execution statistics collected during the simulator run. + * @returns The execution statistics. + */ getStats() { const nodeRPCCalls = typeof (this.aztecNode as ProxiedNode).getStats === 'function' ? (this.aztecNode as ProxiedNode).getStats() : {}; 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 470e1210d8be..089616c7c9b2 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 @@ -362,6 +362,16 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra await noteService.syncNoteNullifiers(this.contractAddress); } + /** + * Validates all note and event validation requests enqueued via `enqueue_note_for_validation` and + * `enqueue_event_for_validation`, inserting them into the note database and event store respectively, making them + * queryable via `get_notes` and `getPrivateEvents`. + * + * This automatically clears both validation request queues, so no further work needs to be done by the caller. + * @param contractAddress - The address of the contract that the logs are tagged for. + * @param noteValidationRequestsArrayBaseSlot - The base slot of capsule array containing note validation requests. + * @param eventValidationRequestsArrayBaseSlot - The base slot of capsule array containing event validation requests. + */ public async utilityValidateEnqueuedNotesAndEvents( contractAddress: AztecAddress, noteValidationRequestsArrayBaseSlot: Fr, @@ -502,6 +512,12 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra return aes128.decryptBufferCBC(ciphertext, iv, symKey); } + /** + * Retrieves the shared secret for a given address and ephemeral public key. + * @param address - The address to get the secret for. + * @param ephPk - The ephemeral public key to get the secret for. + * @returns The secret for the given address. + */ public utilityGetSharedSecret(address: AztecAddress, ephPk: Point): Promise { return this.getSharedSecret(address, ephPk); } diff --git a/yarn-project/pxe/src/notes/note_service.ts b/yarn-project/pxe/src/notes/note_service.ts index 64cebcb6cac6..5ff176808179 100644 --- a/yarn-project/pxe/src/notes/note_service.ts +++ b/yarn-project/pxe/src/notes/note_service.ts @@ -16,6 +16,15 @@ export class NoteService { private readonly anchorBlockDataProvider: AnchorBlockDataProvider, ) {} + /** + * Retrieves a set of notes stored in the database for a given contract address and storage slot. + * The query result is paginated using 'limit' and 'offset' values. + * Returns an object containing an array of note data. + * + * @param owner - The owner of the notes. If undefined, returns notes for all known owners. + * @param status - The status of notes to fetch. + * @param scopes - The accounts whose notes we can access in this call. Currently optional and will default to all. + */ public async getNotes( contractAddress: AztecAddress, owner: AztecAddress | undefined, diff --git a/yarn-project/pxe/src/public_storage/public_storage_service.ts b/yarn-project/pxe/src/public_storage/public_storage_service.ts index 967c051c30e7..158f66d6852b 100644 --- a/yarn-project/pxe/src/public_storage/public_storage_service.ts +++ b/yarn-project/pxe/src/public_storage/public_storage_service.ts @@ -11,6 +11,18 @@ export class PublicStorageService { private readonly aztecNode: AztecNode, ) {} + /** + * Gets the storage value at the given contract storage slot. + * + * @remarks The storage slot here refers to the slot as it is defined in Noir not the index in the merkle tree. + * Aztec's version of `eth_getStorageAt`. + * + * @param blockNumber - The block number at which to get the data. + * @param contract - Address of the contract to query. + * @param slot - Slot to query. + * @returns Storage value at the given contract slot. + * @throws If the contract is not deployed. + */ public async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise { const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { diff --git a/yarn-project/pxe/src/storage/capsule_data_provider/capsule_data_provider.ts b/yarn-project/pxe/src/storage/capsule_data_provider/capsule_data_provider.ts index ae3ff2784c3c..ce1c42fe726f 100644 --- a/yarn-project/pxe/src/storage/capsule_data_provider/capsule_data_provider.ts +++ b/yarn-project/pxe/src/storage/capsule_data_provider/capsule_data_provider.ts @@ -19,10 +19,26 @@ export class CapsuleDataProvider { this.logger = createLogger('pxe:capsule-data-provider'); } + /** + * Stores arbitrary information in a per-contract non-volatile database, which can later be retrieved with `loadCapsule`. + * * If data was already stored at this slot, it is overwritten. + * @param contractAddress - The contract address to scope the data under. + * @param slot - The slot in the database in which to store the value. Slots need not be contiguous. + * @param capsule - An array of field elements representing the capsule. + * @remarks A capsule is a "blob" of data that is passed to the contract through an oracle. It works similarly + * to public contract storage in that it's indexed by the contract address and storage slot but instead of the global + * network state it's backed by local PXE db. + */ async storeCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[]): Promise { await this.#capsules.set(dbSlotToKey(contractAddress, slot), Buffer.concat(capsule.map(value => value.toBuffer()))); } + /** + * Returns data previously stored via `storeCapsule` in the per-contract non-volatile database. + * @param contractAddress - The contract address under which the data is scoped. + * @param slot - The slot in the database to read. + * @returns The stored data or `null` if no data is stored under the slot. + */ async loadCapsule(contractAddress: AztecAddress, slot: Fr): Promise { const dataBuffer = await this.#capsules.getAsync(dbSlotToKey(contractAddress, slot)); if (!dataBuffer) { @@ -36,10 +52,26 @@ export class CapsuleDataProvider { return capsule; } + /** + * Deletes data in the per-contract non-volatile database. Does nothing if no data was present. + * @param contractAddress - The contract address under which the data is scoped. + * @param slot - The slot in the database to delete. + */ async deleteCapsule(contractAddress: AztecAddress, slot: Fr): Promise { await this.#capsules.delete(dbSlotToKey(contractAddress, slot)); } + /** + * Copies a number of contiguous entries in the per-contract non-volatile database. This allows for efficient data + * structures by avoiding repeated calls to `loadCapsule` and `storeCapsule`. + * Supports overlapping source and destination regions (which will result in the overlapped source values being + * overwritten). All copied slots must exist in the database (i.e. have been stored and not deleted) + * + * @param contractAddress - The contract address under which the data is scoped. + * @param srcSlot - The first slot to copy from. + * @param dstSlot - The first slot to copy to. + * @param numEntries - The number of entries to copy. + */ copyCapsule(contractAddress: AztecAddress, srcSlot: Fr, dstSlot: Fr, numEntries: number): Promise { return this.#store.transactionAsync(async () => { // In order to support overlapping source and destination regions, we need to check the relative positions of source diff --git a/yarn-project/pxe/src/tree_membership/tree_membership_service.ts b/yarn-project/pxe/src/tree_membership/tree_membership_service.ts index bba8bce37c1c..a2c838bb3211 100644 --- a/yarn-project/pxe/src/tree_membership/tree_membership_service.ts +++ b/yarn-project/pxe/src/tree_membership/tree_membership_service.ts @@ -15,10 +15,21 @@ export class TreeMembershipService { private readonly anchorBlockDataProvider: AnchorBlockDataProvider, ) {} + /** + * Gets the index of a nullifier in the nullifier tree. + * @returns - The index of the nullifier. Undefined if it does not exist in the tree. + */ public getNullifierIndex(nullifier: Fr) { return this.#findLeafIndex('latest', MerkleTreeId.NULLIFIER_TREE, nullifier); } + /** + * Fetches the index and sibling path of a leaf at a given block from a given tree. + * @param blockNumber - The block number at which to get the membership witness. + * @param treeId - Id of the tree to get the sibling path from. + * @param leafValue - The leaf value + * @returns The index and sibling path concatenated [index, sibling_path] + */ public async getMembershipWitness(blockNumber: BlockParameter, treeId: MerkleTreeId, leafValue: Fr): Promise { const witness = await this.#tryGetMembershipWitness(blockNumber, treeId, leafValue); if (!witness) { @@ -27,6 +38,15 @@ export class TreeMembershipService { return witness; } + /** + * Returns a low nullifier membership witness for a given nullifier at a given block. + * @param blockNumber - The block number at which to get the index. + * @param nullifier - Nullifier we try to find the low nullifier witness for. + * @returns The low nullifier membership witness (if found). + * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked + * 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 async getLowNullifierMembershipWitness( blockNumber: BlockParameter, nullifier: Fr, @@ -38,6 +58,11 @@ export class TreeMembershipService { return this.aztecNode.getLowNullifierMembershipWitness(blockNumber, nullifier); } + /** + * Returns a witness for a given slot of the public data tree at a given block. + * @param blockNumber - The block number at which to get the witness. + * @param leafSlot - The slot of the public data in the public data tree. + */ public async getPublicDataWitness(blockNumber: BlockParameter, leafSlot: Fr): Promise { const anchorBlockNumber = (await this.anchorBlockDataProvider.getBlockHeader()).getBlockNumber(); if (blockNumber !== 'latest' && blockNumber > anchorBlockNumber) { @@ -46,6 +71,13 @@ export class TreeMembershipService { return await this.aztecNode.getPublicDataWitness(blockNumber, leafSlot); } + /** + * Looks for the L1 to L2 membership witness of a message at the Aztec node, given its hash. + * @param contractAddress - Address of a contract by which the message was emitted. + * @dev Contract address and secret are only used to compute the nullifier to get non-nullified messages. + * The message nullifier is computed locally, so the secret is not sent to the node. + * @returns The l1 to l2 membership witness (index of message in the tree and sibling path). + */ public getL1ToL2MembershipWitness( contractAddress: AztecAddress, messageHash: Fr, From ba5c24b2bbf2048e95bda4dafbf89d000e4baef3 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 18:00:32 +0000 Subject: [PATCH 064/140] good bye ExecutionDataProvider --- .../execution_data_provider.ts | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts diff --git a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts b/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts deleted file mode 100644 index fc6c464753af..000000000000 --- a/yarn-project/pxe/src/contract_function_simulator/execution_data_provider.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { NodeStats } from '@aztec/stdlib/tx'; - -/** - * Error thrown when a contract is not found in the database. - */ -export class ContractNotFoundError extends Error { - constructor(contractAddress: string) { - super(`DB has no contract with address ${contractAddress}`); - } -} - -/** - * Error thrown when a contract class is not found in the database. - */ -export class ContractClassNotFoundError extends Error { - constructor(contractClassId: string) { - super(`DB has no contract class with id ${contractClassId}`); - } -} - -/* - * Collected stats during the execution of a transaction. - */ -export type ExecutionStats = { - /** - * Contains an entry for each RPC call performed during the execution - */ - nodeRPCCalls: NodeStats; -}; From 268c6dd773d54a8ee98493b7f0bc03c8c234dbc8 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 20:43:27 +0000 Subject: [PATCH 065/140] wanted --- yarn-project/pxe/src/logs/log_service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/pxe/src/logs/log_service.ts b/yarn-project/pxe/src/logs/log_service.ts index 67f7bba87b8b..81da04f507b6 100644 --- a/yarn-project/pxe/src/logs/log_service.ts +++ b/yarn-project/pxe/src/logs/log_service.ts @@ -26,7 +26,7 @@ import { SiloedTag } from '../tagging/siloed_tag.js'; import { Tag } from '../tagging/tag.js'; import { getInitialIndexesMap, getPreTagsForTheWindow } from '../tagging/utils.js'; -// TODO: good names wanted +// TODO: wanted: good name export class LogService { constructor( private readonly aztecNode: AztecNode, From 5b0a17eb41a9481c85c6b80f1d553947a228cc8d Mon Sep 17 00:00:00 2001 From: mverzilli Date: Fri, 19 Dec 2025 22:02:06 +0000 Subject: [PATCH 066/140] reduce barrel imports --- .../contract_function_simulator.ts | 18 ++++++++---------- .../oracle/oracle_version_is_checked.test.ts | 18 ++++++++---------- .../oracle/private_execution.test.ts | 16 +++++++--------- .../oracle/private_execution_oracle.ts | 18 ++++++++---------- .../oracle/utility_execution.test.ts | 18 ++++++++---------- .../oracle/utility_execution_oracle.ts | 18 ++++++++---------- .../proxied_contract_data_source.ts | 2 +- yarn-project/pxe/src/debug/pxe_debug_utils.ts | 3 ++- .../pxe/src/events/event_service.test.ts | 3 ++- yarn-project/pxe/src/events/event_service.ts | 3 ++- .../private_event_filter_validator.test.ts | 2 +- .../events/private_event_filter_validator.ts | 3 ++- yarn-project/pxe/src/logs/log_service.test.ts | 4 +++- yarn-project/pxe/src/logs/log_service.ts | 10 ++++------ .../pxe/src/notes/note_service.test.ts | 3 ++- yarn-project/pxe/src/notes/note_service.ts | 5 +++-- .../private_kernel_oracle_impl.ts | 2 +- .../public_storage/public_storage_service.ts | 2 +- yarn-project/pxe/src/pxe.test.ts | 2 +- .../tree_membership/tree_membership_service.ts | 2 +- 20 files changed, 73 insertions(+), 79 deletions(-) diff --git a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts index 6c60094edecd..62fb61d86dbb 100644 --- a/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts +++ b/yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts @@ -71,16 +71,14 @@ import { getFinalMinRevertibleSideEffectCounter, } from '@aztec/stdlib/tx'; -import type { - AddressDataProvider, - AnchorBlockDataProvider, - CapsuleDataProvider, - ContractDataProvider, - NoteDataProvider, - PrivateEventDataProvider, - RecipientTaggingDataProvider, - SenderTaggingDataProvider, -} from '../storage/index.js'; +import type { AddressDataProvider } from '../storage/address_data_provider/address_data_provider.js'; +import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import type { CapsuleDataProvider } from '../storage/capsule_data_provider/capsule_data_provider.js'; +import type { ContractDataProvider } from '../storage/contract_data_provider/contract_data_provider.js'; +import type { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; +import type { PrivateEventDataProvider } from '../storage/private_event_data_provider/private_event_data_provider.js'; +import type { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js'; +import type { SenderTaggingDataProvider } from '../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionTaggingIndexCache } from './execution_tagging_index_cache.js'; import { HashedValuesCache } from './hashed_values_cache.js'; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts index 772ec4a4ba1b..6259860fc432 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts @@ -12,16 +12,14 @@ import { BlockHeader, HashedValues, TxContext, TxExecutionRequest } from '@aztec import { jest } from '@jest/globals'; import { mock } from 'jest-mock-extended'; -import { - AddressDataProvider, - AnchorBlockDataProvider, - CapsuleDataProvider, - ContractDataProvider, - NoteDataProvider, - PrivateEventDataProvider, - RecipientTaggingDataProvider, - SenderTaggingDataProvider, -} from '../../storage/index.js'; +import type { AddressDataProvider } from '../../storage/address_data_provider/address_data_provider.js'; +import type { AnchorBlockDataProvider } from '../../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import type { CapsuleDataProvider } from '../../storage/capsule_data_provider/capsule_data_provider.js'; +import type { ContractDataProvider } from '../../storage/contract_data_provider/contract_data_provider.js'; +import type { NoteDataProvider } from '../../storage/note_data_provider/note_data_provider.js'; +import type { PrivateEventDataProvider } from '../../storage/private_event_data_provider/private_event_data_provider.js'; +import type { RecipientTaggingDataProvider } from '../../storage/tagging_data_provider/recipient_tagging_data_provider.js'; +import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts index 9a2299841e19..96d5cd3c21e3 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts @@ -63,15 +63,13 @@ import { jest } from '@jest/globals'; import { Matcher, type MatcherCreator, type MockProxy, mock } from 'jest-mock-extended'; import { toFunctionSelector } from 'viem'; -import { - AddressDataProvider, - AnchorBlockDataProvider, - CapsuleDataProvider, - ContractDataProvider, - NoteDataProvider, - PrivateEventDataProvider, - RecipientTaggingDataProvider, -} from '../../storage/index.js'; +import type { AddressDataProvider } from '../../storage/address_data_provider/address_data_provider.js'; +import type { AnchorBlockDataProvider } from '../../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import type { CapsuleDataProvider } from '../../storage/capsule_data_provider/capsule_data_provider.js'; +import type { ContractDataProvider } from '../../storage/contract_data_provider/contract_data_provider.js'; +import type { NoteDataProvider } from '../../storage/note_data_provider/note_data_provider.js'; +import type { PrivateEventDataProvider } from '../../storage/private_event_data_provider/private_event_data_provider.js'; +import type { RecipientTaggingDataProvider } from '../../storage/tagging_data_provider/recipient_tagging_data_provider.js'; import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts index f984d61aa5d1..0553f5cadb01 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts @@ -29,16 +29,14 @@ import { } from '@aztec/stdlib/tx'; import { NoteService } from '../../notes/note_service.js'; -import type { - AddressDataProvider, - AnchorBlockDataProvider, - CapsuleDataProvider, - ContractDataProvider, - NoteDataProvider, - PrivateEventDataProvider, - RecipientTaggingDataProvider, - SenderTaggingDataProvider, -} from '../../storage/index.js'; +import type { AddressDataProvider } from '../../storage/address_data_provider/address_data_provider.js'; +import type { AnchorBlockDataProvider } from '../../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import type { CapsuleDataProvider } from '../../storage/capsule_data_provider/capsule_data_provider.js'; +import type { ContractDataProvider } from '../../storage/contract_data_provider/contract_data_provider.js'; +import type { NoteDataProvider } from '../../storage/note_data_provider/note_data_provider.js'; +import type { PrivateEventDataProvider } from '../../storage/private_event_data_provider/private_event_data_provider.js'; +import type { RecipientTaggingDataProvider } from '../../storage/tagging_data_provider/recipient_tagging_data_provider.js'; +import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { syncSenderTaggingIndexes } from '../../tagging/sync/sync_sender_tagging_indexes.js'; import { Tag } from '../../tagging/tag.js'; import type { ExecutionNoteCache } from '../execution_note_cache.js'; 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 480b418d425f..698f50f307ca 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 @@ -14,16 +14,14 @@ import { BlockHeader, GlobalVariables, TxHash } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; import type { _MockProxy } from 'jest-mock-extended/lib/Mock.js'; -import { - AddressDataProvider, - AnchorBlockDataProvider, - CapsuleDataProvider, - ContractDataProvider, - NoteDataProvider, - PrivateEventDataProvider, - RecipientTaggingDataProvider, - SenderTaggingDataProvider, -} from '../../storage/index.js'; +import type { AddressDataProvider } from '../../storage/address_data_provider/address_data_provider.js'; +import type { AnchorBlockDataProvider } from '../../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import type { CapsuleDataProvider } from '../../storage/capsule_data_provider/capsule_data_provider.js'; +import type { ContractDataProvider } from '../../storage/contract_data_provider/contract_data_provider.js'; +import type { NoteDataProvider } from '../../storage/note_data_provider/note_data_provider.js'; +import type { PrivateEventDataProvider } from '../../storage/private_event_data_provider/private_event_data_provider.js'; +import type { RecipientTaggingDataProvider } from '../../storage/tagging_data_provider/recipient_tagging_data_provider.js'; +import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { ContractFunctionSimulator } from '../contract_function_simulator.js'; import { UtilityExecutionOracle } from './utility_execution_oracle.js'; 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 089616c7c9b2..3d381ba9d76f 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 @@ -21,16 +21,14 @@ import { LogService } from '../../logs/log_service.js'; import { NoteService } from '../../notes/note_service.js'; import { ORACLE_VERSION } from '../../oracle_version.js'; import { PublicStorageService } from '../../public_storage/public_storage_service.js'; -import type { - AddressDataProvider, - AnchorBlockDataProvider, - CapsuleDataProvider, - ContractDataProvider, - NoteDataProvider, - PrivateEventDataProvider, - RecipientTaggingDataProvider, - SenderTaggingDataProvider, -} from '../../storage/index.js'; +import type { AddressDataProvider } from '../../storage/address_data_provider/address_data_provider.js'; +import type { AnchorBlockDataProvider } from '../../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import type { CapsuleDataProvider } from '../../storage/capsule_data_provider/capsule_data_provider.js'; +import type { ContractDataProvider } from '../../storage/contract_data_provider/contract_data_provider.js'; +import type { NoteDataProvider } from '../../storage/note_data_provider/note_data_provider.js'; +import type { PrivateEventDataProvider } from '../../storage/private_event_data_provider/private_event_data_provider.js'; +import type { RecipientTaggingDataProvider } from '../../storage/tagging_data_provider/recipient_tagging_data_provider.js'; +import type { SenderTaggingDataProvider } from '../../storage/tagging_data_provider/sender_tagging_data_provider.js'; import { TreeMembershipService } from '../../tree_membership/tree_membership_service.js'; import { EventValidationRequest } from '../noir-structs/event_validation_request.js'; import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js'; diff --git a/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts b/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts index ceb673ad7c41..0ff6639ff7bb 100644 --- a/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts +++ b/yarn-project/pxe/src/contract_function_simulator/proxied_contract_data_source.ts @@ -2,7 +2,7 @@ import { FunctionSelector } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractOverrides } from '@aztec/stdlib/tx'; -import type { ContractDataProvider } from '../storage/index.js'; +import type { ContractDataProvider } from '../storage/contract_data_provider/contract_data_provider.js'; /* * Proxy generator for a ContractDataProvider that allows overriding contract instances and artifacts, so diff --git a/yarn-project/pxe/src/debug/pxe_debug_utils.ts b/yarn-project/pxe/src/debug/pxe_debug_utils.ts index ccc5a53f2cf3..6ec6bdfe9d47 100644 --- a/yarn-project/pxe/src/debug/pxe_debug_utils.ts +++ b/yarn-project/pxe/src/debug/pxe_debug_utils.ts @@ -1,7 +1,8 @@ import type { NoteDao, NotesFilter } from '@aztec/stdlib/note'; import type { PXE } from '../pxe.js'; -import type { ContractDataProvider, NoteDataProvider } from '../storage/index.js'; +import type { ContractDataProvider } from '../storage/contract_data_provider/contract_data_provider.js'; +import type { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; /** * Methods provided by this class might help debugging but must not be used in production. diff --git a/yarn-project/pxe/src/events/event_service.test.ts b/yarn-project/pxe/src/events/event_service.test.ts index 2cb5a7e2dd35..e79799125fe2 100644 --- a/yarn-project/pxe/src/events/event_service.test.ts +++ b/yarn-project/pxe/src/events/event_service.test.ts @@ -10,7 +10,8 @@ import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect } from '@a import { mock } from 'jest-mock-extended'; -import { AnchorBlockDataProvider, PrivateEventDataProvider } from '../storage/index.js'; +import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import { PrivateEventDataProvider } from '../storage/private_event_data_provider/private_event_data_provider.js'; import { EventService } from './event_service.js'; describe('deliverEvent', () => { diff --git a/yarn-project/pxe/src/events/event_service.ts b/yarn-project/pxe/src/events/event_service.ts index eaf3a8a1ca5f..329bc2b8d778 100644 --- a/yarn-project/pxe/src/events/event_service.ts +++ b/yarn-project/pxe/src/events/event_service.ts @@ -6,7 +6,8 @@ import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; -import type { AnchorBlockDataProvider, PrivateEventDataProvider } from '../storage/index.js'; +import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import { PrivateEventDataProvider } from '../storage/private_event_data_provider/private_event_data_provider.js'; export class EventService { constructor( diff --git a/yarn-project/pxe/src/events/private_event_filter_validator.test.ts b/yarn-project/pxe/src/events/private_event_filter_validator.test.ts index b3243a84e1bd..2f6baa41b8a6 100644 --- a/yarn-project/pxe/src/events/private_event_filter_validator.test.ts +++ b/yarn-project/pxe/src/events/private_event_filter_validator.test.ts @@ -6,7 +6,7 @@ import { BlockHeader, TxHash } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; import type { MockProxy } from 'jest-mock-extended/lib/Mock.js'; -import { AnchorBlockDataProvider } from '../storage/index.js'; +import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; import { PrivateEventFilterValidator } from './private_event_filter_validator.js'; describe('PrivateEventFilterValidator', () => { diff --git a/yarn-project/pxe/src/events/private_event_filter_validator.ts b/yarn-project/pxe/src/events/private_event_filter_validator.ts index 8eef02287b0c..9d2372619945 100644 --- a/yarn-project/pxe/src/events/private_event_filter_validator.ts +++ b/yarn-project/pxe/src/events/private_event_filter_validator.ts @@ -2,7 +2,8 @@ import type { PrivateEventFilter } from '@aztec/aztec.js/wallet'; import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; import { BlockNumber } from '@aztec/foundation/branded-types'; -import type { AnchorBlockDataProvider, PrivateEventDataProviderFilter } from '../storage/index.js'; +import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import type { PrivateEventDataProviderFilter } from '../storage/private_event_data_provider/private_event_data_provider.js'; export class PrivateEventFilterValidator { constructor(private anchorBlockDataProvider: AnchorBlockDataProvider) {} diff --git a/yarn-project/pxe/src/logs/log_service.test.ts b/yarn-project/pxe/src/logs/log_service.test.ts index 74a573e5c923..d0f4bff17f14 100644 --- a/yarn-project/pxe/src/logs/log_service.test.ts +++ b/yarn-project/pxe/src/logs/log_service.test.ts @@ -16,7 +16,9 @@ import { BlockHeader, GlobalVariables, TxEffect, TxHash, randomIndexedTxEffect } import { type MockProxy, mock } from 'jest-mock-extended'; import { LogRetrievalRequest } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; -import { AddressDataProvider, AnchorBlockDataProvider, CapsuleDataProvider } from '../storage/index.js'; +import { AddressDataProvider } from '../storage/address_data_provider/address_data_provider.js'; +import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import { CapsuleDataProvider } from '../storage/capsule_data_provider/capsule_data_provider.js'; import { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js'; import { WINDOW_HALF_SIZE } from '../tagging/constants.js'; import { SiloedTag } from '../tagging/siloed_tag.js'; diff --git a/yarn-project/pxe/src/logs/log_service.ts b/yarn-project/pxe/src/logs/log_service.ts index 81da04f507b6..917a7040cfe7 100644 --- a/yarn-project/pxe/src/logs/log_service.ts +++ b/yarn-project/pxe/src/logs/log_service.ts @@ -15,12 +15,10 @@ import { import type { LogRetrievalRequest } from '../contract_function_simulator/noir-structs/log_retrieval_request.js'; import { LogRetrievalResponse } from '../contract_function_simulator/noir-structs/log_retrieval_response.js'; -import type { - AddressDataProvider, - AnchorBlockDataProvider, - CapsuleDataProvider, - RecipientTaggingDataProvider, -} from '../storage/index.js'; +import { AddressDataProvider } from '../storage/address_data_provider/address_data_provider.js'; +import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import { CapsuleDataProvider } from '../storage/capsule_data_provider/capsule_data_provider.js'; +import { RecipientTaggingDataProvider } from '../storage/tagging_data_provider/recipient_tagging_data_provider.js'; import { WINDOW_HALF_SIZE } from '../tagging/constants.js'; import { SiloedTag } from '../tagging/siloed_tag.js'; import { Tag } from '../tagging/tag.js'; diff --git a/yarn-project/pxe/src/notes/note_service.test.ts b/yarn-project/pxe/src/notes/note_service.test.ts index 7f3d9b6fd185..7599e806f793 100644 --- a/yarn-project/pxe/src/notes/note_service.test.ts +++ b/yarn-project/pxe/src/notes/note_service.test.ts @@ -14,7 +14,8 @@ import { BlockHeader, GlobalVariables, type IndexedTxEffect, TxEffect, TxHash } import { jest } from '@jest/globals'; import { mock } from 'jest-mock-extended'; -import { AnchorBlockDataProvider, NoteDataProvider } from '../storage/index.js'; +import { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; import { NoteService } from './note_service.js'; describe('NoteService', () => { diff --git a/yarn-project/pxe/src/notes/note_service.ts b/yarn-project/pxe/src/notes/note_service.ts index 5ff176808179..4259dc8bb001 100644 --- a/yarn-project/pxe/src/notes/note_service.ts +++ b/yarn-project/pxe/src/notes/note_service.ts @@ -3,11 +3,12 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { DataInBlock } from '@aztec/stdlib/block'; import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash'; import { type AztecNode, MAX_RPC_LEN } from '@aztec/stdlib/interfaces/server'; -import { Note, NoteStatus } from '@aztec/stdlib/note'; +import { Note, NoteDao, NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId } from '@aztec/stdlib/trees'; import type { TxHash } from '@aztec/stdlib/tx'; -import { type AnchorBlockDataProvider, NoteDao, type NoteDataProvider } from '../storage/index.js'; +import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; +import type { NoteDataProvider } from '../storage/note_data_provider/note_data_provider.js'; export class NoteService { constructor( diff --git a/yarn-project/pxe/src/private_kernel/private_kernel_oracle_impl.ts b/yarn-project/pxe/src/private_kernel/private_kernel_oracle_impl.ts index d50fcbc7cf37..b1c5f9342f66 100644 --- a/yarn-project/pxe/src/private_kernel/private_kernel_oracle_impl.ts +++ b/yarn-project/pxe/src/private_kernel/private_kernel_oracle_impl.ts @@ -18,7 +18,7 @@ import { UpdatedClassIdHints } from '@aztec/stdlib/kernel'; import type { NullifierMembershipWitness } from '@aztec/stdlib/trees'; import type { VerificationKeyAsFields } from '@aztec/stdlib/vks'; -import type { ContractDataProvider } from '../storage/index.js'; +import type { ContractDataProvider } from '../storage/contract_data_provider/contract_data_provider.js'; import type { PrivateKernelOracle } from './private_kernel_oracle.js'; // TODO: Block number should not be "latest". diff --git a/yarn-project/pxe/src/public_storage/public_storage_service.ts b/yarn-project/pxe/src/public_storage/public_storage_service.ts index 158f66d6852b..42666cc24ce0 100644 --- a/yarn-project/pxe/src/public_storage/public_storage_service.ts +++ b/yarn-project/pxe/src/public_storage/public_storage_service.ts @@ -3,7 +3,7 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { BlockParameter } from '@aztec/stdlib/block'; import type { AztecNode } from '@aztec/stdlib/interfaces/server'; -import type { AnchorBlockDataProvider } from '../storage/index.js'; +import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; export class PublicStorageService { constructor( diff --git a/yarn-project/pxe/src/pxe.test.ts b/yarn-project/pxe/src/pxe.test.ts index ff2261c085fd..f9ec845de38e 100644 --- a/yarn-project/pxe/src/pxe.test.ts +++ b/yarn-project/pxe/src/pxe.test.ts @@ -25,7 +25,7 @@ import type { MockProxy } from 'jest-mock-extended/lib/Mock.js'; import type { PXEConfig } from './config/index.js'; import { PXE, type PackedPrivateEvent } from './pxe.js'; -import { PrivateEventDataProvider } from './storage/index.js'; +import { PrivateEventDataProvider } from './storage/private_event_data_provider/private_event_data_provider.js'; describe('PXE', () => { let pxe: PXE; diff --git a/yarn-project/pxe/src/tree_membership/tree_membership_service.ts b/yarn-project/pxe/src/tree_membership/tree_membership_service.ts index a2c838bb3211..57ec5b131077 100644 --- a/yarn-project/pxe/src/tree_membership/tree_membership_service.ts +++ b/yarn-project/pxe/src/tree_membership/tree_membership_service.ts @@ -7,7 +7,7 @@ import type { AztecNode } from '@aztec/stdlib/interfaces/server'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; -import type { AnchorBlockDataProvider } from '../storage/index.js'; +import type { AnchorBlockDataProvider } from '../storage/anchor_block_data_provider/anchor_block_data_provider.js'; export class TreeMembershipService { constructor( From e03955a46e5cb6487f22ffec50b0a112846fe3a2 Mon Sep 17 00:00:00 2001 From: mverzilli Date: Mon, 22 Dec 2025 10:07:16 +0000 Subject: [PATCH 067/140] POC: optimize playground build --- playground/dist-size-report.txt | 158 ++++++++++++++++++ playground/package.json | 1 + .../simple_token_contract-SimpleToken.json.gz | Bin 0 -> 2469266 bytes .../components/home/components/Landing.tsx | 3 +- .../navbar/components/ContractSelector.tsx | 5 +- playground/src/utils/artifacts.ts | 27 +++ playground/vite.config.ts | 55 ++++++ playground/yarn.lock | 8 + 8 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 playground/dist-size-report.txt create mode 100644 playground/src/assets/artifacts/simple_token_contract-SimpleToken.json.gz create mode 100644 playground/src/utils/artifacts.ts diff --git a/playground/dist-size-report.txt b/playground/dist-size-report.txt new file mode 100644 index 000000000000..61f1b64b5702 --- /dev/null +++ b/playground/dist-size-report.txt @@ -0,0 +1,158 @@ +Playground dist size report + +Root: playground/dist +Total files: 146 + +Total size (bytes): 72324060 +Total size (MB): 68.97 + +Index chunks: +- index-CnJ6M4Ah.js 103383 bytes 100.96 KB (gz 31910 bytes, 31.16 KB) + +All files sorted by size (desc): +assets/barretenberg-threads-DRt9GRsj.js 3773306 bytes 3684.87 KB (gz 2849578 bytes, 2782.79 KB) +assets/barretenberg-DcZk41wH.js 3729758 bytes 3642.34 KB (gz 2813626 bytes, 2747.68 KB) +assets/acvm_js_bg-BVl7hUWg.wasm 3439940 bytes 3359.32 KB (gz 863519 bytes, 843.28 KB) +assets/background-Cl9bH3Bs.jpg 2492051 bytes 2433.64 KB +assets/simple_token_contract-SimpleToken.json-RvWCFomg.gz 2469266 bytes 2411.39 KB +assets/aztec-protocol-contracts-artifact-ContractClassRegistry-BBeWROFW.js 1770649 bytes 1729.15 KB (gz 825230 bytes, 805.89 KB) +assets/aztec-accounts-artifact-SchnorrAccount-YNB89-8W.js 1641833 bytes 1603.35 KB (gz 715893 bytes, 699.11 KB) +assets/aztec-accounts-artifact-EcdsaKAccount-BF6YaYHG.js 1559187 bytes 1522.64 KB (gz 656743 bytes, 641.35 KB) +assets/aztec-accounts-artifact-EcdsaRAccount-C8hGq9ZV.js 1558235 bytes 1521.71 KB (gz 656529 bytes, 641.14 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset-BWhrFMNm.js 1375199 bytes 1342.97 KB (gz 846844 bytes, 827.00 KB) +assets/aztec-protocol-contracts-artifact-FeeJuice-Pubcs6IO.js 1094850 bytes 1069.19 KB (gz 375678 bytes, 366.87 KB) +assets/aztec-noir-contracts.js-artifact-private_voting_contract-PrivateVoting-BRDJhizH.js 1015593 bytes 991.79 KB (gz 332753 bytes, 324.95 KB) +assets/aztec-protocol-contracts-artifact-AuthRegistry-CNdoqbtP.js 981763 bytes 958.75 KB (gz 322114 bytes, 314.56 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_32_32_32_32_32_32_32_32-DKP4J69B.js 955998 bytes 933.59 KB (gz 593765 bytes, 579.85 KB) +assets/aztec-protocol-contracts-artifact-ContractInstanceRegistry-D8znTDoz.js 940821 bytes 918.77 KB (gz 283728 bytes, 277.08 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_16_4_4_64_64_64-BfvqObP9.js 864379 bytes 844.12 KB (gz 530714 bytes, 518.28 KB) +assets/aztec-accounts-artifact-SimulatedAccount-CIQ6XRMp.js 859927 bytes 839.77 KB (gz 294134 bytes, 287.24 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_16_4_4_64_4_64-CnvLcdxj.js 832788 bytes 813.27 KB (gz 509146 bytes, 497.21 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_16_4_4_4_64_64-DixL7oss.js 818621 bytes 799.43 KB (gz 501692 bytes, 489.93 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_16_4_4_64_64_64-DWyJFczb.js 807379 bytes 788.46 KB (gz 500345 bytes, 488.62 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_4_4_4_64_64_64-BzJMWDJT.js 806826 bytes 787.92 KB (gz 491511 bytes, 479.99 KB) +assets/aztec-protocol-contracts-artifact-MultiCallEntrypoint-BRaDr2d4.js 791018 bytes 772.48 KB (gz 272709 bytes, 266.32 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_16_4_4_64_64_4-hG9hanZU.js 790224 bytes 771.70 KB (gz 485271 bytes, 473.90 KB) +assets/noirc_abi_wasm_bg-BtVdkQkw.wasm 784563 bytes 766.17 KB (gz 266383 bytes, 260.14 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_16_4_4_4_4_64-Dru2Ybjw.js 782633 bytes 764.29 KB (gz 480823 bytes, 469.55 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_16_4_4_64_4_64-BMZcwTyR.js 776144 bytes 757.95 KB (gz 485210 bytes, 473.84 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_4_4_4_64_4_64-BmMBsjSa.js 775339 bytes 757.17 KB (gz 478011 bytes, 466.81 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_16_4_4_4_64_64-_d9wEd1Q.js 764168 bytes 746.26 KB (gz 474785 bytes, 463.66 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_4_4_4_4_64_64-mJFaBmD_.js 763843 bytes 745.94 KB (gz 469619 bytes, 458.61 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_16_4_4_64_4_4-B2w6DEel.js 759221 bytes 741.43 KB (gz 468652 bytes, 457.67 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_4_4_4_64_64_64-DrZlHkqf.js 751709 bytes 734.09 KB (gz 456515 bytes, 445.82 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_16_4_4_4_64_4-DOmbViYP.js 744517 bytes 727.07 KB (gz 455572 bytes, 444.89 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_16_16_16_16_16_16_16_16_16-DfEAhEVq.js 741119 bytes 723.75 KB (gz 453972 bytes, 443.33 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_16_4_4_64_64_4-D9cCBskY.js 734016 bytes 716.81 KB (gz 454540 bytes, 443.89 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_4_4_4_64_64_4-Bj3-2jvP.js 733035 bytes 715.85 KB (gz 449568 bytes, 439.03 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_16_4_4_4_4_64-Csd5llbV.js 727735 bytes 710.68 KB (gz 453168 bytes, 442.55 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_4_4_4_4_4_64-1v-IAR72.js 726746 bytes 709.71 KB (gz 445263 bytes, 434.83 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_4_4_4_64_4_64-CfUkGaS7.js 718045 bytes 701.22 KB (gz 439790 bytes, 429.48 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_16_4_4_4_4_4-KSkwa-av.js 708325 bytes 691.72 KB (gz 434248 bytes, 424.07 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_4_4_4_4_64_64-m22w29zk.js 707271 bytes 690.69 KB (gz 434471 bytes, 424.29 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_16_4_4_64_4_4-DM_LT4x7.js 702424 bytes 685.96 KB (gz 433542 bytes, 423.38 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_4_4_4_64_4_4-Cv52Ph4k.js 702223 bytes 685.76 KB (gz 424991 bytes, 415.03 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_16_4_4_4_64_4-ctnnlFyj.js 689039 bytes 672.89 KB (gz 428261 bytes, 418.22 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_4_4_4_4_64_4-B0QXJPBc.js 688382 bytes 672.25 KB (gz 424344 bytes, 414.40 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_4_4_4_64_64_4-Cq7jaT5C.js 677237 bytes 661.36 KB (gz 415336 bytes, 405.60 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_4_4_4_4_4_64-CVcmB_I9.js 670455 bytes 654.74 KB (gz 402614 bytes, 393.18 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_0_64_0_0_0_0_0_0_0-CF_YCyt4.js 666358 bytes 650.74 KB (gz 356037 bytes, 347.69 KB) +assets/aztec-noir-contracts.js-artifact-sponsored_fpc_contract-SponsoredFPC-Buujykeh.js 660854 bytes 645.37 KB (gz 206062 bytes, 201.23 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_0_0_0_64_0_0_0_0_0-Dns7UOFT.js 660736 bytes 645.25 KB (gz 386530 bytes, 377.47 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_16_4_4_4_4_4-A5En_R2v.js 653527 bytes 638.21 KB (gz 404443 bytes, 394.96 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_16_32_4_4_4_4_4_4-nb92BYSM.js 653060 bytes 637.75 KB (gz 396946 bytes, 387.64 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_4_4_4_64_4_4-Cs9er_uW.js 643983 bytes 628.89 KB (gz 392953 bytes, 383.74 KB) +assets/sponsored_fpc_contract-SponsoredFPC-5V3MWnPw.js 634109 bytes 619.25 KB (gz 202844 bytes, 198.09 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_4_4_4_4_64_4-CoPE0bz0.js 633567 bytes 618.72 KB (gz 387838 bytes, 378.75 KB) +assets/aztec-protocol-contracts-artifact-Router-BDLvVIPW.js 620850 bytes 606.30 KB (gz 196770 bytes, 192.16 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_tail_to_public-aTS90HEI.js 606319 bytes 592.11 KB (gz 370413 bytes, 361.73 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_32_4_32_4_4_4_4_4_4-CxnnZmOj.js 596843 bytes 582.85 KB (gz 361045 bytes, 352.58 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_4_4_4_4_4_4_4_4_4-CGhceIs6.js 573324 bytes 559.89 KB (gz 344687 bytes, 336.61 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_0_0_0_0_0_64_0_0_0-Bl1qPUGb.js 505731 bytes 493.88 KB (gz 297109 bytes, 290.15 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_inner-4e7V3_jw.js 415744 bytes 406.00 KB (gz 232824 bytes, 227.37 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_0_0_64_0_0_0_0_0_0-CKrUDhfg.js 397017 bytes 387.71 KB (gz 210816 bytes, 205.88 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_64_0_0_0_0_0_0_0_0-B6vkTgYM.js 394015 bytes 384.78 KB (gz 206546 bytes, 201.71 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_0_0_0_0_64_0_0_0_0-CYWtVvV3.js 381751 bytes 372.80 KB (gz 195089 bytes, 190.52 KB) +assets/aztec-node_modules-qdV_Ydc7.js 304165 bytes 297.04 KB (gz 100997 bytes, 98.63 KB) +assets/mui-DkgiYpzU.js 288998 bytes 282.22 KB (gz 83755 bytes, 81.79 KB) +assets/aztec-stdlib-Bl4v5FKz.js 288624 bytes 281.86 KB (gz 63242 bytes, 61.76 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_init-1i_y8f2P.js 256606 bytes 250.59 KB (gz 140730 bytes, 137.43 KB) +assets/vendor-CqFsLK9R.js 253075 bytes 247.14 KB (gz 80530 bytes, 78.64 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated-D7t3fTY6.js 214354 bytes 209.33 KB (gz 99085 bytes, 96.76 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_tail-C4qsDye5.js 206389 bytes 201.55 KB (gz 109222 bytes, 106.66 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_32_32_32_32_32_32_32_32-Cv1i09tZ.js 195961 bytes 191.37 KB (gz 92914 bytes, 90.74 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_16_4_4_4_4_4-DoELBQkM.js 185139 bytes 180.80 KB (gz 86365 bytes, 84.34 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_16_4_4_64_4_4-D624oRfj.js 185058 bytes 180.72 KB (gz 86412 bytes, 84.39 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_16_4_4_4_64_4-DTQqID_C.js 185047 bytes 180.71 KB (gz 86442 bytes, 84.42 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_16_4_4_4_4_64-6CAC01bX.js 185042 bytes 180.71 KB (gz 86326 bytes, 84.30 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_16_4_4_64_64_4-DFfUytQS.js 184984 bytes 180.65 KB (gz 86272 bytes, 84.25 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_16_4_4_64_4_64-BK24Dmdo.js 184978 bytes 180.64 KB (gz 88011 bytes, 85.95 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_16_4_4_4_64_64-C0-o1Oob.js 184966 bytes 180.63 KB (gz 86305 bytes, 84.28 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_16_4_4_64_64_64-zYFKQEIY.js 184793 bytes 180.46 KB (gz 86158 bytes, 84.14 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_16_16_16_16_16_16_16_16_16-DW7BEQqv.js 184601 bytes 180.27 KB (gz 88486 bytes, 86.41 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_16_4_4_4_4_4-Dvg7UVVn.js 181034 bytes 176.79 KB (gz 90792 bytes, 88.66 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_16_4_4_64_4_4-BGpUqE6G.js 181018 bytes 176.78 KB (gz 90768 bytes, 88.64 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_16_4_4_4_64_4-DNY-iSSw.js 181005 bytes 176.76 KB (gz 90638 bytes, 88.51 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_16_4_4_4_4_64-OkiHJB3M.js 181002 bytes 176.76 KB (gz 90688 bytes, 88.56 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_16_4_4_64_64_4-Bf8ELwZH.js 180918 bytes 176.68 KB (gz 90512 bytes, 88.39 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_16_4_4_64_4_64-BI37IZPQ.js 180910 bytes 176.67 KB (gz 90487 bytes, 88.37 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_16_4_4_4_64_64-k6kjixI7.js 180902 bytes 176.66 KB (gz 90591 bytes, 88.47 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_16_4_4_64_64_64-BxxlFqgb.js 180757 bytes 176.52 KB (gz 90535 bytes, 88.41 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_4_4_4_4_4_4-DcFCXpnc.js 180562 bytes 176.33 KB (gz 92423 bytes, 90.26 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_4_4_4_4_64_4-B1z1BO5B.js 180458 bytes 176.23 KB (gz 92079 bytes, 89.92 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_4_4_4_4_4_64-CZI1IIBh.js 180450 bytes 176.22 KB (gz 90722 bytes, 88.60 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_4_4_4_64_4_4-DeWKuTOw.js 180442 bytes 176.21 KB (gz 92220 bytes, 90.06 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_4_4_4_64_4_64-DT98BuIX.js 180397 bytes 176.17 KB (gz 90766 bytes, 88.64 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_4_4_4_4_64_64-CpNWnN7j.js 180390 bytes 176.16 KB (gz 90790 bytes, 88.66 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_4_4_4_64_64_4-CLcpkeJR.js 180390 bytes 176.16 KB (gz 92201 bytes, 90.04 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_16_32_4_4_4_64_64_64-Ce3-a-sj.js 180185 bytes 175.96 KB (gz 90644 bytes, 88.52 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_4_4_4_4_4_4-MkSMNgSE.js 176080 bytes 171.95 KB (gz 86375 bytes, 84.35 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_4_4_4_4_64_4-CLM5cOuX.js 176036 bytes 171.91 KB (gz 85097 bytes, 83.10 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_4_4_4_64_4_4-DPoLJv9K.js 176028 bytes 171.90 KB (gz 86643 bytes, 84.61 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_4_4_4_4_4_64-CxzaX-qB.js 176021 bytes 171.90 KB (gz 86711 bytes, 84.68 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_4_4_4_64_64_4-eMFG0EGS.js 175960 bytes 171.84 KB (gz 85132 bytes, 83.14 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_4_4_4_64_4_64-BGRzXYgu.js 175945 bytes 171.82 KB (gz 86254 bytes, 84.23 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_4_4_4_4_64_64-D_NuaEpb.js 175936 bytes 171.81 KB (gz 86456 bytes, 84.43 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_32_4_32_4_4_4_64_64_64-CUmy7AHu.js 175744 bytes 171.62 KB (gz 85026 bytes, 83.03 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_4_4_4_4_4_4_4_4_4-DNaOQFHA.js 174314 bytes 170.23 KB (gz 84268 bytes, 82.29 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_inner_simulated-DAhw0V6l.js 160678 bytes 156.91 KB (gz 74149 bytes, 72.41 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_0_0_0_64_0_0_0_0_0-BTTq3R0-.js 142257 bytes 138.92 KB (gz 56643 bytes, 55.32 KB) +assets/react-vendor-BMhLz0tS.js 139008 bytes 135.75 KB (gz 44406 bytes, 43.37 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_0_64_0_0_0_0_0_0_0-pMCauWoA.js 138248 bytes 135.01 KB (gz 53909 bytes, 52.65 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_tail_to_public_simulated-BpoQUAiE.js 124394 bytes 121.48 KB (gz 64649 bytes, 63.13 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_init_simulated-H9xwmdOq.js 124154 bytes 121.24 KB (gz 62839 bytes, 61.37 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_0_0_0_0_0_64_0_0_0-DSUKeBN0.js 117792 bytes 115.03 KB (gz 50254 bytes, 49.08 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_0_0_0_0_64_0_0_0_0-BFx6O479.js 111584 bytes 108.97 KB (gz 43922 bytes, 42.89 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_0_0_64_0_0_0_0_0_0-DuwBwxhH.js 110947 bytes 108.35 KB (gz 45517 bytes, 44.45 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_reset_simulated_64_0_0_0_0_0_0_0_0-DoqDFcct.js 110937 bytes 108.34 KB (gz 45619 bytes, 44.55 KB) +assets/aztec-pxe-Di80v2yK.js 107600 bytes 105.08 KB (gz 28523 bytes, 27.85 KB) +assets/index-CnJ6M4Ah.js 103383 bytes 100.96 KB (gz 31910 bytes, 31.16 KB) +assets/aztec-noir-protocol-circuits-types-artifact-private_kernel_tail_simulated-DAgpA0KM.js 91615 bytes 89.47 KB (gz 45635 bytes, 44.57 KB) +assets/aztec-noir-protocol-circuits-types-DO3Pj3N_.js 91268 bytes 89.13 KB (gz 15277 bytes, 14.92 KB) +assets/aztec-bb-prover-BDOf2to8.js 83870 bytes 81.90 KB (gz 13445 bytes, 13.13 KB) +assets/aztec-noir-protocol-circuits-types-artifact-hiding_kernel_to_public-DsJhOXAx.js 77892 bytes 76.07 KB (gz 28815 bytes, 28.14 KB) +assets/sponsored_fpc_contract-SponsoredFPC-Cdl-WRJN.js 75299 bytes 73.53 KB (gz 18901 bytes, 18.46 KB) +assets/sponsored_fpc_contract-SponsoredFPC-ClOekBn1.js 74523 bytes 72.78 KB (gz 19110 bytes, 18.66 KB) +assets/aztec-foundation-BbSUXejA.js 63870 bytes 62.37 KB (gz 19049 bytes, 18.60 KB) +assets/aztec-noir-protocol-circuits-types-artifact-hiding_kernel_to_rollup-TSsV8hV7.js 50930 bytes 49.74 KB (gz 17753 bytes, 17.34 KB) +assets/react-extras-BOqpZmvl.js 21684 bytes 21.18 KB (gz 7433 bytes, 7.26 KB) +assets/aztec-aztec.js-B02K1Bxl.js 21110 bytes 20.62 KB (gz 6284 bytes, 6.14 KB) +assets/emotion-DRb-jOcO.js 19357 bytes 18.90 KB (gz 8250 bytes, 8.06 KB) +assets/welcome_icon-C3mKAOYo.svg 16902 bytes 16.51 KB (gz 2549 bytes, 2.49 KB) +assets/aztec-simulator-Cnlv9VVF.js 14715 bytes 14.37 KB (gz 5464 bytes, 5.34 KB) +assets/main.worker-BoucArFf.js 10483 bytes 10.24 KB (gz 4181 bytes, 4.08 KB) +assets/aztec-blob-lib-Dli2IdLR.js 9254 bytes 9.04 KB (gz 2803 bytes, 2.74 KB) +assets/aztec-kv-store-CNzbTEb0.js 9029 bytes 8.82 KB (gz 3003 bytes, 2.93 KB) +assets/aztec-wallet-sdk-BIA2eO6r.js 7073 bytes 6.91 KB (gz 2734 bytes, 2.67 KB) +assets/aztec_logo-CerwGmK3.png 6643 bytes 6.49 KB +assets/thread.worker-sqzKJNcR.js 6510 bytes 6.36 KB (gz 2867 bytes, 2.80 KB) +assets/aztec-accounts-CRZor1xz.js 5933 bytes 5.79 KB (gz 2023 bytes, 1.98 KB) +assets/aztec-entrypoints-CMc8u70S.js 5104 bytes 4.98 KB (gz 1642 bytes, 1.60 KB) +assets/aztec-constants-Cmmsa16R.js 5067 bytes 4.95 KB (gz 2419 bytes, 2.36 KB) +assets/aztec-key-store-DUYUcHV5.js 4785 bytes 4.67 KB (gz 1254 bytes, 1.22 KB) +assets/aztec-protocol-contracts-DLJAItLp.js 4726 bytes 4.62 KB (gz 1404 bytes, 1.37 KB) +assets/logo_light-BVkIN6WD.svg 4256 bytes 4.16 KB (gz 1907 bytes, 1.86 KB) +index.html 3460 bytes 3.38 KB (gz 1136 bytes, 1.11 KB) +assets/aztec-ethereum-CJwVckvU.js 2206 bytes 2.15 KB (gz 557 bytes, 0.54 KB) +assets/toolpad-DzKetlQC.js 2135 bytes 2.08 KB (gz 1062 bytes, 1.04 KB) +assets/aztec-noir-contracts.js-DRPHc65K.js 2127 bytes 2.08 KB (gz 759 bytes, 0.74 KB) diff --git a/playground/package.json b/playground/package.json index af006849d937..d104346acd00 100644 --- a/playground/package.json +++ b/playground/package.json @@ -34,6 +34,7 @@ "@uidotdev/usehooks": "^2.4.1", "buffer-json": "^2.0.0", "nosleep.js": "^0.12.0", + "pako": "^2.1.0", "react": "^18.3.1", "react-confetti": "^6.4.0", "react-dom": "^18.3.1", diff --git a/playground/src/assets/artifacts/simple_token_contract-SimpleToken.json.gz b/playground/src/assets/artifacts/simple_token_contract-SimpleToken.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..3e7a79dbd457ec1e9d87ddd95e43a262b2821996 GIT binary patch literal 2469266 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%0A&~Ty!{HE+jtD4>^Vg zGSv<`%j=$M+8G8D6r(2U1j3}tTMa)D*JR7DI6 z*lo(&!gFRmH`y<`rs1J}5|=mFudC-;8pPL_&~ z?P9C+LYlEB-5ys5{hv*+=F7!&Pj%RaOH-}5;!_cvJO}+|<<`ty%=jEF<(_h^G7170 zp&PRB z>-+0dw|Yv>=u3Rj;m^7T6_+h(=(jW zZJsAMN(b^%l!PYRdBB^Vf|7AIKX7Dk+DGOLi&<$>L9F7%vn(WjF-EmhbAY)xQKG`O z0U?Td0t?mYkzSk0-0!-fwaFN6uQw#jz>PO+G9!Y!Cn4qcij5G7+SGnL&hquOA(JV~ zBke85U`DnJtnzgN9$%a7ZrMZ}B{R}9!k5{O6%t0wN4F7_KGMCcwU$dL-*t?Fd~u9yczdK~{7?X6K%Bofrh^HO z`gTpLtiK73nkd_HO#bzki{v?Y9vo$4IbQ3;N^iRAc$5Dp#!nV)4F9#Hjce|>& z;5LKyiQfe~Apnkww@=`wJ^kKYso<-bffodtnM#Pwz{)ZWm-TXaH^XtZA?Isy3@CDS zyKLdSa>f{qn`J*DcQADN8#m8(qM>x+?78zwZ~BG348EjWC&!(ZLA(L+^Psdoj=g0o zKt%24AO_>QiM0(lzf4n5FphyiO5rUQxCbl*+2lNSP6>nKyWS!}`WzR%!~tGpKjX*x zvD7>o29 zW2ygQEF2i`@xL*a*Kdr)|8S+-hbv9fC%(l;;nM|VL*g&=CCFaYBND`0-F`Xu?RK={ zZFug|wbOacjaB6Wn9Y%UfR!dIgkS#^Lp=RY$UlvpK2!mhS^i>(Y{m@|uI2ns{Bvr| zZ~QaVk?Xq7#y9?n4}bL*C{EYY|LTS8S1-)kS=_#QVT_iKUI+kIe)Yog(F=1t^ZfFs z7hIqh!UX7r^{W@gsseJy34^!sP<DrzOoPjFiDFS$45t~Eq){ZFq3k|w74c9 zd3%4SY+6*`yL3{RK4_bpiPvg{81FZ(xbdRE9H$_6eoCy0aupib|Hc&&WcgqQ|6oQ3 zm`O$vOi=cVnHA;%Syl>w89Z3Om|;JxKQq}6X3{Tam@j7B%lg4g(6USMkH*&!SWqKn z0N*Wv&MX}6M`vz1#A&wD>7;mjs}F=QW&fiY&= zzjL&IJrQ9ykElat0~{fUQqUTU9i z@Qtmke|2X3o!jwF2Bs6)=fbejlz4yoVrh4R@VhL2l$XmkzHZPa0V}2z!gqa+le1BH zN1WJ>!)IQL_6I|^YOr~&%h;=X1!kl+h2yP%)`(1~*ZTjhvyc`)D~Nyl#I!y*huh!v zEl7w&OfrnWxJvRHSGioFN1^}3Rr-H&A$Csh&p-!Y_pOe|=J^W$#I&G7JsoX_Ki9U+ zPdv^;n?gW8Yectlms)jvXBW^b1=A=Ktbxcqv5Mh=1d8C4dZV{Kez=U0lK>{n;0wx%%(8V_@8a@A}qf z-|KgM%iqmNtPA2l>s$CoUone9fBFsyK&~ySdH7f88;IMgx}tnW5S!upS;GrX;-O29 zRo^Q0&g_@PQhsCSlKnW^@Vic-SDpA>r?5Wj6v+E`ox)={MEqT+7@0U+FGC?kJMjDG z*Y8_o_M3OWD7v@{%)i%Dj_`LqMYBKQhs$vt@5iI3psp@__8oZR&Elp{Tp!xU9Wjb; z%##sA(|g6Nmhq0?Tpi@hf7aph^%Ceq4Y0j==RYw|SRk|!I%*B_Xx^J>>sRx#VkIsG z^*hFvBYxKq*F3#1!lI8~Z2Nz)X0;MUWk;J%?xg0uDwD;*a`44RDa`nTQ4B#-gQc9P zL>^OIAirz=Wp;}W_Fi&!`iT>@&z)ijhwQ1>&TCNI-8+T&(YzDW1I?RI^U6BxKYE?L z?vKu*^qI67$$_8eAGX0rBGx|~(I|hhUCN$pVsDgLgyr^O^AkTr72+ z_-OwZhsz7fc18aa0$rAU7g-+9pG=Y8Rq?*=FicP-BP_J)X;FAHb#w4D{UxpiRW_Zo?ku?3k?ER$C3g#ZF8A z%vY?@9c1!9epQS$a0#SU%Q1WV|LBf1v#PFB)XNr!v*hG+^%M7Bkb;vj<)6MAF83Yx zRL`$am3-HWlwADV2XKw}`pWzVH_ewv8V+#)PGBDC#cQX5q_#KUk#y!iRUzMR@zFQ= ze$5um_^y-ES3t(C3oX!sr`)?+axZ>jZr$>+0h0Ax$@{MYy=0^X;+zNuL`#Jh|%#A(WZ_KTDC-FOww_2*z3`QB$ zCFT@kzA?7~@b$68i|T47AA1G#>e;IS0@NbsZSiNFl>Np!=N0r-TZ_j^%b&K$Es zX>#!RM}Oo)C#VVL#i?@#xWU%sXPuFbcw1i5_uoDMn|Ocs0ciJ*mk|2fcbWKI<9%S< z$^UNJ`JJ!DuXZ{=^EId=z~MA3HQa=siDMUoInn zjrO{ZfEY*zb(`;wxZHZxF}xsT7wdF)O$h%J>zwj$tdnZdr!?f^1-p(sH1#jUuZt*u2lfD5?z>l` z+2r1*_q)DJeSCp;XMml(4%hRNYd?KAo$I4j)W-7a^v+Wq@?CPUGI1Bme z?54PW_l^icHii$8<1*vxw5};{UbZ*?i7{Vg{Vm=vUoHt`-{k__>J4g9DMO2a3Oezg z*L%w6m%|(Fw?E(R&EGmv{oVHhdiu}4m*98b%SZS9?t3|_zkM$Rr(4bc+4mCt=sr_D z&D$P5uX7y29~u=e|PQGr9ZX==ajS=Xdai>+- z@`lm>2Q#F^>sL(utVIfAbg0#@{y1z715)@8Gr6YKQbY;yZa370czHo3Byga(T;617EzN+oKRDj_{lR?3Xfs_e)K4HH&NZS)0Ysj1A*JB1znch3d&D zfVRg)CW_O9bf+$d&BsR)4VSYB@pO$?4PsrJNAI4?3@1IIL-PSr`bMZFMY;N!MJkg zHqB(!FE{YBfGEsWpLfPlq3Q7$QtMK{nbBMT6~E}u%TPH2)Q@$`E%Li?u40Ssvf~=v z^Kw4;>UryR`}&+axguPod+gqgbD3B-_~*Rb=B7MctdsecHhRZ?GV&zPy^UTpC6sTZ z9qT$O-h@q0&nI#?RQ;6FTO~s4678-Nmf<|>XnW^LHbE;359K5&x}Z*R@o`yNbdxXE zfs~#uzqu@}#mJ;P{!-}3*;~8!$R2mH&uz^2oeQb^P^pv(&a!)gapO3$2-ee5h@m-T zcGl6bm7-0Ulj37=mkJ+3wBdqLlMtViRXidYUVP#u?vFYOF`t6GkRq2Mhw(rax6Bhu zf{>xx&a)Fn^AD<9s*x@HQ0t~qcOk-OqGLoK+o}>_RKtM3QL)eFbjT2% z<)F!VHefv#Wr>ZzFf5$|s8q#ty~f{oymhDZyq@R=oy#mu;MGLux5WY@GLeg~ST{Op zUKoOqGz&EEM4qT(d35@Qa3+e$$x7oCgBC1EAdC*bc?ty}B=>Y&kK5^LeU5-rz8)Hc z^W<2jizg+^dg5Yv^+S2d=LyyF8|wBPZR5}Vc>&yiHu-HZxpFAP($0m=VaLkbBqXtT zQ+wiEvxfo)uS5f_02-Lc@*9l!?h&+9z^lsC=B;}+O@nxcWm1;Jpe3^)bKd%xq z_N^M4WU+^2HBB~;6qj_EvbRQ&DKz_jF^=2!FOssH5gn{g%GA*K2d_ zybZBD9AD;ZqSq1eHrGImaNHESbR{g;)(t)L#;5Mn($PRX9JsR(uhrd3WNUpi4_{9< z!H(D4=&)1Ddsj@GwSTcjYQBnIdccRS7W9NoOFM0*oVIYeiWv%#*Aic}DS)VBp+C|5 z23KMX;_l*>YO>sOhABt_3mcCZPvT&6tKXY-K?@1K4^fM;+_^wU}i4W2Y zkf4yhpo{C5IVFYqOk%q%FP*Wf}wXqm5s9iIAY_q0=v3rwiC5kK8^ zI=`UC1-o^cd?#Kiqx#`0BzQa<0&T08k?Zf4xSda|hlLGIRHD#8GYM@%Z~)`YrsHWb8QnE~=^LyyxDDlN zwiKp{Z9ZJc2#1s~o5|Nq;@2%G!j6PrMW!JtTx@7SS#k#z43tVY=p@hKuAMAAwz$Ne zz&VzU7Q)*sK)6Z1;Ic%cL{(rmPm^kZQFM6;4IBp8pjtnljV}VLuYJ78ybE;Hh|CrX z=X%7O8Gox=`g&b+oz>8CNLKN@p`|NoeX1+6_FB{1CC8hAj(6r@t=EfBl2C%5alA)& z33L02?;Y#l_Cu%RZJSv1eTic;pGy{puAVB=w>^IuIUM=~<){m6PA#9$RFc59b%U+} zQ{pbur`iW@NlN;m1z%r_KwXTPOi=Sm#|NrqckHZ)@vWpLR{IYVD;I&5^LeKvOefS?5Q}PSBAJ z=|1{*QjOMf9-KFR*5u=I1*{QjCiy{hIYSg$b3LoOlc_IfM#j9^7N}JzvMn=UMlXaH2R7*oulyrTmv#b z#Fv@qjwDFz5;f(8LHAAo=hk63jW>4dPGo>F?DR<&UY6%{^l(b^p+t6{4agS^qO6PI z3&*o)g#>xO)4hzZZMLzdb<%cyKXO%GP2zIOKqYuc7lg?CpiJpbcGTG^FY{4o!yY=! zDa%v4VyD?>jZ7^p;f9z_y zG|%Pvs9Q3(Q;a!uhR}%zaTS+7Jw%qJ#~oWqPt0;V zrji~>E{)A_DF>V(E+J#%C$pr02x2G0o+0C*CvXU7U)y17`s=+hu>z1NU;s$Ci_WF* zA^+zy419*d&(CKe6ob#QiTTVy3O?o-J15wD_o=Zl6Zi~+&(CKxtb)(1?fbp=&-eVY z2dC&PC_Qo=6WNTspj61VO=g?yNr*ECJY`tN1f=+xB;E7Kmh%$=svE1Moc1v<--N7@9a)JBm zUgzP>$(47c6a^*`W})pKbbN{w#3clWurj@HGWS{swNS={Hwx%|#`39cX2}W8k&T6> zvBZx?rBlX7h1NQ;VnSbJej!slZZ841F!R0Ju)$fhuytR)nZsk&X@ZcuJZ zDIWT;Db;$Z-y%;W+p-(|#x-vTR^ABowuTte8|Yk5SXC4%o3C)J`03Wa^|i7Xl`3oc z`Enq&SM1%|na-V>MIb^EiI`x5Dn7L6ZB-akli#HzD<*Ng^?sk*ywvV5;(S6$Sl1B} zbZ8b8FJ#tNXuAQfxK$c@yzXUxJ2uXJdM*|sCSk!h<|C|TD_ z;aP3n2;2}{1q7#*9IiWaTs{VZdfUE)oC*TQLniYVmXf!Gt)DYS(Kn4a62{z9FA@lq7SRk%>Nqom0cs(4j!K&bV zhqs-X!zdYg4S~SP=F!QfvamI1deBcAf3s%d-ynA>cY~4ajaboJ?&?PiOZ!BuNOQ%{ z@N<6k0N$Lp-oWy>+oeayCAN;7=(r78C0IgT@U7U}s8ub*EqH?SzmRQ2NC$oq3t=(` zxPFMl#hLk^Yg-jfU=plhw{3hI&rQQ`70~0Yd&S8OQ28gKhU|7*#qEqjCiL_2?BW}g zu4|>ilTfy_fFopgfd3rG1)Qe^m&B3Jrd`nY^av}O`kvna=cgR-HF=kS$|{eA%C?wT zpRkGCAZ1Cf;5y)f{Xx0R7GvB-XCbp?Ce*8#Gf$%`1O#$ z7$@P3pbp#-Gq}gRF2OfwDubTj(HY|ffK@#zEbjxc>pPnrdt5013eSo{L> z_~m=9Ae_u5@Q_=r(}68Ug0;3%G9w^mPH)nsMXP*B10OsIJEows_kp^#iLKW2I9n>to|s z*o80B{OV~?`T`xNizn!>xf}3OmuGJKtoa1j$32C^-v}?4E~~$1y3HB)ZZN)`L#{2u zIF%#RmUn>1y2vtyMZjKDTX}`|4r(b2F*m2H^PNOkl3puT&*OB!9Xunckf`ynYYMbR z45C?Iz}X7T6ppmfnH%)f&nI=G)Kg$bYdp~c>x<{+ib+phKEJD*IV7|^OvdF8zqrdY zI^7}9Jh~!@s5_qY0u)mINr)TzQXtks9R!a0Xn6*^$YZ0F@$_}FU$TqH z>}3PvCjP|iiE>9;&)RiXskJnX?9?dv8XWL-OC*|yG-{jbw7SAlZs*9%T&+vK`{=@O z)@6ZfgwX{HshJvKEdkyu=8iy{bAJAVQ?P^W9OK}4d%idydRo=m&HB$dz!=Wi_Z)dP zRZ=;MggbhTW(ne3DRNc|Iq|KPcBnr2kg_4*%*r|??BYrTupHm>QsEunufUX@%G>5N z7#xb^OWF<8~xs+kDa_}ghuey_Z;_) zjlTC1rjYslzTCm#PaC@Jmv5V$GWTyA&ZV~4|zqI|WbTY3TZUGMz z^67Jm-;|)>lt)?|H*~9b;)zdc9)0?BVGX58oo=0WUOzXUp_8 zxm$ry5pm!d)UH&EXr#5?j2mldFYUe+JH-(WoM&Emz#6oJiJjy3jo!llh#E@PdMCBsNcujVt zwX=aYEaq0wYD%33RU+=@zqE!}D5^I&XMY0?p4j158a#{vQDCf<$+rz#6xxHWH9bvk z!-Afye3xLxa^*(~^yRb%X$-x1M{Vu~Nt~|v!NR6osPlsz?fo_ZIRn#%$aQ*OYkU8;$sP24wx)!>syp+|s=GzP5KmYtGAG@`{t`T8(@dh?ATz|HKWlsk zFtj8c=(mov^ldKYp3Pw(zYLfyXh;5s+g&ngzP*<(Hbw&a!3|ZMlLBnkZF8YK`LPM* z`L&70=DrUc48EHp@DDbC8xy|Iql}Tk{)~A||KZgNap605BFZAw*A}s5HvF|Ee{Ju- zwmkZ4GymG~e~!ESwcY>P4*RDq4300B`-z-??uCM)-+S4**H7DndVg__u;u*m8y#!4 z==8O3BgLja_nB{j`L&CE`qzfB@jq?QPaBOkKWz~EH$K|m_>fU!1S_ z$iIKvqU^V=RsP!Ee{J^g#W}m#?$5Y>e%9-YbBq=B-?sbt*CxPxj+wDZrt@A*2FJqT zI5iVs2@!qtHrzr~hfJuxj>eURl|iege5bCoqquOrQU#(R+6oLC384u3Jo`y;Wyj2? zT#GN{0gUNWq^a~{OxFPz(<_91jHyfCCSXi&!0V?#+j-RE+?z{7Jb^J~9_~||t_dQw zTsHEo$lRdwMz?OgSw?(GC+%gKW@Av~plCvhS&vxRZ|7PXQh^l%m51OEW(qC`O#tI2 z2i!)&fTaL3C*zURJZJPBdgn1qvvsOpEO0oD{ouram>&#@J5E8F-og6}v#@O%5i zAs57tzIH&%m4Did-!?BqzT?}@U}}Ay0nuU0hpTqbZTbANFL;KJG{Jw`T5Q&T+T`}f zHr+Yoi-CZ;$v@{t@6p$e$U*Y?6~fQCyQ=@w4e$ zBZLY5V}m~(_|C0cKDmEw$Nt;4%s*|x*nQ_gtt~(EfN$^8`s=^q z-|H^F^XTCYUUaID?Sxm?K2gB-D7a(hJi}f3T$$B>rgBq#e(s$&h8;;Sx)qXzEbzR_Sljrcl%3(!@ zyT8EqeCF=?0$#vf=fE`PvwhFE`(sM##}qfx=eNa|m+8@VK4BGo4Jxe#R(yPyOmkVx zKAKSIMJ6i?%!z@ASvq?ZwCuriDTt>uhhCtqBWXTy==t`|iKd|E#?JYy1W4j9VXH6i z0&50T9fcd6_oV^ds*+qaMhp-F|EWs}A^FH!S?<2bnn@AGH^&X1qsW}2j7~6t0bfC# z5n*1tyVA8^hWQmiVRmmgw9$1eABHfZQ-d~4*tt@xmv^)=_pb>~MOs8%-o%=Av6_DI z_z{<<1adJH&T~2zN<8BtbBWEgKpXF6nyAovXVJPV0mh0UlMT=JNLXU14A41>Nv#&q z#Tu9A{<)3WYyW65LL%U_ye)4(}(_72S-RNFfbCWn-t>=fim{vW;jWI))>U2Umd=PNh)1|Z=`)yNn`P=U*r+6f=Gc&|RQJjGAg9P+n`|J$q z1E25~<5lcYgXu&N^v`>!lefh5*iVw4EB&uN94-ja`x%FLulp(lN8hAQ!=cGtt(w52 ze)ruqq6#sRc=q~LTdi+Bn;miyVe>DSx5dF6qnskNn=y6~GR+juC0h5&p7oO4n-V4M zluu6KZ9ELUe35i?`_`hmKMf(>ukmTST6N8GO{(ut9Sg7f*;UU_IE6%bx4^Nh-@Hz} zA?kZ?&i^N`Q>!q6()4ivSlYXJIezjwvyJxzx-ozAI$L!8=5=1*J#QfXZ@n++uih6Q ziFNy*I&qKbw@#e@4&rxrt`YayK1BguaW(DOA820>K7lcz2 zX$P(uWAbxX;xhT&quf`0WlDM7DEgvnO5+&;q<-r%k%|0z2lsPzhtD2kteo~lB6D2) z*0bTMY_t!G$3=Vl?v-u#Pn{BeL8e{O*3W)Z=y$*A^4YJtC{$Jz|J$RyWy)UOxmFFc z3FAf)a(?!PvxHSy5r_QNqbwHjyI)f$kbBNC@wXlfqW{)|kwoqqSu*pldX#tOTO;L8 zFkZ*_b@AEf8`VGgolPNr_b78mHabVjx%Kc}`x=>&E@9;gLk&8Xi2^@)D8(o5s;M`fs1C z$(LMqBmb2X6hc|{t>=goniMN$)W4;x{Henub`mN4)D>Jmxq9^;aMe5d$!9{;Yz*&_ zs`C!{N#rWc$V|29oZylS(;$9s44(*@)_%VTf z_v4EvFKA;g3Yj=>bNu&Pxw0*a@qco$MC&IP%jzHBT&(x!+V&?G>k_9pH-2)lxbTyU zMeHG%#J{;%#Qe#{dJh$^6)|=1B*OXTVhyt!Jvxy!ebfIZj|?rvK^xmouAblXUQ6Hp za`ixe7Ag5BkL>l6Ka;(`xq6?HYgXp` z_Dw5@pL{*}Y_Gw_T?p#(t@Sh)ywR)nR!A~8PA3?5bot4FE3nVp<;t`_|Cjrd<5kB~ z1~WQ1Ff*-^|JE!kOU~oz7W%c159IvGgE$RAo7%5^Owr%`Jt-4Ak?8-O_g<3hH-E;u zL!Z7|&&Q|lfKeCuzWp!P5cHiY?TVm;pPDrgSNCd+K$ngim6LN2CjYH<=YO6X2W-3f zKg`U%?eeVLd1HUn(ecXuY9E!H%CkrRiy4>r>AU)_cmwaheJ?Tc$F^(4Cr48H&$w6H zPmZJFPtePw$e%f1-leCSXRqLE*D=x?DmGc}f3>C_9*-Q)JYSk?kS+i2Vc~?>BaXWtgr|zvC#{>mGuJNt840m2XrWOnj;55`aMqw-6{O45@ zUVM;E-gV}A!xohc!v@az)R~fHBD-HSHTaZIP`}*ZxqNFTJIa4+R0j2M@fc)qj@F-V zPV!fS72MBYcnfhp_5Sj@xmxzA@#tHEV+~2YeCzh6_HW%@G2sAk{!^>E`K?v$Vb`E0 zK6QIr_LHyo$^CTGkDb4-)ns~PJaghQdEb19GnK&o9Iow0$DHVI&56IYOpPaVjZ^27 z51~o=KfH$8Q{ER~)mw*GaCxzc#(7tB%GZh?Sa1C8yXHzm9Gcv#IPWb`*I^xBfXO+! zpBi5>af`Hv|L^>zvfIA3?iMAu1@{B{6t7iO`BQ59Lgr!rDHa#-Y_EFTi`(f#)J!jX zl{K&{w;RJdO9wn_he_XDc>Gf@*=|_idtcQ5YTvTY%TC|W!g7u)m-Ggl6o=5Cx`X&k0Ndz)^2U;1PBwoyndu+pnBV`GD;Z{5^=fZ` z*UX>Vnr^85%ws$gZFzC_{x|0>9+Gdawi36%Uamj39;5$kj!B5M@}K(b75i&1i`%C@ zyD)#|vSOhyn9Ez*ee>bP!n~~Ua+cK~CtrJsd~(T3Gx^k%-9Gin?$B?pB>Tygw5;bE zzp#Jmny%9By6`tw5)#JZD1U0JVYIS~3Hw$y*QWi$ zhwEQH95kjdjrbSy%iH?tyD{^!_TRd8`6+($JQWWZ`-OXduKiLdD5;?UC3?+!*42$X z+QnoprusNPblB)!Iq;@Wj2w6$z0pJO{^d69XTEm|1?G4DPrc`OS`HyvpLy&wz)?V5 z@lUO4TUd&{|IJZ#Us3qYQ5}!!LG{BY_o~mmUkyleZH4LcziT^|*f)O=Mzu!PcIpD= zn_GNpntFf?N8dd2tF@!OI_f&d!`^RwLE^*9ygMPZiauW5Yo>i`C>)Y4Gf_1|$9 zf5)9eKE3=n#5}pSR?j9d4_vw50)AtcqIWf_l|KUCO#XAxKyffq%@68`(u8auq zUi=5|#O(*~F9qP8=s!B5Sby_8oxeQKwpXLYP6FF@Rr3LC>$7?V`ceJp5XJ5|XBcOHNyr$+!ES(ZQbpY^C$eInX{ z*6V6HYDV#!uXj5>HTB0KV;$->|HBpHJ>+vG@kTK6V6OHgz}lxKL0<&`($Vej_Xy0n z{K?llqEB6=pM1Su|I61qIr~(8%J}~3zwEc~_@#||pgxaJK05-3WN;y4-1=%)1?a!n z$;7i6xcM71_||W^-Lva_b5Hr&1M2Zpo5=grCQ4@raDd<+8}Be?wRVnrS;wW#q*FtKF_B&tI?W0v00T3G$ZNK$e{EvSP3ICHTX^-G(4Ar+5s}Ox^xl&YWdQtK} z|2hgnbOW~Lc-WFdcG+LuaKYx6topMx4ux%>y7-s#d6y9Mw+5y0S0~5yxO&O{Q-g9k zt?ztA$sl?c3iZi{_hFRr?@5sFW;n(oC*wlk^^r)ZT>7m+xk@sy@gDwDH@v^8N&1ry zzZsu+&GgBKPm}-E4S&u+3IWWUUch^6{Hy>wcsgvc&m1<|&ObS}>h{gCC9zmW47B_H zhD(~=sGIjvuL;iGn ztCvcHtobL;6M3~!$Q1h~_nG5eh4GYwF=)BBL2kuGy^Cdb+8SLlx105ZYKZSJQvNJq z67i^RJ;nveK2`&@ya|q0U|Y8U@Qm&8e#i*zD588*Ev3_<$`tom7mFh^by>~d{YdG| z^M7?HF(=sUS0XmP9%#g}_AkT5l@ME#%e#Sr*IwyR4yl~V^SJEsPe~UR8tamTYjkSi z9xDC!#?UymtFX4Y({*B?pWjz|c_9g7)M-Fvo@^)FqeTiqgpq}y%?aNn%?1Iyty<`V zuidmsVV;k@f8W-QB8Sf}Zgx})Uj9&^?%im&J3BreX+%BJ1iWJEL5Grr;cp4sZxV)p z*8yH{Q(YdJpL)!QjbyNhSd%DAfRsr#tSEWUXYDaXp%fsSnh$}>j)gxt?9kP;pUc>& z-Vd4}Qb>LX>%_Gh(#`@il6TRJ5t4r7CI&UlA}$oXbz@7FPDw-4>s+h}a;AiApa}_m zW##b#yDw}2{M)Gz+h(51KAm&Ftk6~B(-qeqP{x)uIa3T0&!IL% z!+I^ul@eYX5U;0e;L+V@Wh%6sFu#)%&0aSvES+0}pY(k#2&hZ?ZM8x$h{(%k**C=$ zHbgrVjlHE!>n+tBnYW6zS!xN$Nmeird{X6o=?@NG1!zMz2X(bxDjO?a zx<=Vnft8>aeeG2%dlxNxM}zyx9w7pc*vpylHGwYBPL28#q9!8!twlL%6~}-s(iR!f z!ezr47#}0N+cL%W97q~&<);>9w{kj}zvMa0J%>x%K`o8>A&-t19a@m%w?pM>wipct>7Uk(2xDncr^k9}f%`Q8DHGw${ zZ#&{KBm})W&GN2Ou7h|x{w$s!FL*oh%b4Ouleteuz!`66$N;UF32jW?bj1R$y0B4A zv^A{};fi_LIStXUk$WXTtLf4%pI0=SdSUz28M^7)40Ynocyqk$5N?D@Nvv>c$ z>lVoI30bV`c4J=tLR;A+9$c$zW%c6Ny8n%Xx)g4Ss>3MfM;~vE@U6F|?e0%4`-Ay^ zn0u3URdp_0`v=jWu9_4oDy35Qs1*UJt6%>G)V0@oztcR8b6tDxHK!05hGgU!3F8hY z$_z4!knuF|!(yBvhwjcN^ie~a5<#w7Jnc#uU(Fc_m#$(#*ad7^9SZ>>q^;DW0`c{P zh{@?>;6B6QAlUJlGsC~f^8D%?==gd(L97{F6u)uV#7_R(ORI(?5bucS@x;)WfTWAT zA)W|yH?13DAR=F;x>MSo<%?6gacv%udfCQtBK?iy5*WwR_4?O1F2j55cxItpcQ4GQ zyN}Qav?nvtMYP3g!nkoU(#(q3OTTk%;B&AZH3r}2U4%uM;XAeSgy3yn@4h+(Ijl4t zvdZ)<$BnmU?M$ln$D}eR0U7`B*7PrvihxfC3D3Vws^9(xaHax5^8SYd-+uO?qI$;Y z#IoBsLL-$VZS5S%;KzaA)+bu`d=n6KLFF@FpLr1A6kdoU;-P|JSBjs3(|LEJJhDb> z#{y?aqTld_{5&pmaY+%9E!scxi{*eK?{;tvWu2tqC2M-HOWeJyO&j$g(-1D=O4&40~L1I3?Qdt%Y<-t8)#bFLXAAH2-~ve)#P3w5<#HAAUxBX+F=T$PONwpZ?Z_5>laIePO--lP1K!-=EL?*mhKZvxLnIA zTK`Whp>`kc3Wi?Sp=-t*!Y_lpKLM!dmb>lXJ* zd)%+i@+XGlLEJBT*x&IdjXn4IqWC$XPe#>tKGU|RhPO=P1>2|B>1_>f&l4Rw!6?{$=$b2!~u6aQ>I? z#3CO?tK7|5N$5MPM|Z>4;EOnFW9jU%fA))?LDUAm zKu!MU88h!Q&j{Zhd`yaa-Al{;GgrqG2TyuV|8kOwFB6_@j_=6X>fQ%+ZwEjhU;KTp z&e^!8wkl7DQ@^bfcg?}x);jvzCA;1c?-`Wn-sVrGxwZLQ4Hfax_%la}?X01vRfo5Y zH6;F6L)gw5lCQscM(k1S^F>%L2yOeDFG_#&MdC^Frhn&)%`rGo~d{OY7FWUB_aKH0K_doMRc7~~c&llBjv+wssP`wJqfcM7gxh7tF zwe$~1j#fZSw{1o7nERcJOBjn2wHbF`OXcN1wZpzI7|Cw)d*gjfA#W-nRb?wav<*H3p+`oarp3_y=_iCuy*EMw)38!+dOWzZ{Vh1E8QAM^Dox+ zB^xU5dg#cFU6yyqC&tf_6Q|*x^^5VPd-Ppze4nAGrjnaBp!1(LAi!8;uEJmb{F(+I zZJ@~_@aM>D^XJv(&rzVyK67$cX`2V4a*;D4<->k7bIocz?TB&pWj)-H>ACkyB|mR_ zT^=^(-GAIl12l8fT={n_Wh|K^YH1LBe!ylB>%(FI&PIYR_|wV1!8BLSL^-v1sbWh zTe#7-SEV^!>!m>D?i#7wVc#e8^5lSh)M1;KtDTL%cH8IW4)b-}rw2Jjb^O;JAB%OE z+#6b4eZ`hFxB0CbEg%lD$#kjCv35D!t6*C8vGg?qe@=aTqPq+qe|{un;LqcK_DHJQ zecdRQu-duJ!^ThBI5sw{3rzVDmx-Wj))2-U7x(g=mlK?*W-#LJ2`_wOhOgEYICQBf z-WvmLW{dpd{$W>NkUOtxy#>ao8*q!^ug}(qkV$@f{q0XBx9WFZP6je^`1aZG*Jm-% zXX$_XteoyIES%wC@3Sbf>3L~evx7jNZ9aXrB0yV)k0xihQMkX~z#>~WhWm+^2XDBQ z8u3{J7^4#Sy?uYG;;(&{*~#Tq0l#j32#iPdPsgy+DKobf?5~bt=YAiL=|6ZtoHm>*nig8PvqiU-RH? z&i_sjf=E6-<5CJVrFnUe(@`#!n{0CXd@hKpw(njfX>HB_?W+J9%aq;@V2tXlR=f5ifUxu|?ZVYS2eG7%I^k-9GK5H2B#;^uo{+~4r9jyRNK(oK^ zf_L2Jb6?%HXwSqi9=sS^ko(^nh97;_FrLJcC5*kVVYsvO(J@DcSESYcdkte`jQt%p z`TpAUZ@X!*dDFka#`Lv!4OT$gy8_2M&|u^Cu7R)U?Y3_#E5ErrUaBuYPrjO9+}1#( zetU1G|BsnSCtJIrmqJdqy6=DQJ+O>mNCRE`_9AT2e1G>r0$R>B$M0T*^zYq=VG>Tu zKQW&ue;M4%rhmsvWB}dWf40_I@%`OFnbJYO7}JR7r7!27Umr4K5{4&Km%GWK=e~&Xhi!eyo@KbPtv7)Kzp!7sIRWh+BK;rj9upt!-p&Qj(jQ-+F3`u< zON8#}Pw}631ZsQN?oMHE+CAO0V7Ko@C;;sa?Q`ky`@R<10p<*v!ai{mG=l-3xiCjy ztG?|;`2568qtQ6#?cJJ=`PID&^cns~pXqn$%N2KTy~uuf z%J7e;oK9yC{+Fi=f7eV|*LiPj?rk1q-Rj-}bss$Uy$IWF56k{hPlybC-b)f-AkbUr z!{!EcKR2JaDc}3Nx$j|_f8J$_k7&y`2wJ(flTk67h_|lxuxp(A+P+yGnUMqVFRS^T ze|qJqk9r@iQ=gEdYob(t*Q_{|=_~EL@b$YlJ<2HxGl9133{pNn4=wQ09TSK10jQ{_ zr9jz18Jy^?z`;GbG*IS0JVC(PCo#QwAD7WZvu*;}hX{F<{OLObxhw`Prbh%SQRK5fCUWUiPq?N!X$>lI&F$x@bOEX>FG4q{@j zcs2a+_i#q1c? zFnP(@HwfRLd+65gtq3d#k=jGKv_cKnx;FSH{oxXnv^;sk!3u~q+GmW}SC~c%4yQGK z93n1_m?PBF;p-8b&Sb%3(7x6qQe9wAh9cjh9wvT_^xha-3BD?qE2Cldi0g)i-N-s> zU`!wYJ)CZz_#Wx|xcDFKuQvNs&qlFIu*zpZh-%7_$35INB1XJ08@Os&xG3AB;h zN^iy*vlxPA*W!q;RihdTzh@!HDMqhI7pfkk)~ zTgOzY#(H&nq_?}Ifer&f*Gfag1*5VKdXEiqDkgzsL?i2o=>*k;#`1{Biik)_U8>CS zDWA+Id72dFIAuCLoZ+@&-rVTD7JocIgyh~f2AG2TM_rUq^3o#M3ZI3e*@P;083*-d zD1$#M<$9b5yuX1%ukgj}Hlwm_VUNX@OnL6^T?*%f+GST#Jyr;3Pk zb@s`onl*Po!x6XNrvv)JAx^xYLOgkmzy zV%Lg|qa&j^(n#8HH!Lq}nY(iXA;EpQN_q;SApI~`bX!AB)vcCIN_N$?hRsTLVz;r) zx^@Ddk4Q?aVXEkh(>qm6wX8tH3`;ZLH|#bA99_v$%vR zpX>Yk_3f=ZUDB|7C}4SPkR*}qv*N<%l|~NYt0D;IJ?KwTkfn=$r!6{)DZ+yI>xp`G z3zeU=y1SCI(t6wqORHo$nLFI7>P+vvN*yM{yD(nUsjqKoF`fhD4KLX17`c}5Xp=>K zFBmU|_tweg$K5^FJZ042+bM*Ti=F1o1C$^K4Zk!GbPcnSDe7ju&p z9HNk?&(j{*ts=ev#ovqFyh=tkEg4@Hk zL!JAg56!Toc#B2(5nPA8vekvR5fctZf3f_8g55D*ygHa+7{q)`;nyQ|r3dRETPkd) z@$zcIp@rte?C%yFyWpHMXe#KmF(=LV0D}(~r1c?~SmTAL!ej((cwwrLl_1+S#tSao z;KD}bC7!7iYw07Yk*ZADEXG#Ka)g)|0ml$$RWR->qATJ$7xm3uB_Q}Ip(aZEom12! zu-Zd@Myw-bs}yd?ZCp;s73JUemp~q!nWGG^D8j36Ue9yYan1GuAbBul+qzNfGwvI- z_Oo-|zx^3A{eyW_iWNlCxtFsT;Zk9{&C8i}li&df1!ZzU7C0K&fKG!qlXzr^lFU@oqbcVId_kd8oL0HM_7`05 z@r9anGNF@k<5T7Y6T|l7-7YxmoSvh+^DLQu`>cLaWi6JMM=xRPmA>=Gq~bXqo6<9mvAtnyu-pr9pE9rNAaT5BOjCKomF$qh@idNFll zjMK@iK&?TbT^;!TMJDJSPl?>vxOq8Lp)Q%YWSOJp65$);Qa|GmVVO)Kt#B_-tWNuI zQrGE0jo{JkNWwZ|>m#r|P~IeO*~XwNN{;ps==)g=K1=oD1U_RRXZvg=?ayZz_-ui! z?K7G5!DrD}IN&p)2QewO7RWGFq3YgSA@0z4|Lk&in95W$*wt=oQow!^DWrwf$n$vt z=boH|0^k2^sm%D7`ZbhYMeR=r7nW5<{wF2>fyJaXz?SV9!iuN(g4 zO{R}@8WM=59%oLQRQ461>*ZKl%3Cdb0(~8v6g++x z&DMsAeOmUF6Pl7K-1ilXVV?eFRTd+LW7~6AtNz%zWthfsR^kXwJl=0CdLVDpDTm|0 zg_l!%gxfhW2Cfm!3$m?SXE)_Ad3>n6Ukw;bwPWk$LDkZ)IgT0l8Jr~~R9d6A%C-W} zj|CChE$lq@7>mjd>(*{cxGb%id^m7RZ)f<5GTuG*);gO{!G*e zRR~afcz9+bTtG7Q2_M0~2adiDDk`)iN}uAahc&29BL@s2bg;2TtG;#%9ZIkwvG&jA z`=y3`nx}{33N*9`dMV8ynWlHs5@(Q;EYT~V)sx%~3wuNHt)GtmgA(hrr^TI1_(?Y5 zW5plmjVq}=^)It>M$rU<^Df)^NF1kwByiXgvA)@Y=P;@_d3z4S*6%Xfrg&DH&*_mx zU>U>FCqyL`6zMtiik-(@yKX$mv2!<`NsAI)zi_HM+YJz z!lf=uUk2{9gRun{t1t)8!yJip`+cMWUvQ@-g5q}yZG2met3sWh6v9VgLJWowvYymZ z`JW~qXl24cFy$f|2ok>?_2!=%?!i8W)VxBb|ga1Kn_JDMt zBCr5D%G6M}=t%aitFkKN!(mJkh54?UH0>wLcw=be4~e9{j!7_AOvt;;Me0pBZYdbY zHe4Jii>u9u`|6m}^7{Zw6zPC#i!dI=;JiyqqAfXwsW3Et4JE7XjBcA%GKHR3fjGuN zDZ|2(xX{yi>aH|*)5HMb?Fdtzr?x1x;qCIi_6p3)SySUAl~tVS2NI$a$*pp$orw_5 zc~xDE0nPE%UvTaTWE1XNtEyvazk^Mvm{75=H}gp z433a55=MlLc)Sp^`EKdtT#PWyWE#i2QAUZYmgNN($rAahC2O9tP94<7yRgHO!EBZQ zL1}Rp5sYsB?N~DS3yp;7gsujBqsixbIjwvWo*{1{mF(`F=QHLiA_r^FfE*p_b(LI; zJF#*pzhiIV+()#sV(l1LRz6AZ_(F+pa`uyX8sG7~5ZhTfuyUik#Ah`~(H7s>tE6KGixJQjtZ#9ZV8P|@i*qU!YIZpXI zzQtlYb(@V>V8fFO@!iJG;}aIxIJ|CLi|;mW-xUHjy26m5Ik<G>@pwdt4m7ZJ$KcK&Nd9C3k&igg z>Ga5WcIZ1!AE)s!-@#|vQRhrn*|>x0G!n$dJd^og;vn2f1W7e6(4ZoMzGmpE48$~@ z;ZQKM#|rLu(2|UYhYnm10_T;dhL@)I5tf9Fd-gtq^2xGJ22pYnv~z_}KAop(vY|%6 zvj_HapzBH7uCcKb%PA)a;{fh2_!-Y6y~63S;WEsHJEr|Z0OfTD5~S8J8RndApPeQS z-Wh-w@cHQ$5}w)dIX?Jla=`S^JET%mmu}~+$MN}SBk*N6{tLYuuz!|;`&L<+ zHW*&nGU@)m%OjE@DCTK3BIt_JI2ENWcx_J!CAS>^8Z#Z-9ND&I$m?T@=q5IJt28{1 z2Xpwiy_J8*u+K67!__k5w%WOEFA({DxNRyn zq&wGYKlb5Z1}N)&j`iu6huB_s9@jnW17_!jmb5eO9L&prhC3?}9CMidbIcug792Q6 z|H|EdBLUOXwCsQFLfxH73nEebTWR*mUU{)hnTM#72%oye;%1d8bVvn@ zzZO-UUnC8M9^Eq{ZR)V_Y_HZbSz(s*(qCz)2O4x{U+8-!I7nvI>XtUq?ZVuiJY%ZM zExBD54t=SCuw=TGQ5j^x9*klpy)icFyT`;~@h&{C8uIZ%nm2l!vaJao8)R-OR`8s2HE1RghVw}^^_n$2Ht=LBCim&1J6nY0~Tc0QWi+dKbNB|(ES_x9Uk z^@lr%4(NxuI`m5RwSyo&cF@lJDLvzom9UH#KZ19z~{)Pwj&;H)?KKBis za_?_pi595WBUMb0Gh@O}<6Ax6J zS&W8xb{Uu==jKGUD&!NBBYVbL%u1dV;yK#Hdmse8!U_hW`HA)k0X+QEJB|~sYHc5S zqCLSU(n}lt>_UYR2ry=bA45N50E>vJM*!Viq?LpCZD6=Q+6X`ZXz zs0TFW3uxv1R#4J?$xwn&M*l5SvSk_+BMTeh@3LA= z2`|AQtn+QSyytUrPFRjIv94)t2(b%3sUI_lxBO=bUC$r_Q$^clMt-P52PcMs(4M%fO&IZ8+)3qkOi$zk(X;1}xgyDoXy{VVw8!@1tC@%moFs(;ro>5cDw zNh1l{S(?yUZjleWT5ey^?(1Bf`|xR{m1n=KYk{KmU2haDFx=a@;ox%X?x#1yvCUs@ zH?2Qoh~c{4t_8p^XK4LgmuIW&YcKq(hdK4m-+FkRfA!%0zQfpOzwc1|zQg>{E#n~5 zyY@;I3H5*9;raUxJo_-d_gcC?_uAQ~;9kl5{QC}n>i6=i-^+)oI}Rch?Bfq8%6?ek zGW~?mU){KFHqgPz92{D$n$^%#lnMw6kF`=`YD6c~q3*A&DL3X_PiIOX0zA^58uCD3 zA{?q-UUrOgu10FWLfW;{zy^@Y1a6ulml&hyMSSDY&|Ky` zli~>_^mPbM`yLtWxrIO+fts4f?)y%HHa?aZeNIa}Ol(txX~t?%frXxgCiP=+8RR0W zp9DLFd=frC%y^ z@XNf$@ZiI|R(tHrybj}r`5pT(uhkCwGOzoA{V=aXclFRV_(jA zUt%B5ZQp%);cWlmg@-XcX zJzB5-@J!+GSbR9lq4(8lv{!vt%L8`#a&r47`W_3auU5kRpH1Z-oBos|f8`#(a-RDu zSNzI#Zidl5XwPo=|8jdivgwr;wYbMxB+Oh^W&45asb&lo{fYOtD>e-(dPNd~N?Pp! zC&yOwRpLHIi(!DU8-=_l&DII$atFM1$iv8%*fJ4PI-r5P{f&g%9pSna%-(yhEeU7E=`F9Na9&^6?A0!g`+}l_6V842sNS)w! zZ1^4fYGQEgqluF%_SL2f^wp-!_|>MP{N;*+V_!ZmIHv!Oef9DTefhkMUF?1pck^!y ziW2ZQ22J#D4C=Pu7!;-8Zw#7@X!Sc5evXmwH?B?6omCEwRUcOQehm5?gTFDa+kE`~ ziDF@RO4B)*FK~e>vnOUXl)fnoXGLnz;I%IvP*}4w9^%(YWi=RyoQV)+@u4dUCTSp+ zeAkZ&I+9Q>pKoLX+gKsGdgQaP7vn$Kd|B^|jYv<-5Tg@PmOL zR=!H^nvvu>4lShElrxfG)Z2oY-KMDZBp>7r3TjG9g11QWVl9o zF^q3tYwtA@W#bb<2!)j*nX`vc#oG3HF`^zwyPB@rSh}>cZp0P^!sBW6lD9%S>~JLI ztgw>?QJTec(AvgV{LV^o%e7$=D7kYRiUmJ5ew@5rBr5wVJc0+9L*b*yCJ^e_(t+dWbms9K>{p)^8suh9~!yw(_Xb?rq(b)*V(2WOF}(;Wv_+dw-j zQ4f)n)??27o`80L8xdlq2DIb)7kzmLNUL=H+#{M!Q z4TSumLf_YSMftC%owQZ>IL&XXwbXFL18976xbiv<=SXfU42f|q^kj*T2Mi3o!OdisCz@T_G30LQE1~}I zY~$?2d+d~C0)<)_>w|jlU6f339G9I61D)}xkVL1=`#j>Z_2!mt7Q!j0xcf4m%{?#t zln6nBJOnR2BZ`k*m-p?IJc9^XDtV!do53*(kMd9CY`YuGHX8(GQt>M}l~^vrk@IIy zcHF7XI*;WIIcZDb0P(nX%9%e!a=fuReCo?Qly7qJ>?54duGIta8%7%*MbvlI=)%Z} zJ!Ln_Hgq&7gwRLS2xoEbcF|hfty}o^jE;WjKIH!TVz?3#-fS~f+4~xtY^+?~_Y%5y zEC_ZUcLZzb)1rIvBMy?=F=AhjHyNXW3z5oyHoN-yl4H+?Vv?nH@D^P<{VZ4sJP4kE z9a;7BPPE#Fj8e-}s7Pn%8Jsp8kQA*SPNOrOJm44KA-*(e80UtI6^Nsh=n_>0RX;=^Kk zsZ%kW<8#N~v7EbG>#)C9jO4_lXhHFQrT)sLmd=-qx)sHhpKO`(GqK1nhcWJW?uz6; zrYcq5_8i_IV$MTH?fE0PU2rXWCWzAa^;2;d;YQhuI8iMP*E2* zryoP$tqJE#?OpDLcoe4MeE9M)JCRellTu@!{9WX9vRBYQM{3Je>-JnV@0Y8l^W51N z|H)N5Y&k^L>rJWc#<2|!z?OjgaBQnc*>lxS>Xxh4ofMv*zqxANo~ssg+@bTA*F%}7 z7cl+q5*yFP1+FZl+CS~QCJbKHE|%{vpu>Y||F##m6F9ZGn7=%`yZOnpgHN07AC^v} z9KH;u2X_v|e{#OJO5gI0ygcx$gSX`iVfF;OxOHCU@whRVBHq-jyivHvjC4B>F^FFm6jt!c8PmGjG>^hu#9IRFLqzHF#RXT* z|K%=F-@IdME_jvk_nen`%0JApQsG2}`6qX`!pgmmuFVYEda`cZt#aek&BY^ee(bBh zx|+Ji1Kabj?HR3Er)1+t-FJSJd)@MlxJ7+#EInYmR{m!@on@s$$z6S$h1y@->}z|T zalNj2Kii?N?VYz|1M^|*$+P&$*AW;_kyqm%<`{Y0^Dc(wi8;4v=QfAeCT(Zn8*N

$m+g?vD@Ww!-%Iz3=GUY7 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 z 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ξuH#m^v}S=af_&RHJw{RzXlj( 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

%qz#tw3)bbY!|tKRp~4~B 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 z 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

ZH_~uMoOOhuay% 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 Jb*G`a+-laXotfGpK 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�lUK+BpIZ&S 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%0D))hOaz%sn6;h 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

9vC)wBI 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%0D8-(ipRS|88)2ofl;h5SwqiWYgP?Z zNaEm$9a(n*GBxG z++vb7uq0;q*pOu$DKqlsH0nkx+Yk|sn^wa5yR0{@0*T>Sx65;L)x_LfMc4)sNNe<$ z?Hl^7EF~2{DMF~uu9IGC@4ZRdhL;Y%lBhp)=f%i-Sgj5uwc_hDA2@7Mof3R&2>pV(3olzXj~t_a4im|QiE)e{ysZ|M&U1Bx07mAb`RKrutbfQ1qI|mv zW>1kH3Oh?IswXXVV?~JGtf)in&7OpzmkS8);9p8ttowezQqR1gSJylR2tGkNhZUaN zPO>j4qrEQFVx@T~#5tySXt!c}?#2vdo6@1}kNcGN48Q0P6~II(0`big)VClS27cpTvRO^Ncth2!!iZoMClACQ88dkd)<=b;wv z*MwDPlV1bcGDx!%$Qx3ia*RtR(meK~22wGHN2rdd|&w#{v(bci0J6O{!z8S7M<3pNpYCUsdj(upVYpD(! z2uYMajPy6E3W+zM5S6qI1C#9WrrU-cDCDLa=zSCIdQHChf<7rj&z4OOeMu{kXL4cE zl*{)|vz}D@h+5IiO{dJ@{Qj~=Wap1FXUJctU2m&! z>cX%6bKvgh+mmGTEECk>s+U@@cD&X|wV}`|g*f{ass6E`L3Ek*^Dkx>MO`9QszKSP zicO%O@D$K%!Qqwo>LizSicvKsyo)uE8$r3S#LE$poBop8F>UN8Wc9oOlCHh_Zv8vL zI(+Yu88#+LBkPN9u9rkC$<^(6v|M43)C?g(;c0#AvNPbB>=vW&feJ|O_eqvMD94{4 zKcq9-hWEm!nRiVx_rH71-2d)1=j21S3F@3)45giT`U*{GBvW`nzgRbd>bv3dl0z}? z|8cL`e9Txn_ynKx4!4n4;2!?>RpJmDaT0Y^l4Il8ZjlxE_Y1h;Tqz9ej?cn{;xfC^YABWcgN@cgrh`Wk~{g@Cy3}1}yOm?IXk1G`--FM+j?p?2wgH zi9Lj#OZ+5<-Cb+zf!^8IljdEy5G?jz*)JE0i%+G|T9!IH@?ns}k z4oR$F5jZ}GQMA4wLyI0+@kW|jubARueK~KLs1NiKk|bTP12d((6aL-zUQhf}N<_~v zCW&#Oo?a5cq@EC(1qh;Zhasceo-U*HK7?EGm^v>jNsv*(U1c?y&oCD8lb(Q-L07h?i$_@@?pi`?6KQzQiyLEh#4`ZpL_X21@>&g zJbJIm;E-CL;-cG`3T;w8@O`m}3QKmC7k6*CFt$+Ima`I=M`&v&86Jz)GG#h=oACEO zCuzsoNRBGLj?#!4Svgpc@59f|@F6(TrE+?H^p%I95@_pMetd8RQPDx>(g!0AkSU=- z{hE)R^jVK+U9!+E0eKBuTu|%IS%TyaQOh=SSt-?y;&vCqUsOArEy~5*5q&eETiZ7= zaw^rCPvedFehK1|uQ!cc0sEjNqI~LdhBTJ@-QwpZQ|$HQp1Ap;1oiyIk)%!^udX(e zN_gpDDMr((a*i^Pej#2&*JJm_gM= zgN?HADt`;3y*p&#ckc_E<>M!BI<+(lUpalHW!(CWO_}^T_;Y#>1>6K2+z#}+B4A&D zBaXk8tw08?%p2%b7W>38=2fN182$*_W8)V!TD=M%vuQJDJeftOqc~t&?z)+X%p&X-;LCg0^bJV1ioXw8}qXe^wS*7^YLRsOvQ?AdWeij zAR2w(l$0{;r!@0jd!-f5xK#Zq)gte{*p_Tt#90xtH4$_TMH_yVBybnf(B2}WLKh#Y zJ%xAeKIhOS>~`>ZF?rG3GX2CG@zhtdGMEpe2*opdZC4Wx5kk;3vRJ|j)6s{n+mj}E zB~$%bS5ss+BY|9gbZO44!-yP&O@3yk=q(d0$O1raa6+==30#Eu!yLxP%8ZrHMi&CP zSS`#6{-KwL#uWVWGTUbH^e;rFZvx87qee7>1mxvpzgo3;-Ouc#d#V~ZZ{#@&eP1;c zoWHiBbKcZ*qWxtxvRCs~%}*J|CrzGC9G{zPyIGDHdFu}~}qpfFm3@R1TGb&Q^@ z2en#0PmCzQ(<22XlK~GcwGNjZkkIOs(8rbnYE;lGV@ecZ6w`FGY!RS7625dgI@Ss+ z9f*O3i(=NpV3W``cxTm+z#^wDo!)BDGGur!njV?Kq2@8hnt%kx%qM;l+V0HiLL* zOlT6qQtxRL05MoRT9^z){l$bGoJL!muUllQEwPsAye*`#sTOFNSB* zNWVv^r!NeXDkglrHucSQD=TOQT)|p<6wR&12!u0t>-QjzFm=Yh}&$t_fA@I(^Gl%EA>eukpzOoYL*_Sabo?7d+pDb|z zX>}N!9&f$=(~Nc@lxm)<)}EnlvND=uCt$JCvS;3-C`FJYJUgjd-?W;Y;Z>t%AxMOy zGVBzM&d~9hW)(v<5<|Vn`4NuKg#5JIWREKR<~xmrmoKlWtESr!G1fp&|NRbBQz6i{ zYlJT0Ov{&`ocdmP&>7g78`iLjLy@jno^XZtBXGAJkpt#M_yZq^jDA<7q4CKqVs?rkMoCuti2M5+Gk2Y?3T|$myo3huf$L z6Jm8`x5GFxHX$Jnn_mK`D3&j=xB%NY@?HB<4X@9pY@v!2`}U(re4|#B9{~Xmg+IN= zp{AIR+GijjD_w;r<-!^@8;_?+dQQH~76nQA=sDB6xm?HOXwpx_Xpa+35%#J%CjOFJ zmPnGlnNktKiQdB>aD#D;)Zw6jCk|m0^cHE!0-0ViQBn^jYNilt=g%g#bJjSVOnujH z**uO*^PfM>;YXqJ5K363Ns_ASZOG_bY{TB%FEUzPC0DvvZt!Lw@U-)W)%)$0_ZJ3p z=3%u#EBdqV`KA6=2?NTWmkSL& zl%wu@NH=p9Cewyr<#yS=C7(yHb}LQ&pb`oj9}q;A84sKc<@vKSzcb8BPOPn;6Hjzd z<1oVu|1iJyeo+F26tRLMn)%-Eks?=WBv~YRRi{9nG7jhP^(vQv9^S`uFaD_zStNvx z0}DdoD~BXI>5CE)cjk>x!Cx8BXJW7Hv{(cWcrHjtiW$y~%$^T}Mmq-HMt3d|`=bjO z%JyrfD5b9?toR0`#N@FLERJ6m3MLKj9OdPJ$}r)OJ?2xrhelOtg6TL=pf=vlf2<6t zC0{9ciKlvpU*rmn-`hjSv=(Qw?W|uRV{wYpYFx0GPQ6nBHyVmZdi~*`o4iS)ObIj| zA`|U;YU&$EyA9hshw}^P4N*-A^iocfE{R8NF%xv{vbVt%Nzbv`K`hb)>q>bB=VRY0 z;fc&1R$tEz;$suYo5e0Sm~uf69XG6|UNrQEmEJ&-yhYkgN0v+1lT~;fmURKt2&Jt- zicKA-E_bId#07ryHY@9!UL`m^wq^ap(l%O_MN4<-2i*! zG+VJJvBT_E&YCfT`ozaKd)phXsc{X5&%g90DExZn5t$$w1QKHw^X2k4hgsBqaFWDe)r6?q0j})_m z%+uG^$158U9X0b3Xxn%-K=VD<&dbn4MO*P}RY7cztvUJyG1s~;r;0pkW_Xp$BN>Rn6; z=v^KsqY|NrDp3DZ%OY(LRr801C7r$Q~g~cOF@<_CuwHQiHFHh+tLS*~-<+6}w z2KXzc{a#C#SA?J99?WzlQVx5*X!Q8I7J*HIK`Rad#7(sH9bl+m0OW+H(T#mUhAP6j zU9m0EK+vAhegm}btc&f9Gr@LVhG}e zg5#bh)(+f?a%-=0uvPzfGl5xA7NqKlYz#YWtmN+-dq@^vyt4ez?VU_qFNCO|{n3My z{1OD4pJ9;0i36qWkGh=ZbFQ#-%J*eagqx`*uYys)j#4NdLbkz5wOA2Tz-~WCrjR!#>om6EuBga#!S8U3gfoaY*dv;_*LEH-`p80E#bL`4A0;P*`BHCKX z7e6Hfh$P}qm*$Vl+K=gZU!T7sdpAWhlbQGRn9}RQKTp`!W+Cl>j-S^D4@2wx$+hQB z{rsY3wF*>D@NROH_(oB7x~@a2j54DZ#Q$oJ(qa0#*ZcU|;-A0&7_XgP%_r?3m4s2I zJq$5RgTC;{rmtJ3SUPi0KQY^Hrs;HY%XNppDg(;`JS?+XLY&$zkVCXc*c#KUyEK zszq%)0aC`8Ka&l8msc^bSr!zXU=ff}uQ{r%=(VUb%6H>4;=%_ed~oU4Kj5|#1qN9G zbr3|;SW3N>Qifbj{c1YtV}@pt&uu`AsSWv0Q77xtf5k*Ix}1b5T`oxEg4_4sv93t2q!qZZAtz-e&8AvE+C8?cU@d%^^32+{7^P)H&f4 zOKYhz`=I*yQg>*}LyJ`*qp9+l zaz^)2W7j5>x*|$DmDriWnu({i5U{6^^1AooXM~XnROWCIV=uMTi?VwyIh*RX_SH)zcB81&6w$N`f%{YqqEJz zbh7+cCahc76P`G8J5vHv^33(l3b>1{3l+xlFw#IA+UqwLlw)?%2^VB3_4dZ^(icl# zW&9N^lGMW)8ROo`FA@6q!6>d;F=5Vt%!fKx zWqImnXpsu*#7y62gbj)56BbV?BY#(<5XJKFOU(U-Brad!LJ-O757xkUb)I#bY8ySM z`ImP9Vc*vzhektm3PFW3)qKS(`%Li1c!)1GM#^~{8dND5DatO2-sPCTe-gN;2z!x* zLoA@P-<+Sj{Afz+MKA6u`*(eq4F+`-`j|?*hUmi5ub&J(Y!tVnfh$?1Sy4GDwRBPu zXw+g4dOn{Nd(fabmoPpoKn$#6djE1c0qQ|YhEC#R9>jx@Ahn=M2Q_gi^+i%k_aG%% zQ=PNN$QKf+(>8g(ljOJ;+3*MX$qLEv)5mw?9Z~z`A=@^Bmp@j)D^UA^KMBpU8Sc8T zaptIeTv7AJ$ci{Jx3Le;O<9^qpQi*3C!xQoz3qvUDJuZMLsix&lX|FOBxW*z@(ZDY zHW+GB&)%91EPY%DAcIWqareUs@URn$=FI74HPnSjJao!4+z}QrK4xf#zCR-Z#ewyR zw~MD93$#1KFXw?&sNq#WVprM)=B>aCu?@B-`tWeVD&;QYwr4guBoK?8cJ|onLFaaD z%GP6IUEC~13K6((c7)%oKfXYF*VGOZ`m>~kA4SvnlmESRkGQO}dFBMR&F>>@K7?nu z+wg+EI((Sgx!MSNp<@UWb=nzh-RZKWZIsvTR0CN-$q+m3H3vkJ*ekHj58fEJzUHf?tba-hYX18>+*K)oKip6FBkZ z;7S`Vg+CVeHRL7C#QVMIkmLWo7Y)C(M^N*xFRqRUmoJO?(Cug%-~l>cukx~k2}lcM z#@PT@?V^qbrfRH*rgVC4E>Qy6$$J1wMmq$gjp!#4?F^j<3%%uzom^ereeU10?bZH8 zF7$){?bZ}TyH8^Nj91XCm5gEJt5@i8OFr9%;omZoYgp+8*0sn!q*NQSroZrhHj!Xb zl{I3Er)i7@$>Y1z5M}KHmRuW)vcWpfS4m|pmL}VCmmiSP<1cqnd#q-LOK^RAtYT9N zQ5d4GO;p&9!+DwFMc<=>4^2hZKp6D%Tt7jtO4pV2Umb_~M+FBvT}jwEFhg*Pbh9N^ zvvL|lh6UKi99{WQt;KR2nt)J!P|edDwmvzb76nO|c|MA+@X5xs9O|uFG+WcTaKFud z=KI8Q^P`+=_JMsj$7Nca{Fx`m@d3rbFXPbh=L`{cAiK@5y@Zypq&V@eGOu2nK+74Z zz0Ev7d@F+KUp1qk9?x2Zd7vL4bh@jHXf^u5^@+*jk5!@A64pJjSuNR-eN&C6)EsR? zL0?95eqxS2Wf)<;`Gu*54Lx9yHbl?vl)7Qki<4Gl<>o=qXeV=uIhm-C`vtL^A3xD5 zzM4#Xi_G-j0R@GUqS-Xde6e5mCA$0v-yhdbKzJFgoC1;rigc1F*%F;}BC+ZWT49pU zRiE@6*rS2Eq%I z0@#tQgKc*Q?_c!rFd6x@W1!K_iyw9n#M#6U$BvdvB)QtfIvL}Y_wQuhb$%cso4}}+ zFe`4ji3YXXWbX05eyhY8HyP9a3gvXrvW>MM(}g4Z-rS8mU{!ugUnadTPJ=psyzBgQ zZ9s8j)63Bjxb_@Yhtv*eIl zutbzzL;qyB3K;=9=^A^un$A}6n!=3ym_SVTbZuxjgmR$b?F=FnnP>d$hn7@gm^R*p zjiy8J;sM3uB!0f-k(l85p08+wi= zC~OUo)E|&ZTt%B4Ob2JX86RVWK|j+92Vlgm7~X`Z#MdAQ&MfHdi5}U>6XkPfYVwOA zFl&*Y5abRS3O7bb)kBf@KO9mNY?4HKEO`l4a=gHGhvf{>{%idtF# zp}bzwU)_`1`kR$Ve)grqqqF&`4sicFdqTL{90m+a{5(>Kp)-q_X()-x#_3DsBhxjb z%es!|@yS=mg9Q)&Im34vV%M`^sg?|dy?=QHcYQUVx z6W8Onf=jbpMo-hMUA}6y+6f=36eSuqiZJF%69F-#vu=FEy-BK&(B#DkKm{#v3e6L9 ztWSxxq%`%`_rk{Chbe5t)_p*ET8%PGG|Pq5IrZIWYexw^DI~bVcH2TnIEexzg74QU zW|j;g<@>P4*t=2~6w11O2`~4@_y!POT;HcGhAEKhaS^r6VkmjD>Z~vKQHQE0GGu** z-y^4=n4?3rrgPn9oeon<6}8^)y?$I{`DpGJ{(J8=xwtc(sr>gcT%EO!;>{XN>2#L+ z_CcvUF~OTLp5FiC-HIgBp~9{b6Lz6De%K%n${i&{G;Cp0mkk!F;nAC?Wijcl7m%aq zE3OH%8t@)Lyl|Pa?xb_(iNBaoqOYD`Q|g?0wp{qr>nC~a}m+Fo>q$^)q2UP*$sA1XSf1@|_To0&A@*({hw zj?g-eP{h5f$6E8tSMcj)!_SnH?uCC<@?W)l%!zZU)b?oLqD+_te$#lQSug$6KiAvj z?B!FPFAz6R5@;RjeT`9x<8ie)89V?DOpK$xiLhyG3eCnk^+gYU$DbmI&owup3Gn#O zuoPwtlj=IU>?!{2@KJuEC&~z|`L%e2)ofHPt@p+h!q8w5_SLJ88ao`@@ob7b^735Z z=h;7q?je*<;LSRzC>a*3CVwl0+ZNdT8RHmh1)Az=GG~l=xQgKW!mmao@tJkk*6(=A z!Aps%hbYY1JwFhV9NG%2>zPFQxyK*m-bGW59zDrFKGwzbr5b}ef?vZEl*GefUZD~u z?M2?#Qr~`MuoKy-dHrN73J}*Alc@Qf4oK;wB+teIK{X?b2T(inUt`CA{lfthJe(su z=M7VZqVRD^_1&*V7AssPkidk56S94P^-cMk6mMFzu$Z2ifN!erPcch}JWzVEkh;93 z!gZ&v2y}>esJ(J>4O9VRK`niZ7}jw0vWOmBqi;~U^-oYoI}Q8&(W9>&M1=M)ar$4K z{EX@SGw*|BLAt^Wh7OcLd;hQ?mmba;s=q!|Kwd^kGD}Md=!7acP zrTTHdMM1}ml~NYL$UEf;90DZksT;J-(x*!Vh_}_e9xf8mQx-l-!>dfpk}qceQ(I7o z`7ecn=}4TM?O1pe^0!JM)W*vb3vveUccoa1(9?{3b9LrTOv82u6j_o%4#zf5spNb* zi)Vk_bP{BR56ygFg@O1k=8?pf?pz^VOFQUL0CP*#g%sIYUJmDk$UKhw_u7eHLmQr) zzLiSZXf{gxEYP6e`}*c`$4vX+51c(;*ebXpd*bG5W?$9XRXM#jh*%kzF+s&Nq?J;T ziG)nN!KhR4)=cw=&*{+ySB=B`Y!NxU%w9HLI68c|k!21Ue$Lv2O4?4qpl(9=Osc!A zz;~-tGl}-a6D)nfU`h8<=u{HO-w_sRN66(wZA4tVrC9JKY2(fh_lR=s9Bq|1Dw?c} zFSrO2N50vg@`f2FV;=rTx26+fhLT|x%etR!^IV1)VTR-(1=@u&mm)pL50nv^Y}i!l zEE@Y0OEv@q_>$*!=)dfr+$wvHGh%!<(_%Q=_9Z0IZ%R=D7AcRIkrL%fCznLc@FDV6i3;LMj zq`sj-YG3{r`&bl&2PTC+rpy~=)*4fZ<;~REVJPYHP?0fTe^C^vPnV0SRAT^^?a?4BzUx^F9NF`01iVq2`JQ4pInxt>O= z&GcJZ*=Mzt2heM}+ig1qTv%;{lQZzOx0GU{nn0__HXTF49-Y)(cyYR4FX;6gpGbpF z`JM1j778kjVWuls_8}6(1DW;~+p&fJ_uM(OXunXo^7nQB0nyciKCmJUVn(KVO5y+; z^4YNt5n&XLWO{6fbaLdjRDIbg(#d`~mKIdX-ES2C1*VN zt41Ko9k}JJ1O&*gWk&HNOD?1{1Jq7iXAcA$8TGoIk&Pj!v(&^Iy)?DY4=qXd8NL?7 zccjvdODZ6Bvu)54)^?Bm{n+V++yP30<<`f=98IK_^mvMqNwtCd9$*@N@%iSJDI0=mcQ8kBjqdSZIR zs>Jq==JT6X_Yn>m_4qRE3Z#ySG$U-%QRN2+zD+{oP21D>IygxKKWucmAW5K8IDICK zWw+%ro%?eDI-6$Hg2Aidt&B(W|6}jJmK{gBFj4GYPjO;yp$K?)B6I-{f^Y;t!rUm} zL3r<;Uf|wU)z#hg&xtu2jWnav(YpvH2{6qv-?wsQ=FRZ~NWttTxx}XAHd4NUXz5C? z;0YPR1W97eTCkfl*ySQcmW4sU0OClX!)R`w`y)aXeEXa=B7dDVa+rbjIX94B4HMuV zWXxP1q_(q#1&tt405O(nZ+PGKICZ7?voj2ObtIURR+;V-+V1G4voJJ+3efI!3pa`^ z$Bqya)2O}yuY(DW@jByqHkUcd3XWA7+Kzv16z`J$$pyC0q zu(85u+p9;%z|TI#tiXg9tj+ zl#DIaq*5KeKU>m93J`Sj62-2njv2aNoY%c-$T+kUqA;D4Patf-(FHp~FCTGSf(h7u zHd@uea$a6!gsEH*Y$RMq=i+kl3+h`2HJT`>E7t=Dfs}z-GBA8A_Q@qrikn2!&M&Q< zONl?gWZ5TYZh-!__)4(WX?2nI^KqOy-@<4P05n!p1Jd5r3tkz!utJNFOrY%H9+Ke* z`O^4jP;!9iArU<35-~2Dm*8eVYsJY0 zX*#uxDdD~VGhRQ--UQZpK<8C1JHNMH^M9z`cG*z(Lud>holeO*T=G z=*b_e1iwv;BCO1GQv!xlq?sM^OfaC1deeq8^cdmYlQSkxP(HkDQ8Mq*8(j`ig?71; zP2uuVo<1G{Q76E>sZKGB1;`R+_VR|;$(V|_R(j`T zNy;N6Fhzrbt90cR+T435VWGTP4pplwJNd$Ptc9F$eTs|XacdsuDg|nVJlR0krpf6X zBe1vR$@>R2nr;oLO_dcXwOtlI!{u4#H9GWcvSR3DjKOLhi-y*W0DGhr1(6RY3;p1v z!P9U;fIOr*1aDM%{h-&R8clAGalGJRsi&10@_p33EHv%Wz#Aljc07OvXf=jTD&C3n zcG0V-&Ys}2^l*!!K(feAj((p57_n9^v;_(TJx@Gq)?L0l+kwzytAL* zF>!GtNR08{h zVrWH&{G@SF_!Bf=EmS zHf;`wVM~f8P^vvp-wchw#nRt9Sx!>$PLGAcq6((MTZ>QGnKXHjgJE>BVeGRf>004E z5%1gJZW<;kAyHHe@`yn9od6jg$v7G@=_5T>qoOH3cP|W`wCcoDTrp+TJ38}ieRvS! z>R9}y1{t8AyZ4AqhfNKHQeNB5n^{EAXpgTM-$3ua(?)zUqgDx3VYqskO?xP@K4