@aztec/pxe 0.0.1-commit.2b2662070 → 0.0.1-commit.2c0ee1788

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dest/block_synchronizer/block_stream_source.d.ts +10 -0
  2. package/dest/block_synchronizer/block_stream_source.d.ts.map +1 -0
  3. package/dest/block_synchronizer/block_stream_source.js +37 -0
  4. package/dest/block_synchronizer/block_synchronizer.d.ts +6 -2
  5. package/dest/block_synchronizer/block_synchronizer.d.ts.map +1 -1
  6. package/dest/block_synchronizer/block_synchronizer.js +24 -10
  7. package/dest/contract_function_simulator/contract_function_simulator.d.ts +1 -1
  8. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  9. package/dest/contract_function_simulator/contract_function_simulator.js +3 -4
  10. package/dest/contract_function_simulator/oracle/interfaces.d.ts +3 -1
  11. package/dest/contract_function_simulator/oracle/interfaces.d.ts.map +1 -1
  12. package/dest/contract_function_simulator/oracle/oracle.d.ts +2 -1
  13. package/dest/contract_function_simulator/oracle/oracle.d.ts.map +1 -1
  14. package/dest/contract_function_simulator/oracle/oracle.js +7 -0
  15. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +10 -11
  16. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  17. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +18 -15
  18. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +9 -4
  19. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  20. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +52 -4
  21. package/dest/contract_function_simulator/pick_notes.d.ts +1 -1
  22. package/dest/contract_function_simulator/pick_notes.d.ts.map +1 -1
  23. package/dest/contract_function_simulator/pick_notes.js +11 -1
  24. package/dest/events/event_service.d.ts +1 -1
  25. package/dest/events/event_service.d.ts.map +1 -1
  26. package/dest/events/event_service.js +10 -1
  27. package/dest/events/private_event_filter_validator.d.ts +3 -2
  28. package/dest/events/private_event_filter_validator.d.ts.map +1 -1
  29. package/dest/events/private_event_filter_validator.js +15 -0
  30. package/dest/oracle_version.d.ts +2 -2
  31. package/dest/oracle_version.js +2 -2
  32. package/dest/private_kernel/private_kernel_execution_prover.d.ts +1 -1
  33. package/dest/private_kernel/private_kernel_execution_prover.d.ts.map +1 -1
  34. package/dest/private_kernel/private_kernel_execution_prover.js +4 -7
  35. package/dest/private_kernel/private_kernel_oracle.d.ts +5 -5
  36. package/dest/private_kernel/private_kernel_oracle.d.ts.map +1 -1
  37. package/dest/private_kernel/private_kernel_oracle.js +12 -15
  38. package/dest/pxe.d.ts +15 -4
  39. package/dest/pxe.d.ts.map +1 -1
  40. package/dest/pxe.js +41 -18
  41. package/dest/storage/anchor_block_store/anchor_block_store.js +1 -1
  42. package/dest/storage/capsule_store/capsule_store.d.ts +1 -1
  43. package/dest/storage/capsule_store/capsule_store.d.ts.map +1 -1
  44. package/dest/storage/capsule_store/capsule_store.js +8 -5
  45. package/dest/storage/contract_store/contract_store.d.ts +1 -1
  46. package/dest/storage/contract_store/contract_store.d.ts.map +1 -1
  47. package/dest/storage/contract_store/contract_store.js +4 -2
  48. package/dest/storage/private_event_store/private_event_store.d.ts +1 -1
  49. package/dest/storage/private_event_store/private_event_store.d.ts.map +1 -1
  50. package/dest/storage/private_event_store/private_event_store.js +3 -0
  51. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts +1 -1
  52. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts.map +1 -1
  53. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.js +1 -1
  54. package/package.json +16 -16
  55. package/src/block_synchronizer/block_stream_source.ts +52 -0
  56. package/src/block_synchronizer/block_synchronizer.ts +27 -11
  57. package/src/contract_function_simulator/contract_function_simulator.ts +2 -3
  58. package/src/contract_function_simulator/oracle/interfaces.ts +10 -0
  59. package/src/contract_function_simulator/oracle/oracle.ts +14 -0
  60. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +32 -19
  61. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +81 -4
  62. package/src/contract_function_simulator/pick_notes.ts +13 -1
  63. package/src/events/event_service.ts +13 -1
  64. package/src/events/private_event_filter_validator.ts +21 -1
  65. package/src/oracle_version.ts +2 -2
  66. package/src/private_kernel/private_kernel_execution_prover.ts +4 -9
  67. package/src/private_kernel/private_kernel_oracle.ts +14 -14
  68. package/src/pxe.ts +76 -24
  69. package/src/storage/anchor_block_store/anchor_block_store.ts +1 -1
  70. package/src/storage/capsule_store/capsule_store.ts +15 -5
  71. package/src/storage/contract_store/contract_store.ts +8 -6
  72. package/src/storage/private_event_store/private_event_store.ts +4 -0
  73. package/src/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/pxe",
