Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
21db667
extract getContractInstance from PXEOracleInterface
Dec 16, 2025
077c625
Merge branch 'next' into martin/refactor-pxe-oracle-interface-away
Dec 16, 2025
511fb18
extract getFunctionArtifact from PXEOracleInterface
Dec 16, 2025
3510ad5
extract getDebugFunctionName from PXEOracleInterface
Dec 16, 2025
cec0289
extract getNotes from PXEOracleInterface
Dec 17, 2025
cef673b
merge
Dec 17, 2025
2ebef27
extract getKeyValidationRequest from PXEOracleInterface
Dec 17, 2025
61dec41
extract getCompleteAddress from PXEOracleInterface
Dec 17, 2025
d1beaca
calculateDirectionalAppTaggingSecret
Dec 17, 2025
13118f9
getSharedSecret
Dec 17, 2025
cad9544
getL1ToL2MembershipWitness
Dec 17, 2025
37ea1da
fix regression
Dec 17, 2025
aabc3a0
getMembershipWitness
Dec 17, 2025
8e79091
getLowNullifierMembershipWitness
Dec 17, 2025
6b9cd95
fix some regressions
Dec 17, 2025
6844a44
fix regression
Dec 17, 2025
31b5cea
getBlock
Dec 17, 2025
b8f8de3
getNulliferMembershipWitness and getNullifierMembershipWitnessAtLates…
Dec 17, 2025
7514360
getPublicDataWitness
Dec 17, 2025
19c6fd7
getPublicStorageAt
Dec 17, 2025
95918da
fix regressions
Dec 18, 2025
245af9a
assertCompatibleOracleVersion
Dec 18, 2025
cd0313d
remove getSenders
Dec 18, 2025
3bf3ac2
storeCapsule
Dec 18, 2025
a022b02
rest of capsule delegates
Dec 18, 2025
a94eb8e
getStats
Dec 18, 2025
ee24653
almost the rest of the frigging owl
Dec 18, 2025
c99cdf6
getNullifierIndex
Dec 18, 2025
86db9f5
remove aztec node getter from PXEOracleInterface
Dec 18, 2025
ba6c7ed
note and event validation
Dec 18, 2025
2b60f30
good bye PXEOracleInterface
Dec 18, 2025
46c5d3c
move getContractInstance to utility execution oracle
Dec 18, 2025
74fdb15
move getNotes to UtilityExecutionOracle
Dec 18, 2025
f4c1c6b
bring some more stuff inside oracles
Dec 18, 2025
2a3c9de
getMembershipWitness
Dec 18, 2025
8ae2fb2
more
Dec 18, 2025
79d4fda
common => utility_execution_oracle 1/
Dec 18, 2025
71d42be
common => utility_execution_oracle 2/
Dec 18, 2025
7b6e829
common => utility_execution_oracle 3/
Dec 18, 2025
9074c82
common => utility_execution_oracle 4/
Dec 18, 2025
fb87a5a
common => utility_execution_oracle 5/
Dec 18, 2025
014fca7
common => utility_execution_oracle 6/
Dec 18, 2025
b2cfbfb
common => utility_execution_oracle 7/
Dec 18, 2025
5b7d6cd
common => utility_execution_oracle 8/
Dec 18, 2025
78c8966
common => utility_execution_oracle 9/
Dec 18, 2025
750862b
common => utility_execution_oracle 10/
Dec 19, 2025
57362d2
introduce NoteSynchronizer
Dec 19, 2025
66c7836
deliverNote
Dec 19, 2025
eec0dd3
rename NoteSynchronizer => NoteService
Dec 19, 2025
375364b
EventService
Dec 19, 2025
6ac2b9c
refactor validateEnqueuedNotesAndEvents
Dec 19, 2025
4a0d9d4
getFunctionArtifactWithDebugMetadata
Dec 19, 2025
88e3f33
good bye common
Dec 19, 2025
9a6bcbd
simplify readCurrentClassId
Dec 19, 2025
455db79
help shaking trees
Dec 19, 2025
80708ce
fix weird lint issue
Dec 19, 2025
2fe98d5
LogService
Dec 19, 2025
0b97781
bulkRetrieveLogs
Dec 19, 2025
343adc2
MembershipWitnessService
Dec 19, 2025
3e4cca4
more MembershipWitnessService
Dec 19, 2025
20b47ff
getBlock
Dec 19, 2025
5f9a2e9
getNotes
Dec 19, 2025
dbc48fc
TreeMembershipService
Dec 19, 2025
9fd681e
PublicStorageService
Dec 19, 2025
719255e
port docs
Dec 19, 2025
ba5c24b
good bye ExecutionDataProvider
Dec 19, 2025
268c6dd
wanted
Dec 19, 2025
bb4422b
merge
Dec 19, 2025
5b0a17e
reduce barrel imports
Dec 19, 2025
e03955a
POC: optimize playground build
Dec 22, 2025
bd84fe7
remove report
Dec 22, 2025
2567d4c
Revert "remove report"
Dec 22, 2025
fb2c21e
Revert "POC: optimize playground build"
Dec 22, 2025
328a86b
hunt down barrel imports
Dec 22, 2025
c3d269b
went too far in the shaking
Dec 22, 2025
ef40d3b
sync from PXE instead of from contracts
Dec 22, 2025
91241dd
make it bootstrap
Dec 22, 2025
b33eb0f
Merge branch 'next' into martin/f-162-explicit-contract-sync
Dec 22, 2025
9cfbcc1
guard against external sync_private_state invocations
Dec 23, 2025
864a62c
more guards
Dec 23, 2025
ee980bf
decomplect sync in txe
Dec 23, 2025
33a4acd
refactor to unify where sync happens
Dec 23, 2025
43101f3
wip
Dec 23, 2025
b28b007
fix getPrivateEvents
Dec 23, 2025
814ea1d
Sync private state before simulating utility in TXE
Jan 2, 2026
88979d2
Merge branch 'next' into martin/f-162-explicit-contract-sync
Jan 2, 2026
a79eec1
Fix PXE unit test
Jan 2, 2026
beb5666
Call syncPrivateState on txePrivateCallNewFlow
Jan 2, 2026
dd934b7
Sync notes when entering private state in TXE
Jan 2, 2026
4ac3539
fix circuit recorder tests
Jan 2, 2026
9ef873e
fix multiple blobs test
Jan 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,6 @@ pub(crate) comptime fn get_abi_relevant_attributes(f: FunctionDefinition) -> Quo
attributes
}

