Skip to content

Commit 8912d34

Browse files
authored
feat: utilityGetUtilityContext oracle (#16179)
Introduces a new `utilityGetUtilityContext` oracle that returns all the info needed for `UtilityContext` construction in a single call. This reduces the number of `AztecNode` calls from 4 to 1. This should significantly speed up utility function execution when there is a high latency between PXE and the node. ## Bug fix in this PR Before, in PXE, there was a serious bug in the UtilityContext construction because it was constructed based on values obtained from aztec node. This is just wrong because the simulation should be run against the PXE's view of the network state and not against the node's view. I think we didn't stumble upon this being an issue because we perform a sync right when the simulation is started. But I think it would be inevitable to happen that a node would sync a block from p2p in between the initial PXE sync and construction of the utility context and at that point we would enter an undefined behavior and a world of pain of debugging. ## Followup work As Nico mentioned in review round 1 seems to make sense to nuke the following oracles: - `utilityGetChainId` - `utilityGetVersion` - `utilityGetTimestamp` and possibly also - `utilityGetBlockNumber` as all these values are contained inside the response from the `utilityGetUtilityContext` oracle. That would also allow me to nuke these handlers from `PXEOracleInterface` which would make me very happy because the naming there was terrible (`PXEOracleInterface::getBlockHeader` returned block header of the latest block synchronized by `PXE` while `getTimestamp` and `getBlockNumber` returned values from aztec node.
2 parents 31331e2 + ef44b87 commit 8912d34

File tree

15 files changed

+191
-114
lines changed

15 files changed

+191
-114
lines changed

barretenberg/cpp/src/barretenberg/vm2/common/protocol_contract_data.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
namespace bb::avm2 {
55

66
const std::unordered_map<CanonicalAddress, DerivedAddress> derived_addresses = {
7-
{ CANONICAL_AUTH_REGISTRY_ADDRESS, AztecAddress("0x1c2474a77af3c756895867fa7622e5e9295121b35849e965f4614d51f00c62e2") },
8-
{ CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, AztecAddress("0x1f4284dea3e475d9d40283c7fe4c1aef5cdea59c012a8fa84c0c9ebe0752327e") },
9-
{ CONTRACT_CLASS_REGISTRY_CONTRACT_ADDRESS, AztecAddress("0x1bd2e13b35e2745e638132262e6642a86ac4219f1b7a95086bebe250f5d557ea") },
10-
{ MULTI_CALL_ENTRYPOINT_ADDRESS, AztecAddress("0x27b35c8e0851d28c56846ce8977155ffbb5725c4e6b47a67f5d5c6639d1c1a1c") },
11-
{ FEE_JUICE_ADDRESS, AztecAddress("0x2ee34a2d304636f81114dffc8a2357e03e08345a008262eb36112f95aaa58eb1") },
12-
{ ROUTER_ADDRESS, AztecAddress("0x180b58198cce7c0866a828841df6eca6369a71a6e884ca5cf08938301d7ad0ca") }
7+
{ CANONICAL_AUTH_REGISTRY_ADDRESS, AztecAddress("0x18544daa308f64e57ec663f668b0ea3b0cbebd1eb97f8df5aeb39e8d8719ecf5") },
8+
{ CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, AztecAddress("0x171962d76064dbcb6fcb6ab6cbb32205bb47b55a5c74debcbe17ae62582817ce") },
9+
{ CONTRACT_CLASS_REGISTRY_CONTRACT_ADDRESS, AztecAddress("0x016b8b23f165308f796bb8f578bbf4b9931dd0a90d99c7c2d2dddacdde181552") },
10+
{ MULTI_CALL_ENTRYPOINT_ADDRESS, AztecAddress("0x2b1c3ae38d7f786b5509c369dc5e14d5ed42ad7bcbc11333cd67f2470d84a1c5") },
11+
{ FEE_JUICE_ADDRESS, AztecAddress("0x006806d65634d483ae6e8b00cfa4fcbd6b1ed3af44370d893230ed6256ca6f15") },
12+
{ ROUTER_ADDRESS, AztecAddress("0x05ccb6ee750c3b88305ebbc2c7390a6c08bf70f7a14eb48f9ab25af6c55da3c3") }
1313
};
1414

1515
} // namespace bb::avm2

noir-projects/aztec-nr/aztec/src/context/utility_context.nr

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
use crate::oracle::{
2-
execution::{get_block_number, get_chain_id, get_contract_address, get_timestamp, get_version},
3-
storage::storage_read,
4-
};
5-
use dep::protocol_types::{address::AztecAddress, traits::Packable};
1+
use crate::oracle::{execution::get_utility_context, storage::storage_read};
2+
use protocol_types::{address::AztecAddress, traits::Packable};
63

4+
// If you'll modify this struct don't forget to update utility_context.ts as well.
75
pub struct UtilityContext {
86
block_number: u32,
97
timestamp: u64,
@@ -14,31 +12,35 @@ pub struct UtilityContext {
1412

1513
impl UtilityContext {
1614
pub unconstrained fn new() -> Self {
17-
// We could call these oracles on the getters instead of at creation, which makes sense given that they might
18-
// not even be accessed. However any performance gains are minimal, and we'd rather fail early if a user
19-
// incorrectly attempts to create a UtilityContext in an environment in which these oracles are not
20-
// available.
21-
let block_number = get_block_number();
22-
let timestamp = get_timestamp();
23-
let contract_address = get_contract_address();
24-
let version = get_version();
25-
let chain_id = get_chain_id();
26-
Self { block_number, timestamp, contract_address, version, chain_id }
15+
get_utility_context()
2716
}
2817

2918
pub unconstrained fn at(contract_address: AztecAddress) -> Self {
30-
let block_number = get_block_number();
31-
let timestamp = get_timestamp();
32-
let chain_id = get_chain_id();
33-
let version = get_version();
34-
Self { block_number, timestamp, contract_address, version, chain_id }
19+
// We get a context with default contract address, and then we construct the final context with the provided
20+
// contract address.
21+
let default_context = get_utility_context();
22+
23+
Self {
24+
block_number: default_context.block_number,
25+
timestamp: default_context.timestamp,
26+
contract_address,
27+
version: default_context.version,
28+
chain_id: default_context.chain_id,
29+
}
3530
}
3631

3732
pub unconstrained fn at_historical(contract_address: AztecAddress, block_number: u32) -> Self {
38-
let timestamp = get_timestamp();
39-
let chain_id = get_chain_id();
40-
let version = get_version();
41-
Self { block_number, timestamp, contract_address, version, chain_id }
33+
// We get a context with default contract address and block number, and then we construct the final context
34+
// with the provided contract address and block number.
35+
let default_context = get_utility_context();
36+
37+
Self {
38+
block_number,
39+
timestamp: default_context.timestamp,
40+
contract_address,
41+
version: default_context.version,
42+
chain_id: default_context.chain_id,
43+
}
4244
}
4345

4446
pub fn block_number(self) -> u32 {

noir-projects/aztec-nr/aztec/src/macros/functions/utils.nr

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,9 @@ pub(crate) comptime fn transform_utility(f: FunctionDefinition) {
288288
// Create utility context
289289
let context_creation =
290290
quote { let mut context = dep::aztec::context::utility_context::UtilityContext::new(); };
291-
let module_has_storage = module_has_storage(f.module());
292291

293292
// Initialize Storage if module has storage
294-
let storage_init = if module_has_storage {
293+
let storage_init = if module_has_storage(f.module()) {
295294
quote {
296295
// Some functions don't access storage, but it'd be quite difficult to only inject this variable if it is
297296
// referenced. We instead ignore 'unused variable' warnings for it.

noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,13 @@ pub struct NoteHashAndNullifier {
6161
/// ```
6262
type ComputeNoteHashAndNullifier<Env> = unconstrained fn[Env](/* packed_note */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /* storage_slot */ Field, /* note_type_id */ Field, /* contract_address */ AztecAddress, /* note nonce */ Field) -> Option<NoteHashAndNullifier>;
6363

64-
/// Performs the message discovery process, in which private are downloaded and inspected to find new private notes,
65-
/// partial notes and events, etc., and pending partial notes are processed to search for their completion logs.
64+
/// Performs the message discovery process, in which private logs are downloaded and inspected to find new private
65+
/// notes, partial notes and events, etc., and pending partial notes are processed to search for their completion logs.
6666
/// This is the mechanism via which a contract updates its knowledge of its private state.
6767
///
68+
/// Note that the state is synchronized up to the latest block synchronized by PXE. That should be close to the chain
69+
/// tip as block synchronization is performed before contract function simulation is done.
70+
///
6871
/// Receives the address of the contract on which discovery is performed along with its
6972
/// `compute_note_hash_and_nullifier` function.
7073
pub unconstrained fn discover_new_messages<Env>(

noir-projects/aztec-nr/aztec/src/oracle/execution.nr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::context::utility_context::UtilityContext;
12
use dep::protocol_types::address::AztecAddress;
23

34
#[oracle(utilityGetContractAddress)]
@@ -15,6 +16,9 @@ unconstrained fn get_chain_id_oracle() -> Field {}
1516
#[oracle(utilityGetVersion)]
1617
unconstrained fn get_version_oracle() -> Field {}
1718

19+
#[oracle(utilityGetUtilityContext)]
20+
unconstrained fn get_utility_context_oracle() -> UtilityContext {}
21+
1822
pub unconstrained fn get_contract_address() -> AztecAddress {
1923
get_contract_address_oracle()
2024
}
@@ -34,3 +38,7 @@ pub unconstrained fn get_chain_id() -> Field {
3438
pub unconstrained fn get_version() -> Field {
3539
get_version_oracle()
3640
}
41+
42+
pub unconstrained fn get_utility_context() -> UtilityContext {
43+
get_utility_context_oracle()
44+
}

yarn-project/pxe/src/contract_function_simulator/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export { Oracle } from './oracle/oracle.js';
99
export { executePrivateFunction, extractPrivateCircuitPublicInputs } from './oracle/private_execution.js';
1010
export { generateSimulatedProvingResult } from './contract_function_simulator.js';
1111
export { packAsRetrievedNote } from './oracle/note_packing_utils.js';
12+
export { UtilityContext } from './noir-structs/utility_context.js';
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Fr } from '@aztec/foundation/fields';
2+
import type { FieldsOf } from '@aztec/foundation/types';
3+
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4+
import type { UInt64 } from '@aztec/stdlib/types';
5+
6+
/**
7+
* TypeScript counterpart of utility_context.nr. Used only as a return value for the utilityGetUtilityContext oracle.
8+
*/
9+
export class UtilityContext {
10+
private constructor(
11+
public readonly blockNumber: number,
12+
public readonly timestamp: UInt64,
13+
public readonly contractAddress: AztecAddress,
14+
public readonly version: Fr,
15+
public readonly chainId: Fr,
16+
) {}
17+
18+
static from(fields: FieldsOf<UtilityContext>) {
19+
return new UtilityContext(
20+
fields.blockNumber,
21+
fields.timestamp,
22+
fields.contractAddress,
23+
fields.version,
24+
fields.chainId,
25+
);
26+
}
27+
28+
/**
29+
* Returns a representation of the utility context as expected by intrinsic Noir deserialization.
30+
* The order of the fields has to be the same as the order of the fields in the utility_context.nr.
31+
*/
32+
public toNoirRepresentation(): (string | string[])[] {
33+
// TODO(#12874): remove the stupid as string conversion by modifying ForeignCallOutput type in acvm.js
34+
return [
35+
new Fr(this.blockNumber).toString() as string,
36+
new Fr(this.timestamp).toString() as string,
37+
this.contractAddress.toString() as string,
38+
this.version.toString() as string,
39+
this.chainId.toString() as string,
40+
];
41+
}
42+
}

yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ export class Oracle {
9898
return [toACVMField(await this.typedOracle.utilityGetChainId())];
9999
}
100100

101+
async utilityGetUtilityContext(): Promise<(ACVMField | ACVMField[])[]> {
102+
const context = await this.typedOracle.utilityGetUtilityContext();
103+
return context.toNoirRepresentation();
104+
}
105+
101106
async utilityGetKeyValidationRequest([pkMHash]: ACVMField[]): Promise<ACVMField[]> {
102107
const keyValidationRequest = await this.typedOracle.utilityGetKeyValidationRequest(Fr.fromString(pkMHash));
103108

yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ describe('Oracle Version Check test suite', () => {
2323
executionDataProvider = mock<ExecutionDataProvider>();
2424

2525
// Mock basic oracle responses
26-
executionDataProvider.getChainId.mockResolvedValue(1);
27-
executionDataProvider.getVersion.mockResolvedValue(1);
28-
executionDataProvider.getTimestamp.mockResolvedValue(0n);
29-
executionDataProvider.getBlockNumber.mockResolvedValue(1);
3026
executionDataProvider.getPublicStorageAt.mockResolvedValue(Fr.ZERO);
3127
executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null));
3228
executionDataProvider.getBlockHeader.mockResolvedValue(BlockHeader.empty());

yarn-project/pxe/src/contract_function_simulator/oracle/typed_oracle.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness }
1010
import type { BlockHeader } from '@aztec/stdlib/tx';
1111
import type { UInt64 } from '@aztec/stdlib/types';
1212

13+
import type { UtilityContext } from '../noir-structs/utility_context.js';
1314
import type { MessageLoadOracleInputs } from './message_load_oracle_inputs.js';
1415

1516
/**
@@ -82,6 +83,10 @@ export abstract class TypedOracle {
8283
return Promise.reject(new OracleMethodNotAvailableError(this.className, 'utilityGetVersion'));
8384
}
8485

86+
utilityGetUtilityContext(): Promise<UtilityContext> {
87+
return Promise.reject(new OracleMethodNotAvailableError(this.className, 'utilityGetUtilityContext'));
88+
}
89+
8590
utilityGetKeyValidationRequest(_pkMHash: Fr): Promise<KeyValidationRequest> {
8691
return Promise.reject(new OracleMethodNotAvailableError(this.className, 'utilityGetKeyValidationRequest'));
8792
}

0 commit comments

Comments
 (0)