3
- "version": "0.0.1-commit.2b2662070",
3
+ "version": "0.0.1-commit.2c0ee1788",
4
4
  "type": "module",
5
5
  "typedocOptions": {
6
6
  "entryPoints": [
@@ -70,19 +70,19 @@
70
70
  ]
71
71
  },
72
72
  "dependencies": {
73
- "@aztec/bb-prover": "0.0.1-commit.2b2662070",
74
- "@aztec/bb.js": "0.0.1-commit.2b2662070",
75
- "@aztec/builder": "0.0.1-commit.2b2662070",
76
- "@aztec/constants": "0.0.1-commit.2b2662070",
77
- "@aztec/ethereum": "0.0.1-commit.2b2662070",
78
- "@aztec/foundation": "0.0.1-commit.2b2662070",
79
- "@aztec/key-store": "0.0.1-commit.2b2662070",
80
- "@aztec/kv-store": "0.0.1-commit.2b2662070",
81
- "@aztec/noir-protocol-circuits-types": "0.0.1-commit.2b2662070",
82
- "@aztec/noir-types": "0.0.1-commit.2b2662070",
83
- "@aztec/protocol-contracts": "0.0.1-commit.2b2662070",
84
- "@aztec/simulator": "0.0.1-commit.2b2662070",
85
- "@aztec/stdlib": "0.0.1-commit.2b2662070",
73
+ "@aztec/bb-prover": "0.0.1-commit.2c0ee1788",
74
+ "@aztec/bb.js": "0.0.1-commit.2c0ee1788",
75
+ "@aztec/builder": "0.0.1-commit.2c0ee1788",
76
+ "@aztec/constants": "0.0.1-commit.2c0ee1788",
77
+ "@aztec/ethereum": "0.0.1-commit.2c0ee1788",
78
+ "@aztec/foundation": "0.0.1-commit.2c0ee1788",
79
+ "@aztec/key-store": "0.0.1-commit.2c0ee1788",
80
+ "@aztec/kv-store": "0.0.1-commit.2c0ee1788",
81
+ "@aztec/noir-protocol-circuits-types": "0.0.1-commit.2c0ee1788",
82
+ "@aztec/noir-types": "0.0.1-commit.2c0ee1788",
83
+ "@aztec/protocol-contracts": "0.0.1-commit.2c0ee1788",
84
+ "@aztec/simulator": "0.0.1-commit.2c0ee1788",
85
+ "@aztec/stdlib": "0.0.1-commit.2c0ee1788",
86
86
  "koa": "^2.16.1",
87
87
  "koa-router": "^13.1.1",
88
88
  "lodash.omit": "^4.5.0",
@@ -91,8 +91,8 @@
91
91
  "viem": "npm:@aztec/viem@2.38.2"
92
92
  },