/// Injects a call to `aztec::messages::discovery::discover_new_messages`, causing for new notes to be added to PXE and made
/// available for the current execution.
pub(crate) comptime fn create_message_discovery_call() -> Quoted {
quote {
/// Safety: message discovery returns nothing and is performed solely for its side-effects. It is therefore
/// always safe to call.
unsafe {
dep::aztec::messages::discovery::discover_new_messages(
self.address,
_compute_note_hash_and_nullifier,
);
};
}
}

/// Injects an authwit verification check of the form:
/// ```
/// if (!from.eq(context.msg_sender().unwrap())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::macros::{
internals_functions_generation::external::helpers::{
create_authorize_once_check, create_message_discovery_call, get_abi_relevant_attributes,
create_authorize_once_check, get_abi_relevant_attributes,
},
notes::NOTES,
utils::{
fn_has_authorize_once, fn_has_noinitcheck, fn_has_nophasecheck, is_fn_initializer,
is_fn_only_self, is_fn_view, module_has_initializer, module_has_storage,
Expand Down Expand Up @@ -120,16 +119,6 @@ pub(crate) comptime fn generate_private_external(f: FunctionDefinition) -> Quote
}
};

// All private functions perform message discovery, since they may need to access notes. This is slightly
// inefficient and could be improved by only doing it once we actually attempt to read any. Note that the message
// discovery call syncs private events as well. We do not sync those here if there are no notes because we don't
// have an API that would access events from private functions.
Comment on lines -123 to -126
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: check that we're fine not handling this case with this new implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked: this is indeed an optimization, if the contract doesn't have any notes, discovery isn't necessary. We should discuss if it's worth keeping it now that we control this process from TS world

let message_discovery_call = if NOTES.len() > 0 {
create_message_discovery_call()
} else {
quote {}
};

// Inject the authwit check if the function is marked with #[authorize_once].
let authorize_once_check = if fn_has_authorize_once(f) {
create_authorize_once_check(f, true)
Expand Down Expand Up @@ -192,7 +181,6 @@ pub(crate) comptime fn generate_private_external(f: FunctionDefinition) -> Quote
$init_check
$internal_check
$view_check
$message_discovery_call
$authorize_once_check
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::macros::{
internals_functions_generation::external::helpers::create_message_discovery_call,
utils::module_has_storage,
};
use crate::macros::utils::module_has_storage;

pub(crate) comptime fn generate_utility_external(f: FunctionDefinition) -> Quoted {
// Initialize Storage if module has storage
Expand All @@ -28,16 +25,10 @@ pub(crate) comptime fn generate_utility_external(f: FunctionDefinition) -> Quote
};
};

// All utility functions perform message discovery, since they may need to access private notes that would be
// found during this process or they may be used to sync private events from TypeScript
// (`sync_private_state` function gets invoked by PXE::getPrivateEvents function).
let message_discovery_call = create_message_discovery_call();

// A quote to be injected at the beginning of the function body.
let to_prepend = quote {
dep::aztec::oracle::version::assert_compatible_oracle_version();
$contract_self_creation
$message_discovery_call
};