93
93
  "devDependencies": {
94
- "@aztec/noir-test-contracts.js": "0.0.1-commit.2b2662070",
95
- "@aztec/world-state": "0.0.1-commit.2b2662070",
94
+ "@aztec/noir-test-contracts.js": "0.0.1-commit.2c0ee1788",
95
+ "@aztec/world-state": "0.0.1-commit.2c0ee1788",
96
96
  "@jest/globals": "^30.0.0",
97
97
  "@types/jest": "^30.0.0",
98
98
  "@types/lodash.omit": "^4.5.7",
@@ -0,0 +1,52 @@
1
+ import type { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
+ import { Fr } from '@aztec/foundation/curves/bn254';
3
+ import { L2Block, type L2BlockSource } from '@aztec/stdlib/block';
4
+ import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
5
+ import type { AztecNode } from '@aztec/stdlib/interfaces/client';
6
+
7
+ // TODO(spl/new-rpc-api): delete once `L2BlockStream` is refactored to consume the new
8
+ // `BlockResponse` / `CheckpointResponse` shapes. For now the stream requires concrete `L2Block`
9
+ // and `PublishedCheckpoint` instances, so we rehydrate them from RPC responses.
10
+ /**
11
+ * Lifts an {@link AztecNode} RPC client into the shape {@link L2BlockStream} expects. `getBlocks`
12
+ * requests transaction bodies so that real `L2Block` instances can be constructed;
13
+ * `getCheckpoints` requests blocks + L1 info + attestations so that `PublishedCheckpoint`
14
+ * instances are fully populated.
15
+ */
16
+ export function blockStreamSourceFromAztecNode(
17
+ node: AztecNode,
18
+ ): Pick<L2BlockSource, 'getBlocks' | 'getBlockHeader' | 'getL2Tips' | 'getCheckpoints' | 'getCheckpointedBlocks'> {
19
+ return {
20
+ getL2Tips: () => node.getL2Tips(),
21
+ getBlockHeader: number => node.getBlockHeader(number),
22
+ getCheckpointedBlocks: (from: BlockNumber, limit: number) => node.getCheckpointedBlocks(from, limit),
23
+
24
+ async getBlocks(from: BlockNumber, limit: number): Promise<L2Block[]> {
25
+ const responses = await node.getBlocks(from, limit, { includeTransactions: true });
26
+ return responses.map(r => new L2Block(r.archive, r.header, r.body!, r.checkpointNumber, r.indexWithinCheckpoint));
27
+ },
28
+
29
+ async getCheckpoints(from: CheckpointNumber, limit: number): Promise<PublishedCheckpoint[]> {
30
+ const responses = await node.getCheckpoints(from, limit, {
31
+ includeBlocks: true,
32
+ includeTransactions: true,
33
+ includeL1PublishInfo: true,
34
+ includeAttestations: true,
35
+ });
36
+ return responses.map(r => {
37
+ const checkpoint = new Checkpoint(
38
+ r.archive,
39
+ r.header,
40
+ r.blocks!.map(b => new L2Block(b.archive, b.header, b.body!, b.checkpointNumber, b.indexWithinCheckpoint)),
41
+ r.number,
42
+ r.feeAssetPriceModifier,
43
+ );
44
+ const l1 =
45
+ r.l1?.published === true
46
+ ? new L1PublishedData(r.l1.blockNumber, r.l1.timestamp, r.l1.blockHash)
47
+ : new L1PublishedData(0n, 0n, Fr.ZERO.toString());
48
+ return new PublishedCheckpoint(checkpoint, l1, r.attestations ?? []);
49
+ });
50
+ },
51
+ };
52
+ }
@@ -1,5 +1,6 @@
1
1
  import { BlockNumber } from '@aztec/foundation/branded-types';
2
2
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
3
+ import { SerialQueue } from '@aztec/foundation/queue';
3
4
  import type { AztecAsyncKVStore } from '@aztec/kv-store';
4
5
  import type { L2TipsKVStore } from '@aztec/kv-store/stores';
5
6
  import { BlockHash, L2BlockStream, type L2BlockStreamEvent, type L2BlockStreamEventHandler } from '@aztec/stdlib/block';
@@ -11,6 +12,7 @@ import type { ContractSyncService } from '../contract_sync/contract_sync_service
11
12
  import type { AnchorBlockStore } from '../storage/anchor_block_store/anchor_block_store.js';
12
13
  import type { NoteStore } from '../storage/note_store/note_store.js';
13
14
  import type { PrivateEventStore } from '../storage/private_event_store/private_event_store.js';
15
+ import { blockStreamSourceFromAztecNode } from './block_stream_source.js';
14
16
 
15
17
  /**
16
18
  * The BlockSynchronizer class orchestrates synchronization between PXE and Aztec node, maintaining an up-to-date
@@ -20,6 +22,7 @@ import type { PrivateEventStore } from '../storage/private_event_store/private_e
20
22
  export class BlockSynchronizer implements L2BlockStreamEventHandler {
21
23
  private log: Logger;
22
24
  private isSyncing: Promise<void> | undefined;
25
+ private readonly eventQueue = new SerialQueue();
23
26
  protected readonly blockStream: L2BlockStream;
24
27
 
25
28
  constructor(
@@ -35,11 +38,12 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
35
38
  ) {
36
39
  this.log = createLogger('pxe:block_synchronizer', bindings);
37
40
  this.blockStream = this.createBlockStream(config);
41
+ this.eventQueue.start();
38
42
  }
39
43
 
40
44
  protected createBlockStream(config: Partial<BlockSynchronizerConfig>): L2BlockStream {
41
45
  return new L2BlockStream(
42
- this.node,
46
+ blockStreamSourceFromAztecNode(this.node),
43
47
  this.l2TipsStore,
44
48
  this,
45
49
  createLogger('pxe:block_stream', this.log.getBindings()),
@@ -52,8 +56,12 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
52
56
  );
53
57
  }
54
58
 
55
- /** Handle events emitted by the block stream. */
56
- public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
59
+ /** Handle events emitted by the block stream. Serialized to prevent concurrent mutations to anchor state. */
60
+ public handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
61
+ return this.eventQueue.put(() => this.doHandleBlockStreamEvent(event));
62
+ }
63
+
64
+ private async doHandleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
57
65
  await this.l2TipsStore.handleBlockStreamEvent(event);
58
66
 
59
67
  switch (event.type) {
@@ -74,9 +82,9 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
74
82
  }
75
83
  case 'chain-proven': {
76
84
  if (this.config.syncChainTip === 'proven') {
77
- const blockHeader = await this.node.getBlockHeader(BlockNumber(event.block.number));
78
- if (blockHeader) {
79
- await this.updateAnchorBlockHeader(blockHeader);
85
+ const block = await this.node.getBlock(BlockNumber(event.block.number));
86
+ if (block) {
87
+ await this.updateAnchorBlockHeader(block.header);
80
88
  } else {
81
89
  this.log.warn(`Block header not found for proven block ${event.block.number}, skipping anchor update`);
82
90
  }
@@ -85,9 +93,9 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
85
93
  }
86
94
  case 'chain-finalized': {
87
95
  if (this.config.syncChainTip === 'finalized') {
88
- const blockHeader = await this.node.getBlockHeader(BlockNumber(event.block.number));
89
- if (blockHeader) {
90
- await this.updateAnchorBlockHeader(blockHeader);
96
+ const block = await this.node.getBlock(BlockNumber(event.block.number));
97
+ if (block) {
98
+ await this.updateAnchorBlockHeader(block.header);
91
99
  } else {
92
100
  this.log.warn(`Block header not found for finalized block ${event.block.number}, skipping anchor update`);
93
101
  }
@@ -110,7 +118,8 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
110
118
  // Note that the following is not necessarily the anchor block that will be used in the transaction - if
111
119
  // the chain has already moved past the reorg, we'll also see blocks-added events that will push the anchor
112
120
  // forward.
113
- const newAnchorBlockHeader = await this.node.getBlockHeader(BlockHash.fromString(event.block.hash));
121
+ const newAnchorBlock = await this.node.getBlock(BlockHash.fromString(event.block.hash));
122
+ const newAnchorBlockHeader = newAnchorBlock?.header;
114
123
 
115
124
  if (!newAnchorBlockHeader) {
116
125
  throw new Error(
@@ -167,6 +176,13 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
167
176
  }
168
177
  }
169
178
 
179
+ /** Stops the block synchronizer, waiting for any in-progress sync and queued events to complete. */
180
+ public async stop() {
181
+ await this.isSyncing;
182
+ await this.blockStream.stop();
183
+ await this.eventQueue.end();
184
+ }
185
+
170
186
  private async doSync() {
171
187
  let currentHeader;
172
188
 
@@ -177,7 +193,7 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
177
193
  }
178
194
  if (!currentHeader) {
179
195
  // REFACTOR: We should know the header of the genesis block without having to request it from the node.
180
- await this.anchorBlockStore.setHeader((await this.node.getBlockHeader(BlockNumber.ZERO))!);
196
+ await this.anchorBlockStore.setHeader((await this.node.getBlock(BlockNumber.ZERO))!.header);
181
197
  }
182
198
  await this.blockStream.sync();
183
199
  }
@@ -208,7 +208,7 @@ export class ContractFunctionSimulator {
208
208
  }
209
209
 
210
210
  if (request.origin !== contractAddress) {
211
- this.log.warn(
211
+ throw new Error(
212
212
  `Request origin does not match contract address in simulation. Request origin: ${request.origin}, contract address: ${contractAddress}`,
213
213
  );
214
214
  }
@@ -309,7 +309,6 @@ export class ContractFunctionSimulator {
309
309
  }
310
310
  }
311
311
 
312
- // docs:start:execute_utility_function
313
312
  /**
314
313
  * Runs a utility function.
315
314
  * @param call - The function call to execute.
@@ -351,6 +350,7 @@ export class ContractFunctionSimulator {
351
350
  l2TipsStore: this.l2TipsStore,
352
351
  jobId,
353
352
  scopes,
353
+ simulator: this.simulator,
354
354
  });
355
355
 
356
356
  try {
@@ -384,7 +384,6 @@ export class ContractFunctionSimulator {
384
384
  throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during private execution'));
385
385
  }
386
386
  }
387
- // docs:end:execute_utility_function
388
387
 
389
388
  /**
390
389
  * Returns the execution statistics collected during the simulator run.
@@ -164,6 +164,11 @@ export interface IUtilityExecutionOracle {
164
164
  getSharedSecret(address: AztecAddress, ephPk: Point, contractAddress: AztecAddress): Promise<Fr>;
165
165
  setContractSyncCacheInvalid(contractAddress: AztecAddress, scopes: AztecAddress[]): void;
166
166
  emitOffchainEffect(data: Fr[]): Promise<void>;
167
+ callUtilityFunction(
168
+ targetContractAddress: AztecAddress,
169
+ functionSelector: FunctionSelector,
170
+ args: Fr[],
171
+ ): Promise<Fr[]>;
167
172
 
168
173
  // Ephemeral array methods
169
174
  pushEphemeral(slot: Fr, elements: Fr[]): number;
@@ -204,6 +209,11 @@ export interface IPrivateExecutionOracle {
204
209
  sideEffectCounter: number,
205
210
  isStaticCall: boolean,
206
211
  ): Promise<{ endSideEffectCounter: Fr; returnsHash: Fr }>;
212
+ callUtilityFunction(
213
+ targetContractAddress: AztecAddress,
214
+ functionSelector: FunctionSelector,
215
+ args: Fr[],
216
+ ): Promise<Fr[]>;
207
217
  assertValidPublicCalldata(calldataHash: Fr): Promise<void>;
208
218
  notifyRevertiblePhaseStart(minRevertibleSideEffectCounter: number): Promise<void>;
209
219
  isExecutionInRevertiblePhase(sideEffectCounter: number): Promise<boolean>;
@@ -490,6 +490,20 @@ export class Oracle {
490
490
  return [values.map(toACVMField)];
491
491
  }
492
492
 
493
+ // eslint-disable-next-line camelcase
494
+ async aztec_utl_callUtilityFunction(
495
+ [contractAddress]: ACVMField[],
496
+ [functionSelector]: ACVMField[],
497
+ args: ACVMField[],
498
+ ): Promise<ACVMField[][]> {
499
+ const result = await this.handlerAsUtility().callUtilityFunction(
500
+ AztecAddress.fromField(Fr.fromString(contractAddress)),
501
+ FunctionSelector.fromField(Fr.fromString(functionSelector)),
502
+ args.map(Fr.fromString),
503
+ );
504
+ return [result.map(toACVMField)];
505
+ }
506
+
493
507
  // eslint-disable-next-line camelcase
494
508
  aztec_prv_notifyCreatedContractClassLog(
495
509
  [contractAddress]: ACVMField[],
@@ -2,7 +2,7 @@ import { MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS, PRIVATE_CONTEXT_INPUTS_LENGTH }
2
2
  import { Fr } from '@aztec/foundation/curves/bn254';
3
3
  import { createLogger } from '@aztec/foundation/log';
4
4
  import { Timer } from '@aztec/foundation/timer';
5
- import { type CircuitSimulator, toACVMWitness } from '@aztec/simulator/client';
5
+ import { toACVMWitness } from '@aztec/simulator/client';
6
6
  import {
7
7
  type FunctionAbi,
8
8
  type FunctionArtifact,
@@ -50,7 +50,6 @@ export type PrivateExecutionOracleArgs = Omit<UtilityExecutionOracleArgs, 'contr
50
50
  totalPublicCalldataCount?: number;
51
51
  sideEffectCounter?: number;
52
52
  senderForTags?: AztecAddress;
53
- simulator?: CircuitSimulator;
54
53
  };
55
54
 
56
55
  /**
@@ -81,9 +80,11 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
81
80
  private readonly taggingIndexCache: ExecutionTaggingIndexCache;
82
81
  private readonly senderTaggingStore: SenderTaggingStore;
83
82
  private totalPublicCalldataCount: number;
84
- protected sideEffectCounter: number;
85
- private senderForTags?: AztecAddress;
86
- private readonly simulator?: CircuitSimulator;
83
+ private readonly initialSideEffectCounter: number;
84
+ /** Sender for tags passed in at oracle construction time. Returned by `getSenderForTags` unless overridden. */
85
+ private readonly defaultSenderForTags: AztecAddress | undefined;
86
+ /** Per-call sender-for-tags override, set by `setSenderForTags`. Takes precedence over `defaultSenderForTags`. */
87
+ private currentSenderForTags: AztecAddress | undefined;
87
88
 
88
89
  constructor(args: PrivateExecutionOracleArgs) {
89
90
  super({
@@ -100,13 +101,17 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
100
101
  this.taggingIndexCache = args.taggingIndexCache;
101
102
  this.senderTaggingStore = args.senderTaggingStore;
102
103
  this.totalPublicCalldataCount = args.totalPublicCalldataCount ?? 0;
103
- this.sideEffectCounter = args.sideEffectCounter ?? 0;
104
- this.senderForTags = args.senderForTags;
105
- this.simulator = args.simulator;
104
+ this.initialSideEffectCounter = args.sideEffectCounter ?? 0;
105
+ this.defaultSenderForTags = args.senderForTags;
106
106
  }
107
107
 
108
108
  public getPrivateContextInputs(): PrivateContextInputs {
109
- return new PrivateContextInputs(this.callContext, this.anchorBlockHeader, this.txContext, this.sideEffectCounter);
109
+ return new PrivateContextInputs(
110
+ this.callContext,
111
+ this.anchorBlockHeader,
112
+ this.txContext,
113
+ this.initialSideEffectCounter,
114
+ );
110
115
  }
111
116
 
112
117
  // We still need this function until we can get user-defined ordering of structs for fn arguments
@@ -173,11 +178,10 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
173
178
  * for a tag in order to emit a log. Constrained tagging should not use this as there is no
174
179
  * guarantee that the recipient knows about the sender, and hence about the shared secret.
175
180
  *
176
- * The value persists through nested calls, meaning all calls down the stack will use the same
177
- * 'senderForTags' value (unless it is replaced).
181
+ * Returns `currentSenderForTags` if set (via `setSenderForTags`), otherwise `defaultSenderForTags`.
178
182
  */
179
183
  public getSenderForTags(): Promise<AztecAddress | undefined> {
180
- return Promise.resolve(this.senderForTags);
184
+ return Promise.resolve(this.currentSenderForTags ?? this.defaultSenderForTags);
181
185
  }
182
186
 
183
187
  /**
@@ -187,12 +191,14 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
187
191
  * for a tag in order to emit a log. Constrained tagging should not use this as there is no
188
192
  * guarantee that the recipient knows about the sender, and hence about the shared secret.
189
193
  *
190
- * Account contracts typically set this value before calling other contracts. The value persists
191
- * through nested calls, meaning all calls down the stack will use the same 'senderForTags'
192
- * value (unless it is replaced by another call to this setter).
194
+ * Overrides `defaultSenderForTags` for the remainder of this call. Each oracle instance is
195
+ * independent, so this has no effect on any other call in the execution.
193
196
  */
194
197
  public setSenderForTags(senderForTags: AztecAddress): Promise<void> {
195
- this.senderForTags = senderForTags;
198
+ this.logger.debug(
199
+ `Sender for tags switched to ${senderForTags} by contract ${this.contractAddress} (default was ${this.defaultSenderForTags})`,
200
+ );
201
+ this.currentSenderForTags = senderForTags;
196
202
  return Promise.resolve();
197
203
  }
198
204
 
@@ -573,21 +579,24 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
573
579
  sideEffectCounter,
574
580
  log: this.logger,
575
581
  scopes: this.scopes,
576
- senderForTags: this.senderForTags,
577
- simulator: this.simulator!,
582
+ senderForTags: this.defaultSenderForTags,
583
+ simulator: this.simulator,
578
584
  l2TipsStore: this.l2TipsStore,
579
585
  });
580
586
 
581
587
  const setupTime = simulatorSetupTimer.ms();
582
588
 
583
589
  const childExecutionResult = await executePrivateFunction(
584
- this.simulator!,
590
+ this.simulator,
585
591
  privateExecutionOracle,
586
592
  targetArtifact,
587
593
  targetContractAddress,
588
594
  functionSelector,
589
595
  );
590
596
 
597
+ // Propagate the nested call's calldata count so the parent sees its increments on subsequent enqueues.
598
+ this.totalPublicCalldataCount = privateExecutionOracle.getTotalPublicCalldataCount();
599
+
591
600
  if (isStaticCall) {
592
601
  this.#checkValidStaticCall(childExecutionResult);
593
602
  }
@@ -621,6 +630,10 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
621
630
  return Promise.resolve();
622
631
  }
623
632
 
633
+ public getTotalPublicCalldataCount(): number {
634
+ return this.totalPublicCalldataCount;
635
+ }
636
+
624
637
  public notifyRevertiblePhaseStart(minRevertibleSideEffectCounter: number): Promise<void> {
625
638
  return this.noteCache.setMinRevertibleSideEffectCounter(minRevertibleSideEffectCounter);
626
639
  }
@@ -7,6 +7,15 @@ import { LogLevels, type Logger, createLogger } from '@aztec/foundation/log';
7
7
  import type { MembershipWitness } from '@aztec/foundation/trees';
8
8
  import type { KeyStore } from '@aztec/key-store';
9
9
  import { isProtocolContract } from '@aztec/protocol-contracts';
10
+ import {
11
+ type CircuitSimulator,
12
+ ExecutionError,
13
+ extractCallStack,
14
+ resolveAssertionMessageFromError,
15
+ toACVMWitness,
16
+ witnessMapToFields,
17
+ } from '@aztec/simulator/client';
18
+ import { FunctionSelector } from '@aztec/stdlib/abi';
10
19
  import type { AuthWitness } from '@aztec/stdlib/auth-witness';
11
20
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
12
21
  import { BlockHash, type L2TipsProvider } from '@aztec/stdlib/block';
@@ -44,6 +53,7 @@ import { UtilityContext } from '../noir-structs/utility_context.js';
44
53
  import { pickNotes } from '../pick_notes.js';
45
54
  import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js';
46
55
  import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js';
56
+ import { Oracle } from './oracle.js';
47
57
 
48
58
  /** Args for UtilityExecutionOracle constructor. */
49
59
  export type UtilityExecutionOracleArgs = {
@@ -67,6 +77,7 @@ export type UtilityExecutionOracleArgs = {
67
77
  jobId: string;
68
78
  log?: ReturnType<typeof createLogger>;
69
79
  scopes: AztecAddress[];
80
+ simulator: CircuitSimulator;
70
81
  };
71
82
 
72
83
  /**
@@ -103,6 +114,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
103
114
  protected readonly jobId: string;
104
115
  protected logger: ReturnType<typeof createLogger>;
105
116
  protected readonly scopes: AztecAddress[];
117
+ protected readonly simulator: CircuitSimulator;
106
118
 
107
119
  constructor(args: UtilityExecutionOracleArgs) {
108
120
  this.contractAddress = args.contractAddress;
@@ -124,6 +136,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
124
136
  this.jobId = args.jobId;
125
137
  this.logger = args.log ?? createLogger('simulator:client_view_context');
126
138
  this.scopes = args.scopes;
139
+ this.simulator = args.simulator;
127
140
  }
128
141
 
129
142
  public assertCompatibleOracleVersion(major: number, minor: number): void {
@@ -333,10 +346,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
333
346
  }
334
347
 
335
348
  /**
336
- * Returns an auth witness for the given message hash. Checks on the list of transient witnesses
337
- * for this transaction first, and falls back to the local database if not found.
349
+ * Returns an auth witness for the given message hash from the list of transient witnesses for this transaction.
338
350
  * @param messageHash - Hash of the message to authenticate.
339
- * @returns Authentication witness for the requested message hash.
351
+ * @returns Authentication witness for the requested message hash, or undefined if not found.
340
352
  */
341
353
  public getAuthWitness(messageHash: Fr): Promise<Fr[] | undefined> {
342
354
  return Promise.resolve(this.authWitnesses.find(w => w.requestHash.equals(messageHash))?.witness);
@@ -895,6 +907,70 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
895
907
  return Promise.resolve();
896
908
  }
897
909
 
910
+ /** Executes another utility function from within this one and returns its serialized return values. */
911
+ public async callUtilityFunction(
912
+ targetContractAddress: AztecAddress,
913
+ functionSelector: FunctionSelector,
914
+ args: Fr[],
915
+ ): Promise<Fr[]> {
916
+ // TODO(F-29): We want to support cross-contract utility calls, but doing so safely requires wallets to have
917
+ // a way to authorize which contracts can be called transitively, since those calls may expose private state.
918
+ // Until that is in place, restrict nested utility calls to the same contract only.
919
+ if (!targetContractAddress.equals(this.contractAddress)) {
920
+ throw new Error(
921
+ `Cross-contract utility calls are not yet supported: cannot call ${targetContractAddress} from utility function on ${this.contractAddress}.`,
922
+ );
923
+ }
924
+
925
+ this.logger.debug(
926
+ `Calling nested utility function ${targetContractAddress}:${functionSelector} from ${this.contractAddress}`,
927
+ );
928
+
929
+ const targetArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(
930
+ targetContractAddress,
931
+ functionSelector,
932
+ );
933
+
934
+ const nestedOracle = new UtilityExecutionOracle({
935
+ contractAddress: targetContractAddress,
936
+ authWitnesses: this.authWitnesses,
937
+ capsules: this.capsules,
938
+ anchorBlockHeader: this.anchorBlockHeader,
939
+ contractStore: this.contractStore,
940
+ noteStore: this.noteStore,
941
+ keyStore: this.keyStore,
942
+ addressStore: this.addressStore,
943
+ aztecNode: this.aztecNode,
944
+ recipientTaggingStore: this.recipientTaggingStore,
945
+ senderAddressBookStore: this.senderAddressBookStore,
946
+ capsuleService: this.capsuleService,
947
+ privateEventStore: this.privateEventStore,
948
+ messageContextService: this.messageContextService,
949
+ contractSyncService: this.contractSyncService,
950
+ l2TipsStore: this.l2TipsStore,
951
+ jobId: this.jobId,
952
+ scopes: this.scopes,
953
+ simulator: this.simulator,
954
+ log: this.logger,
955
+ });
956
+
957
+ const initialWitness = toACVMWitness(0, args);
958
+ const acvmCallback = new Oracle(nestedOracle);
959
+ const acirExecutionResult = await this.simulator
960
+ .executeUserCircuit(initialWitness, targetArtifact, acvmCallback.toACIRCallback())
961
+ .catch((err: Error) => {
962
+ err.message = resolveAssertionMessageFromError(err, targetArtifact);
963
+ throw new ExecutionError(
964
+ err.message,
965
+ { contractAddress: targetContractAddress, functionSelector },
966
+ extractCallStack(err, targetArtifact.debug),
967
+ { cause: err },
968
+ );
969
+ });
970
+
971
+ return witnessMapToFields(acirExecutionResult.returnWitness);
972
+ }
973
+
898
974
  /** Returns offchain effects collected during execution. */
899
975
  public getOffchainEffects(): OffchainEffect[] {
900
976
  return this.offchainEffects;
@@ -905,7 +981,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
905
981
  const [response] = await Promise.all([
906
982
  query(),
907
983
  (async () => {
908
- const header = await this.aztecNode.getBlockHeader(blockHash);
984
+ const block = await this.aztecNode.getBlock(blockHash);
985
+ const header = block?.header;
909
986
  if (!header) {
910
987
  throw new Error(`Could not find block header for block hash ${blockHash}`);
911
988
  }
@@ -85,6 +85,14 @@ interface ContainsNote {
85
85
  }
86
86
 
87
87
  const selectPropertyFromPackedNoteContent = (noteData: Fr[], selector: PropertySelector): Fr => {
88
+ if (selector.index >= noteData.length) {
89
+ throw new Error(`Property selector index ${selector.index} out of bounds for note with ${noteData.length} fields`);
90
+ }
91
+ if (selector.offset + selector.length > Fr.SIZE_IN_BYTES) {
92
+ throw new Error(
93
+ `Property selector range (offset=${selector.offset}, length=${selector.length}) exceeds Fr buffer size of ${Fr.SIZE_IN_BYTES} bytes`,
94
+ );
95
+ }
88
96
  const noteValueBuffer = noteData[selector.index].toBuffer();
89
97
  // Noir's PropertySelector counts offset from the LSB (last byte of the big-endian buffer),
90
98
  // so offset=0,length=Fr.SIZE_IN_BYTES reads the entire field, and offset=0,length=1 reads the last byte.
@@ -110,7 +118,11 @@ const selectNotes = <T extends ContainsNote>(noteDatas: T[], selects: Select[]):
110
118
  [Comparator.GTE]: () => !noteValueFr.lt(value),
111
119
  };
112
120
 
113
- return comparatorSelector[comparator]();
121
+ const fn = comparatorSelector[comparator];
122
+ if (!fn) {
123
+ throw new Error(`Invalid comparator value: ${comparator}`);
124
+ }
125
+ return fn();
114
126
  }),
115
127
  );
116
128
 
@@ -2,7 +2,7 @@ import type { Fr } from '@aztec/foundation/curves/bn254';
2
2
  import { createLogger } from '@aztec/foundation/log';
3
3
  import type { EventSelector } from '@aztec/stdlib/abi';
4
4
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
- import { siloNullifier } from '@aztec/stdlib/hash';
5
+ import { computePrivateEventCommitment, siloNullifier } from '@aztec/stdlib/hash';
6
6
  import type { AztecNode } from '@aztec/stdlib/interfaces/server';
7
7
  import type { BlockHeader, TxHash } from '@aztec/stdlib/tx';
8
8
 
@@ -26,6 +26,18 @@ export class EventService {
26
26
  txHash: TxHash,
27
27
  scope: AztecAddress,
28
28
  ): Promise<void> {
29
+ // Defense-in-depth: the built-in private-event path derives this commitment from content before enqueueing, but
30
+ // unconstrained PXE-side code (e.g. a custom message handler) can reach this oracle with arbitrary
31
+ // (content, commitment) pairs. Without this check it could bind arbitrary content to a legitimate tx nullifier,
32
+ // causing PXE to surface fabricated event data.
33
+ const recomputedCommitment = await computePrivateEventCommitment(randomness, selector.toField(), content);
34
+ if (!recomputedCommitment.equals(eventCommitment)) {
35
+ this.log.warn(
36
+ `Skipping event whose content does not hash to the provided commitment. contract=${contractAddress}, selector=${selector}, eventCommitment=${eventCommitment}, txHash=${txHash}, recomputedCommitment=${recomputedCommitment}`,
37
+ );
38
+ return;
39
+ }
40
+
29
41
  // While using 'latest' block number would be fine for private events since they cannot be accessed from Aztec.nr
30
42
  // (and thus we're less concerned about being ahead of the synced block), we use the synced block number to
31
43
  // maintain consistent behavior in the PXE. Additionally, events should never be ahead of the synced block here
@@ -1,11 +1,14 @@
1
1
  import type { PrivateEventFilter } from '@aztec/aztec.js/wallet';
2
2
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
3
3
  import { BlockNumber } from '@aztec/foundation/branded-types';
4
+ import { createLogger } from '@aztec/foundation/log';
4
5
 
5
6
  import type { PrivateEventStoreFilter } from '../storage/private_event_store/private_event_store.js';
6
7
 
7
8
  export class PrivateEventFilterValidator {
8
- constructor(private lastBlock: BlockNumber) {}
9
+ private readonly log = createLogger('pxe:private_event_filter_validator');
10
+
11
+ constructor(private readonly lastBlock: BlockNumber) {}
9
12
 
10
13
  validate(filter: PrivateEventFilter): PrivateEventStoreFilter {
11
14
  let { fromBlock, toBlock } = filter;
@@ -35,6 +38,23 @@ export class PrivateEventFilterValidator {
35
38
  throw new Error('toBlock must be strictly greater than fromBlock');
36
39
  }
37
40
 
41
+ // Cap the requested range to the synced block range. Without this, callers that pass a large
42
+ // toBlock (e.g. Number.MAX_SAFE_INTEGER as a "give me everything" idiom) would silently receive
43
+ // only the events that happen to be synced and believe they have complete coverage.
44
+ // We warn + cap rather than throw so callers don't need to query the last synced block before
45
+ // every request (which would also be unreliable, as the block can advance between the two calls).
46
+ const syncedUpperBound = BlockNumber(this.lastBlock + 1);
47
+ if (fromBlock >= syncedUpperBound) {
48
+ this.log.warn(
49
+ `Requested fromBlock ${fromBlock} is past last synced block ${this.lastBlock}; no events will be returned until PXE syncs further.`,
50
+ );
51
+ } else if (toBlock > syncedUpperBound) {
52
+ this.log.warn(
53
+ `Requested toBlock ${toBlock} exceeds last synced block ${this.lastBlock}; capping to ${syncedUpperBound}. Retry once PXE is further synced for complete coverage.`,
54
+ );
55
+ toBlock = syncedUpperBound;
56
+ }
57
+
38
58
  return {
39
59
  contractAddress: filter.contractAddress,
40
60
  scopes: filter.scopes,