let original_function_name = f.name();
Expand Down
8 changes: 6 additions & 2 deletions yarn-project/end-to-end/src/e2e_multiple_blobs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,19 @@ describe('e2e_multiple_blobs', () => {
const utilityFunctions = contractArtifact.functions.filter(fn => fn.functionType == FunctionType.UTILITY);

// Increase the minimum number of txs per block so that all txs will be mined in the same block.
const TX_COUNT = 5;
// We need enough function broadcasts to produce more than FIELDS_PER_BLOB (4096) fields.
const TX_COUNT = 8;
await aztecNodeAdmin.setConfig({ minTxsPerBlock: TX_COUNT });

const provenTxs = [
// 1 contract deployment tx.
await publishContractClass(wallet, AvmTestContract.artifact),
// 2 private function broadcast txs.
// 5 private function broadcast txs.
await broadcastFunction(privateFunctions[0]),
await broadcastFunction(privateFunctions[1]),
await broadcastFunction(privateFunctions[2]),
await broadcastFunction(privateFunctions[3]),
await broadcastFunction(privateFunctions[4]),
// 1 utility function broadcast tx.
await broadcastFunction(utilityFunctions[0]),
// 1 tx to emit note hash, nullifier, l2_to_l1_message, private log and public log.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ 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 { isProtocolContract, protocolContractsHash } from '@aztec/protocol-contracts';
import {
type CircuitSimulator,
ExecutionError,
Expand Down Expand Up @@ -134,6 +134,13 @@ export class ContractFunctionSimulator {
): Promise<PrivateExecutionResult> {
const simulatorSetupTimer = new Timer();

// Protocol contracts don't have private state to sync
if (!isProtocolContract(contractAddress)) {
await this.contractDataProvider.syncPrivateState(contractAddress, selector, privateSyncCall =>
this.runUtility(privateSyncCall, [], anchorBlockHeader, scopes),
);
}

await verifyCurrentClassId(contractAddress, this.aztecNode, this.contractDataProvider, anchorBlockHeader);

const entryPointArtifact = await this.contractDataProvider.getFunctionArtifactWithDebugMetadata(
Expand Down Expand Up @@ -170,6 +177,9 @@ export class ContractFunctionSimulator {
request.txContext,
callContext,
anchorBlockHeader,
async call => {
await this.runUtility(call, [], anchorBlockHeader, scopes);
},
request.authWitnesses,
request.capsules,
HashedValuesCache.create(request.argsOfCalls),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,26 @@ describe('Private Execution test suite', () => {
}
return { ...artifact, debug: undefined };
});
contractDataProvider.getFunctionCall.mockImplementation(async (functionName, args, to) => {
const contract = contracts[to.toString()];
if (!contract) {
throw new Error(`Contract not found: ${to}`);
}
const functionArtifact = getFunctionArtifactByName(contract, functionName);
return {
name: functionArtifact.name,
args: encodeArguments(functionArtifact, args),
selector: await FunctionSelector.fromNameAndParameters(functionArtifact.name, functionArtifact.parameters),
type: functionArtifact.functionType,
to,
hideMsgSender: false,
isStatic: functionArtifact.isStatic,
returnTypes: functionArtifact.returnTypes,
};
});
contractDataProvider.syncPrivateState.mockImplementation(async (contractAddress, _functionSelector, _executor) => {
await contractDataProvider.getFunctionCall('sync_private_state', [], contractAddress);
});

capsuleDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null));

Expand Down Expand Up @@ -671,18 +691,40 @@ describe('Private Execution test suite', () => {
artifact: ParentContractArtifact,
anchorBlockHeader,
functionName: 'entry_point',
contractAddress: parentAddress,
});

expect(result.returnValues).toEqual([new Fr(privateIncrement)]);

// First fetch of the function artifact is the parent contract
expect(contractDataProvider.getFunctionArtifact.mock.calls[1]).toEqual([childAddress, childSelector]);
expect(contractDataProvider.getFunctionArtifact).toHaveBeenCalledWith(childAddress, childSelector);
expect(result.nestedExecutionResults).toHaveLength(1);
expect(result.nestedExecutionResults[0].returnValues).toEqual([new Fr(privateIncrement)]);
expect(result.publicInputs.privateCallRequests.array[0].callContext).toEqual(
result.nestedExecutionResults[0].publicInputs.callContext,
);
});

it('syncs private state for child in nested calls', async () => {
const childArtifact = getFunctionArtifactByName(ChildContractArtifact, 'value');
const parentAddress = await AztecAddress.random();
const childAddress = await AztecAddress.random();
const childSelector = await FunctionSelector.fromNameAndParameters(childArtifact.name, childArtifact.parameters);

await mockContractInstance(ChildContractArtifact, childAddress);

contractDataProvider.getFunctionCall.mockClear();

const args = [childAddress, childSelector];
await runSimulator({
args,
artifact: ParentContractArtifact,
anchorBlockHeader,
functionName: 'entry_point',
contractAddress: parentAddress,
});

expect(contractDataProvider.getFunctionCall).toHaveBeenCalledWith('sync_private_state', [], childAddress);
});
});

describe('consuming messages', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ 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 { isProtocolContract } from '@aztec/protocol-contracts';
import { type CircuitSimulator, toACVMWitness } from '@aztec/simulator/client';
import {
type FunctionAbi,
type FunctionArtifact,
type FunctionCall,
FunctionSelector,
type NoteSelector,
countArgumentsSize,
Expand Down Expand Up @@ -83,6 +85,8 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
private readonly callContext: CallContext,
/** Header of a block whose state is used during private execution (not the block the transaction is included in). */
protected override readonly anchorBlockHeader: BlockHeader,
/** Needed to trigger contract synchronization before nested calls */
private readonly utilityExecutor: (call: FunctionCall) => Promise<void>,
/** List of transient auth witnesses to be used during this simulation */
authWitnesses: AuthWitness[],
capsules: Capsule[],
Expand Down Expand Up @@ -553,6 +557,11 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
this.anchorBlockHeader,
);

// Protocol contracts don't have private state to sync
if (!isProtocolContract(targetContractAddress)) {
await this.contractDataProvider.syncPrivateState(targetContractAddress, functionSelector, this.utilityExecutor);
}

const targetArtifact = await this.contractDataProvider.getFunctionArtifactWithDebugMetadata(
targetContractAddress,
functionSelector,
Expand All @@ -567,6 +576,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
derivedTxContext,
derivedCallContext,
this.anchorBlockHeader,
this.utilityExecutor,
this.authWitnesses,
this.capsules,
this.executionCache,
Expand Down
44 changes: 29 additions & 15 deletions yarn-project/pxe/src/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Timer } from '@aztec/foundation/timer';
import { KeyStore } from '@aztec/key-store';
import type { AztecAsyncKVStore } from '@aztec/kv-store';
import { L2TipsKVStore } from '@aztec/kv-store/stores';
import { type ProtocolContractsProvider, protocolContractNames } from '@aztec/protocol-contracts';
import { type ProtocolContractsProvider, isProtocolContract, protocolContractNames } from '@aztec/protocol-contracts';
import type { CircuitSimulator } from '@aztec/simulator/client';
import {
type ContractArtifact,
Expand Down Expand Up @@ -996,6 +996,14 @@ export class PXE {
const syncTime = syncTimer.ms();
const functionTimer = new Timer();
const contractFunctionSimulator = this.#getSimulatorForTx();

// Protocol contracts don't have private state to sync
if (!isProtocolContract(call.to)) {
await this.contractDataProvider.syncPrivateState(call.to, call.selector, privateSyncCall =>
this.#simulateUtility(contractFunctionSimulator, privateSyncCall),
);
}

const executionResult = await this.#simulateUtility(contractFunctionSimulator, call, authwits ?? [], scopes);
const functionTime = functionTimer.ms();

Expand Down Expand Up @@ -1037,21 +1045,27 @@ export class PXE {
* Defaults to the latest known block to PXE + 1.
* @returns - The packed events with block and tx metadata.
*/
public async getPrivateEvents(
eventSelector: EventSelector,
filter: PrivateEventFilter,
): Promise<PackedPrivateEvent[]> {
// We need to manually trigger private state sync to have a guarantee that all the events are available.
const call = await this.contractDataProvider.getFunctionCall('sync_private_state', [], filter.contractAddress);
await this.simulateUtility(call);

const sanitizedFilter = await new PrivateEventFilterValidator(this.anchorBlockDataProvider).validate(filter);

this.log.debug(
`Getting private events for ${sanitizedFilter.contractAddress.toString()} from ${sanitizedFilter.fromBlock} to ${sanitizedFilter.toBlock}`,
);
public getPrivateEvents(eventSelector: EventSelector, filter: PrivateEventFilter): Promise<PackedPrivateEvent[]> {
return this.#putInJobQueue(async () => {
await this.blockStateSynchronizer.sync();
const contractFunctionSimulator = this.#getSimulatorForTx();
// Protocol contracts don't have private state to sync
if (!isProtocolContract(filter.contractAddress)) {
await this.contractDataProvider.syncPrivateState(
filter.contractAddress,
null,
async privateSyncCall => await this.#simulateUtility(contractFunctionSimulator, privateSyncCall),
);
}

const sanitizedFilter = await new PrivateEventFilterValidator(this.anchorBlockDataProvider).validate(filter);

return this.privateEventDataProvider.getPrivateEvents(eventSelector, sanitizedFilter);
this.log.debug(
`Getting private events for ${sanitizedFilter.contractAddress.toString()} from ${sanitizedFilter.fromBlock} to ${sanitizedFilter.toBlock}`,
);

return this.privateEventDataProvider.getPrivateEvents(eventSelector, sanitizedFilter);
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,20 @@ export class ContractDataProvider {
returnTypes: functionDao.returnTypes,
};
}

// Synchronize target contract data
public async syncPrivateState(
contractAddress: AztecAddress,
functionToInvokeAfterSync: FunctionSelector | null,
utilityExecutor: (privateSyncCall: FunctionCall) => Promise<any>,
) {
const syncPrivateStateFunctionCall = await this.getFunctionCall('sync_private_state', [], contractAddress);
if (functionToInvokeAfterSync && functionToInvokeAfterSync.equals(syncPrivateStateFunctionCall.selector)) {
throw new Error(
'Forbidden `sync_private_state` invocation. `sync_private_state` can only be invoked by PXE, manual execution can lead to inconsistencies.',
);
}

return utilityExecutor(syncPrivateStateFunctionCall);
}
}
Loading
Loading