@aztec/pxe 0.0.1-commit.f650c0a5c → 0.0.1-commit.f7ea82942

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 (77) hide show
  1. package/dest/block_synchronizer/block_synchronizer.d.ts +6 -2
  2. package/dest/block_synchronizer/block_synchronizer.d.ts.map +1 -1
  3. package/dest/block_synchronizer/block_synchronizer.js +13 -1
  4. package/dest/config/index.d.ts +1 -1
  5. package/dest/config/index.d.ts.map +1 -1
  6. package/dest/config/index.js +7 -14
  7. package/dest/contract_function_simulator/contract_function_simulator.d.ts +4 -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 +6 -4
  10. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +3 -2
  11. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  12. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +11 -5
  13. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +6 -5
  14. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  15. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +5 -4
  16. package/dest/contract_function_simulator/pick_notes.d.ts +1 -1
  17. package/dest/contract_function_simulator/pick_notes.d.ts.map +1 -1
  18. package/dest/contract_function_simulator/pick_notes.js +11 -1
  19. package/dest/contract_function_simulator/proxied_contract_data_source.d.ts +1 -1
  20. package/dest/contract_function_simulator/proxied_contract_data_source.d.ts.map +1 -1
  21. package/dest/contract_function_simulator/proxied_contract_data_source.js +3 -0
  22. package/dest/contract_sync/contract_sync_service.d.ts +1 -1
  23. package/dest/contract_sync/contract_sync_service.d.ts.map +1 -1
  24. package/dest/contract_sync/contract_sync_service.js +35 -23
  25. package/dest/events/event_service.d.ts +1 -1
  26. package/dest/events/event_service.d.ts.map +1 -1
  27. package/dest/events/event_service.js +10 -1
  28. package/dest/events/private_event_filter_validator.d.ts +3 -2
  29. package/dest/events/private_event_filter_validator.d.ts.map +1 -1
  30. package/dest/events/private_event_filter_validator.js +15 -0
  31. package/dest/logs/log_service.d.ts +4 -2
  32. package/dest/logs/log_service.d.ts.map +1 -1
  33. package/dest/logs/log_service.js +6 -3
  34. package/dest/private_kernel/private_kernel_execution_prover.d.ts +1 -1
  35. package/dest/private_kernel/private_kernel_execution_prover.d.ts.map +1 -1
  36. package/dest/private_kernel/private_kernel_execution_prover.js +4 -7
  37. package/dest/private_kernel/private_kernel_oracle.d.ts +5 -5
  38. package/dest/private_kernel/private_kernel_oracle.d.ts.map +1 -1
  39. package/dest/private_kernel/private_kernel_oracle.js +12 -15
  40. package/dest/pxe.d.ts +2 -1
  41. package/dest/pxe.d.ts.map +1 -1
  42. package/dest/pxe.js +23 -16
  43. package/dest/storage/anchor_block_store/anchor_block_store.js +1 -1
  44. package/dest/storage/capsule_store/capsule_store.d.ts +1 -1
  45. package/dest/storage/capsule_store/capsule_store.d.ts.map +1 -1
  46. package/dest/storage/capsule_store/capsule_store.js +8 -5
  47. package/dest/storage/contract_store/contract_store.d.ts +1 -1
  48. package/dest/storage/contract_store/contract_store.d.ts.map +1 -1
  49. package/dest/storage/contract_store/contract_store.js +4 -2
  50. package/dest/storage/private_event_store/private_event_store.d.ts +1 -1
  51. package/dest/storage/private_event_store/private_event_store.d.ts.map +1 -1
  52. package/dest/storage/private_event_store/private_event_store.js +3 -0
  53. package/dest/storage/private_event_store/stored_private_event.js +1 -1
  54. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts +2 -2
  55. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts.map +1 -1
  56. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.js +2 -16
  57. package/package.json +16 -16
  58. package/src/block_synchronizer/block_synchronizer.ts +16 -2
  59. package/src/config/index.ts +2 -8
  60. package/src/contract_function_simulator/contract_function_simulator.ts +7 -4
  61. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +17 -4
  62. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +7 -4
  63. package/src/contract_function_simulator/pick_notes.ts +13 -1
  64. package/src/contract_function_simulator/proxied_contract_data_source.ts +8 -1
  65. package/src/contract_sync/contract_sync_service.ts +57 -51
  66. package/src/events/event_service.ts +13 -1
  67. package/src/events/private_event_filter_validator.ts +21 -1
  68. package/src/logs/log_service.ts +6 -1
  69. package/src/private_kernel/private_kernel_execution_prover.ts +4 -9
  70. package/src/private_kernel/private_kernel_oracle.ts +14 -14
  71. package/src/pxe.ts +53 -16
  72. package/src/storage/anchor_block_store/anchor_block_store.ts +1 -1
  73. package/src/storage/capsule_store/capsule_store.ts +15 -5
  74. package/src/storage/contract_store/contract_store.ts +8 -6
  75. package/src/storage/private_event_store/private_event_store.ts +4 -0
  76. package/src/storage/private_event_store/stored_private_event.ts +1 -1
  77. package/src/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.ts +5 -15
@@ -42,7 +42,7 @@ import type { FunctionCall } from '@aztec/stdlib/abi';
42
42
  import { FunctionSelector, FunctionType } from '@aztec/stdlib/abi';
43
43
  import type { AuthWitness } from '@aztec/stdlib/auth-witness';
44
44
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
45
- import type { BlockParameter } from '@aztec/stdlib/block';
45
+ import type { BlockParameter, L2TipsProvider } from '@aztec/stdlib/block';
46
46
  import { Gas } from '@aztec/stdlib/gas';
47
47
  import {
48
48
  computeNoteHashNonce,
@@ -134,6 +134,7 @@ export type ContractFunctionSimulatorArgs = {
134
134
  keyStore: KeyStore;
135
135
  addressStore: AddressStore;
136
136
  aztecNode: AztecNode;
137
+ l2TipsStore: L2TipsProvider;
137
138
  senderTaggingStore: SenderTaggingStore;
138
139
  recipientTaggingStore: RecipientTaggingStore;
139
140
  senderAddressBookStore: SenderAddressBookStore;
@@ -154,6 +155,7 @@ export class ContractFunctionSimulator {
154
155
  private readonly keyStore: KeyStore;
155
156
  private readonly addressStore: AddressStore;
156
157
  private readonly aztecNode: AztecNode;
158
+ private readonly l2TipsStore: L2TipsProvider;
157
159
  private readonly senderTaggingStore: SenderTaggingStore;
158
160
  private readonly recipientTaggingStore: RecipientTaggingStore;
159
161
  private readonly senderAddressBookStore: SenderAddressBookStore;
@@ -169,6 +171,7 @@ export class ContractFunctionSimulator {
169
171
  this.keyStore = args.keyStore;
170
172
  this.addressStore = args.addressStore;
171
173
  this.aztecNode = args.aztecNode;
174
+ this.l2TipsStore = args.l2TipsStore;
172
175
  this.senderTaggingStore = args.senderTaggingStore;
173
176
  this.recipientTaggingStore = args.recipientTaggingStore;
174
177
  this.senderAddressBookStore = args.senderAddressBookStore;
@@ -205,7 +208,7 @@ export class ContractFunctionSimulator {
205
208
  }
206
209
 
207
210
  if (request.origin !== contractAddress) {
208
- this.log.warn(
211
+ throw new Error(
209
212
  `Request origin does not match contract address in simulation. Request origin: ${request.origin}, contract address: ${contractAddress}`,
210
213
  );
211
214
  }
@@ -255,6 +258,7 @@ export class ContractFunctionSimulator {
255
258
  scopes,
256
259
  senderForTags,
257
260
  simulator: this.simulator,
261
+ l2TipsStore: this.l2TipsStore,
258
262
  });
259
263
 
260
264
  const setupTime = simulatorSetupTimer.ms();
@@ -305,7 +309,6 @@ export class ContractFunctionSimulator {
305
309
  }
306
310
  }
307
311
 
308
- // docs:start:execute_utility_function
309
312
  /**
310
313
  * Runs a utility function.
311
314
  * @param call - The function call to execute.
@@ -344,6 +347,7 @@ export class ContractFunctionSimulator {
344
347
  privateEventStore: this.privateEventStore,
345
348
  messageContextService: this.messageContextService,
346
349
  contractSyncService: this.contractSyncService,
350
+ l2TipsStore: this.l2TipsStore,
347
351
  jobId,
348
352
  scopes,
349
353
  });
@@ -379,7 +383,6 @@ export class ContractFunctionSimulator {
379
383
  throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during private execution'));
380
384
  }
381
385
  }
382
- // docs:end:execute_utility_function
383
386
 
384
387
  /**
385
388
  * Returns the execution statistics collected during the simulator run.
@@ -81,7 +81,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
81
81
  private readonly taggingIndexCache: ExecutionTaggingIndexCache;
82
82
  private readonly senderTaggingStore: SenderTaggingStore;
83
83
  private totalPublicCalldataCount: number;
84
- protected sideEffectCounter: number;
84
+ private readonly initialSideEffectCounter: number;
85
85
  private senderForTags?: AztecAddress;
86
86
  private readonly simulator?: CircuitSimulator;
87
87
 
@@ -100,13 +100,18 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
100
100
  this.taggingIndexCache = args.taggingIndexCache;
101
101
  this.senderTaggingStore = args.senderTaggingStore;
102
102
  this.totalPublicCalldataCount = args.totalPublicCalldataCount ?? 0;
103
- this.sideEffectCounter = args.sideEffectCounter ?? 0;
103
+ this.initialSideEffectCounter = args.sideEffectCounter ?? 0;
104
104
  this.senderForTags = args.senderForTags;
105
105
  this.simulator = args.simulator;
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
@@ -216,7 +221,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
216
221
  this.logger.warn(`Computing a tag for invalid recipient ${recipient} - returning a random tag instead`, {
217
222
  contractAddress: this.contractAddress,
218
223
  });
219
- return new Tag(Fr.random());
224
+ return Tag.random();
220
225
  }
221
226
 
222
227
  const index = await this.#getIndexToUseForSecret(extendedSecret);
@@ -575,6 +580,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
575
580
  scopes: this.scopes,
576
581
  senderForTags: this.senderForTags,
577
582
  simulator: this.simulator!,
583
+ l2TipsStore: this.l2TipsStore,
578
584
  });
579
585
 
580
586
  const setupTime = simulatorSetupTimer.ms();
@@ -587,6 +593,9 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
587
593
  functionSelector,
588
594
  );
589
595
 
596
+ // Propagate the nested call's calldata count so the parent sees its increments on subsequent enqueues.
597
+ this.totalPublicCalldataCount = privateExecutionOracle.getTotalPublicCalldataCount();
598
+
590
599
  if (isStaticCall) {
591
600
  this.#checkValidStaticCall(childExecutionResult);
592
601
  }
@@ -620,6 +629,10 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
620
629
  return Promise.resolve();
621
630
  }
622
631
 
632
+ public getTotalPublicCalldataCount(): number {
633
+ return this.totalPublicCalldataCount;
634
+ }
635
+
623
636
  public notifyRevertiblePhaseStart(minRevertibleSideEffectCounter: number): Promise<void> {
624
637
  return this.noteCache.setMinRevertibleSideEffectCounter(minRevertibleSideEffectCounter);
625
638
  }
@@ -9,7 +9,7 @@ import type { KeyStore } from '@aztec/key-store';
9
9
  import { isProtocolContract } from '@aztec/protocol-contracts';
10
10
  import type { AuthWitness } from '@aztec/stdlib/auth-witness';
11
11
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
12
- import { BlockHash } from '@aztec/stdlib/block';
12
+ import { BlockHash, type L2TipsProvider } from '@aztec/stdlib/block';
13
13
  import type { CompleteAddress, ContractInstance, PartialAddress } from '@aztec/stdlib/contract';
14
14
  import { siloNullifier } from '@aztec/stdlib/hash';
15
15
  import type { AztecNode } from '@aztec/stdlib/interfaces/server';
@@ -63,6 +63,7 @@ export type UtilityExecutionOracleArgs = {
63
63
  privateEventStore: PrivateEventStore;
64
64
  messageContextService: MessageContextService;
65
65
  contractSyncService: ContractSyncService;
66
+ l2TipsStore: L2TipsProvider;
66
67
  jobId: string;
67
68
  log?: ReturnType<typeof createLogger>;
68
69
  scopes: AztecAddress[];
@@ -98,6 +99,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
98
99
  protected readonly privateEventStore: PrivateEventStore;
99
100
  protected readonly messageContextService: MessageContextService;
100
101
  protected readonly contractSyncService: ContractSyncService;
102
+ protected readonly l2TipsStore: L2TipsProvider;
101
103
  protected readonly jobId: string;
102
104
  protected logger: ReturnType<typeof createLogger>;
103
105
  protected readonly scopes: AztecAddress[];
@@ -118,6 +120,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
118
120
  this.privateEventStore = args.privateEventStore;
119
121
  this.messageContextService = args.messageContextService;
120
122
  this.contractSyncService = args.contractSyncService;
123
+ this.l2TipsStore = args.l2TipsStore;
121
124
  this.jobId = args.jobId;
122
125
  this.logger = args.log ?? createLogger('simulator:client_view_context');
123
126
  this.scopes = args.scopes;
@@ -330,10 +333,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
330
333
  }
331
334
 
332
335
  /**
333
- * Returns an auth witness for the given message hash. Checks on the list of transient witnesses
334
- * for this transaction first, and falls back to the local database if not found.
336
+ * Returns an auth witness for the given message hash from the list of transient witnesses for this transaction.
335
337
  * @param messageHash - Hash of the message to authenticate.
336
- * @returns Authentication witness for the requested message hash.
338
+ * @returns Authentication witness for the requested message hash, or undefined if not found.
337
339
  */
338
340
  public getAuthWitness(messageHash: Fr): Promise<Fr[] | undefined> {
339
341
  return Promise.resolve(this.authWitnesses.find(w => w.requestHash.equals(messageHash))?.witness);
@@ -531,6 +533,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
531
533
  return new LogService(
532
534
  this.aztecNode,
533
535
  this.anchorBlockHeader,
536
+ this.l2TipsStore,
534
537
  this.keyStore,
535
538
  this.recipientTaggingStore,
536
539
  this.senderAddressBookStore,
@@ -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
 
@@ -29,6 +29,7 @@ export class ProxiedContractStoreFactory {
29
29
  }
30
30
  instance.currentContractClassId = realInstance.currentContractClassId;
31
31
  instance.originalContractClassId = realInstance.originalContractClassId;
32
+ instance.initializationHash = realInstance.initializationHash;
32
33
  return instance;
33
34
  } else {
34
35
  return target.getContractInstance(address);
@@ -47,6 +48,9 @@ export class ProxiedContractStoreFactory {
47
48
  return fn;
48
49
  }
49
50
  }
51
+ throw new Error(
52
+ `Function with selector ${selector} not found in stub artifact for overridden contract at ${contractAddress}. The stub does not implement this function.`,
53
+ );
50
54
  } else {
51
55
  return target.getFunctionArtifact(contractAddress, selector);
52
56
  }
@@ -64,6 +68,9 @@ export class ProxiedContractStoreFactory {
64
68
  return fn;
65
69
  }
66
70
  }
71
+ throw new Error(
72
+ `Function with selector ${selector} not found in stub artifact for overridden contract at ${contractAddress}. The stub does not implement this function.`,
73
+ );
67
74
  } else {
68
75
  return target.getFunctionArtifactWithDebugMetadata(contractAddress, selector);
69
76
  }
@@ -78,6 +85,6 @@ export class ProxiedContractStoreFactory {
78
85
  }
79
86
  }
80
87
  },
81
- });
88
+ }) satisfies ContractStore;
82
89
  }
83
90
  }
@@ -1,4 +1,5 @@
1
1
  import type { Logger } from '@aztec/foundation/log';
2
+ import { Semaphore } from '@aztec/foundation/queue';
2
3
  import type { FunctionCall, FunctionSelector } from '@aztec/stdlib/abi';
3
4
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
5
  import type { AztecNode } from '@aztec/stdlib/interfaces/client';
@@ -9,6 +10,9 @@ import type { ContractStore } from '../storage/contract_store/contract_store.js'
9
10
  import type { NoteStore } from '../storage/note_store/note_store.js';
10
11
  import { syncState, verifyCurrentClassId } from './helpers.js';
11
12
 
13
+ /** Maximum number of scope syncs running concurrently across the PXE. */
14
+ const MAX_CONCURRENT_SCOPE_SYNCS = 5;
15
+
12
16
  /**
13
17
  * Service for syncing the private state of contracts and verifying that the PXE holds the current class artifact.
14
18
  * It uses a cache to avoid redundant sync operations - the cache is wiped when the anchor block changes.
@@ -26,6 +30,11 @@ export class ContractSyncService implements StagedStore {
26
30
  // Per-job excluded contract addresses - these contracts should not be synced.
27
31
  private excludedFromSync: Map<string, Set<string>> = new Map();
28
32
 
33
+ // Bounds the number of scope syncs running concurrently. Scopes beyond this limit queue here. Sized to trade off
34
+ // parallelism on non-ACIR work (node RPC, note store reads) against memory pressure from concurrent circuit
35
+ // execution.
36
+ #syncSlot = new Semaphore(MAX_CONCURRENT_SCOPE_SYNCS);
37
+
29
38
  constructor(
30
39
  private aztecNode: AztecNode,
31
40
  private contractStore: ContractStore,
@@ -59,15 +68,22 @@ export class ContractSyncService implements StagedStore {
59
68
  return;
60
69
  }
61
70
 
62
- this.#startSyncIfNeeded(contractAddress, scopes, scopesToSync =>
63
- this.#syncContract(
64
- contractAddress,
65
- functionToInvokeAfterSync,
66
- utilityExecutor,
67
- anchorBlockHeader,
68
- jobId,
69
- scopesToSync,
70
- ),
71
+ this.#startSyncIfNeeded(
72
+ contractAddress,
73
+ scopes,
74
+ () => verifyCurrentClassId(contractAddress, this.aztecNode, this.contractStore, anchorBlockHeader),
75
+ scope =>
76
+ syncState(
77
+ contractAddress,
78
+ this.contractStore,
79
+ functionToInvokeAfterSync,
80
+ utilityExecutor,
81
+ this.noteStore,
82
+ this.aztecNode,
83
+ anchorBlockHeader,
84
+ jobId,
85
+ scope,
86
+ ),
71
87
  );
72
88
 
73
89
  await this.#awaitSync(contractAddress, scopes);
@@ -81,39 +97,6 @@ export class ContractSyncService implements StagedStore {
81
97
  scopes.forEach(scope => this.syncedContracts.delete(toKey(contractAddress, scope)));
82
98
  }
83
99
 
84
- async #syncContract(
85
- contractAddress: AztecAddress,
86
- functionToInvokeAfterSync: FunctionSelector | null,
87
- utilityExecutor: (call: FunctionCall, scopes: AztecAddress[]) => Promise<any>,
88
- anchorBlockHeader: BlockHeader,
89
- jobId: string,
90
- scopes: AztecAddress[],
91
- ): Promise<void> {
92
- this.log.debug(`Syncing contract ${contractAddress}`);
93
-
94
- await Promise.all([
95
- // Call sync_state sequentially for each scope address — each invocation synchronizes one account's private
96
- // state using scoped capsule arrays.
97
- (async () => {
98
- for (const scope of scopes) {
99
- await syncState(
100
- contractAddress,
101
- this.contractStore,
102
- functionToInvokeAfterSync,
103
- utilityExecutor,
104
- this.noteStore,
105
- this.aztecNode,
106
- anchorBlockHeader,
107
- jobId,
108
- scope,
109
- );
110
- }
111
- })(),
112
- verifyCurrentClassId(contractAddress, this.aztecNode, this.contractStore, anchorBlockHeader),
113
- ]);
114
- this.log.debug(`Contract ${contractAddress} synced`);
115
- }
116
-
117
100
  /** Clears sync cache. Called by BlockSynchronizer when anchor block changes. */
118
101
  wipe(): void {
119
102
  this.log.debug(`Wiping contract sync cache (${this.syncedContracts.size} entries)`);
@@ -138,22 +121,45 @@ export class ContractSyncService implements StagedStore {
138
121
  return !!this.excludedFromSync.get(jobId)?.has(contractAddress.toString());
139
122
  }
140
123
 
141
- /** If there are unsynced scopes, starts sync and stores the promise in cache with error cleanup. */
124
+ /**
125
+ * If there are unsynced scopes, starts one sync per scope (bounded by #syncSlot) and stores each promise in the
126
+ * cache with per-scope error cleanup. The verifyFn runs once for the whole fan-out and is awaited by every new
127
+ * scope's promise, matching the pre-parallelization invariant that a cache-miss batch re-verifies the class id.
128
+ */
142
129
  #startSyncIfNeeded(
143
130
  contractAddress: AztecAddress,
144
131
  scopes: AztecAddress[],
145
- syncFn: (scopesToSync: AztecAddress[]) => Promise<void>,
132
+ verifyFn: () => Promise<void>,
133
+ syncScopeFn: (scope: AztecAddress) => Promise<void>,
146
134
  ): void {
147
135
  const scopesToSync = scopes.filter(scope => !this.syncedContracts.has(toKey(contractAddress, scope)));
148
- const keys = scopesToSync.map(scope => toKey(contractAddress, scope));
149
- if (keys.length === 0) {
136
+ if (scopesToSync.length === 0) {
150
137
  return;
151
138
  }
152
- const promise = syncFn(scopesToSync).catch(err => {
153
- keys.forEach(key => this.syncedContracts.delete(key));
154
- throw err;
155
- });
156
- keys.forEach(key => this.syncedContracts.set(key, promise));
139
+
140
+ this.log.debug(`Syncing contract ${contractAddress} for ${scopesToSync.length} scope(s)`);
141
+ const verifyPromise = verifyFn();
142
+
143
+ for (const scope of scopesToSync) {
144
+ const key = toKey(contractAddress, scope);
145
+ const promise = Promise.all([verifyPromise, this.#runBounded(() => syncScopeFn(scope))])
146
+ .then(() => {})
147
+ .catch(err => {
148
+ this.syncedContracts.delete(key);
149
+ throw err;
150
+ });
151
+ this.syncedContracts.set(key, promise);
152
+ }
153
+ }
154
+
155
+ /** Runs fn while holding a slot in #syncSlot, bounding total concurrent scope syncs. */
156
+ async #runBounded<T>(fn: () => Promise<T>): Promise<T> {
157
+ await this.#syncSlot.acquire();
158
+ try {
159
+ return await fn();
160
+ } finally {
161
+ this.#syncSlot.release();
162
+ }
157
163
  }
158
164
 
159
165
  /** Collects all relevant scope promises (including in-flight ones from concurrent calls) and awaits them. */
@@ -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,
@@ -1,6 +1,7 @@
1
1
  import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
2
2
  import type { KeyStore } from '@aztec/key-store';
3
3
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
4
+ import type { L2TipsProvider } from '@aztec/stdlib/block';
4
5
  import type { AztecNode } from '@aztec/stdlib/interfaces/server';
5
6
  import { ExtendedDirectionalAppTaggingSecret, PendingTaggedLog, SiloedTag, Tag } from '@aztec/stdlib/logs';
6
7
  import type { BlockHeader } from '@aztec/stdlib/tx';
@@ -22,6 +23,7 @@ export class LogService {
22
23
  constructor(
23
24
  private readonly aztecNode: AztecNode,
24
25
  private readonly anchorBlockHeader: BlockHeader,
26
+ private readonly l2TipsStore: L2TipsProvider,
25
27
  private readonly keyStore: KeyStore,
26
28
  private readonly recipientTaggingStore: RecipientTaggingStore,
27
29
  private readonly senderAddressBookStore: SenderAddressBookStore,
@@ -118,6 +120,8 @@ export class LogService {
118
120
  const anchorBlockNumber = this.anchorBlockHeader.getBlockNumber();
119
121
  const anchorBlockHash = await this.anchorBlockHeader.hash();
120
122
 
123
+ const l2Tips = await this.l2TipsStore.getL2Tips();
124
+ const currentTimestamp = this.anchorBlockHeader.globalVariables.timestamp;
121
125
  // Get all secrets for this recipient (one per sender)
122
126
  const secrets = await this.#getSecretsForSenders(contractAddress, recipient);
123
127
 
@@ -130,6 +134,8 @@ export class LogService {
130
134
  this.recipientTaggingStore,
131
135
  anchorBlockNumber,
132
136
  anchorBlockHash,
137
+ currentTimestamp,
138
+ l2Tips.finalized.block.number,
133
139
  this.jobId,
134
140
  ),
135
141
  ),
@@ -174,7 +180,6 @@ export class LogService {
174
180
 
175
181
  if (!secret) {
176
182
  // Note that all senders originate from either the SenderAddressBookStore or the KeyStore.
177
- // TODO(F-512): make sure we actually prevent registering invalid senders.
178
183
  throw new Error(
179
184
  `Failed to compute a tagging secret for sender ${sender} - this implies this is an invalid address, which should not happen as they have been previously registered in PXE.`,
180
185
  );
@@ -33,6 +33,7 @@ import {
33
33
  } from '@aztec/stdlib/tx';
34
34
  import { VerificationKeyAsFields, VerificationKeyData, VkData } from '@aztec/stdlib/vks';
35
35
 
36
+ import { computeTxExpirationTimestamp } from './hints/compute_tx_expiration_timestamp.js';
36
37
  import { PrivateKernelResetPrivateInputsBuilder } from './hints/private_kernel_reset_private_inputs_builder.js';
37
38
  import type { PrivateKernelOracle } from './private_kernel_oracle.js';
38
39
 
@@ -267,15 +268,9 @@ export class PrivateKernelExecutionProver {
267
268
  // TODO: Enable padding once we better understand the final amounts to pad to.
268
269
  const paddedSideEffectAmounts = PaddedSideEffectAmounts.empty();
269
270
 
270
- // Use the aggregated expirationTimestamp set throughout the tx execution.
271
- // TODO: Call `computeTxExpirationTimestamp` to round the value down and reduce precision, improving privacy.
272
- const expirationTimestampUpperBound = previousKernelData.publicInputs.expirationTimestamp;
273
- const anchorBlockTimestamp = previousKernelData.publicInputs.constants.anchorBlockHeader.globalVariables.timestamp;
274
- if (expirationTimestampUpperBound <= anchorBlockTimestamp) {
275
- throw new Error(
276
- `Include-by timestamp must be greater than the anchor block timestamp. Anchor block timestamp: ${anchorBlockTimestamp}. Include-by timestamp: ${expirationTimestampUpperBound}.`,
277
- );
278
- }
271
+ // Round the aggregated expirationTimestamp down to reduce precision and avoid leaking which private
272
+ // functions were called via their exact expiration offsets.
273
+ const expirationTimestampUpperBound = computeTxExpirationTimestamp(previousKernelData.publicInputs);
279
274
 
280
275
  const privateInputs = new PrivateKernelTailCircuitPrivateInputs(
281
276
  previousKernelData,
@@ -7,13 +7,13 @@ import { getVKIndex, getVKSiblingPath } from '@aztec/noir-protocol-circuits-type
7
7
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
8
8
  import type { FunctionSelector } from '@aztec/stdlib/abi';
9
9
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
10
- import { BlockHash } from '@aztec/stdlib/block';
11
10
  import { type ContractInstanceWithAddress, computeSaltedInitializationHash } from '@aztec/stdlib/contract';
12
11
  import { DelayedPublicMutableValues, DelayedPublicMutableValuesWithHash } from '@aztec/stdlib/delayed-public-mutable';
13
12
  import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
14
13
  import type { AztecNode } from '@aztec/stdlib/interfaces/client';
15
14
  import { UpdatedClassIdHints } from '@aztec/stdlib/kernel';
16
15
  import type { NullifierMembershipWitness } from '@aztec/stdlib/trees';
16
+ import type { BlockHeader } from '@aztec/stdlib/tx';
17
17
  import type { VerificationKeyAsFields } from '@aztec/stdlib/vks';
18
18
 
19
19
  import type { ContractStore } from '../storage/contract_store/contract_store.js';
@@ -26,7 +26,7 @@ export class PrivateKernelOracle {
26
26
  private contractStore: ContractStore,
27
27
  private keyStore: KeyStore,
28
28
  private node: AztecNode,
29
- private blockHash: BlockHash,
29
+ private blockHeader: BlockHeader,
30
30
  ) {}
31
31
 
32
32
  /** Retrieves the preimage of a contract address from the registered contract instances db. */
@@ -80,22 +80,20 @@ export class PrivateKernelOracle {
80
80
  }
81
81
 
82
82
  /** Returns a membership witness with the sibling path and leaf index in our note hash tree. */
83
- getNoteHashMembershipWitness(noteHash: Fr): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
84
- return this.node.getNoteHashMembershipWitness(this.blockHash, noteHash);
83
+ async getNoteHashMembershipWitness(
84
+ noteHash: Fr,
85
+ ): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
86
+ return this.node.getNoteHashMembershipWitness(await this.blockHeader.hash(), noteHash);
85
87
  }
86
88
 
87
89
  /** Returns a membership witness with the sibling path and leaf index in our nullifier indexed merkle tree. */
88
- getNullifierMembershipWitness(nullifier: Fr): Promise<NullifierMembershipWitness | undefined> {
89
- return this.node.getNullifierMembershipWitness(this.blockHash, nullifier);
90
+ async getNullifierMembershipWitness(nullifier: Fr): Promise<NullifierMembershipWitness | undefined> {
91
+ return this.node.getNullifierMembershipWitness(await this.blockHeader.hash(), nullifier);
90
92
  }
91
93
 
92
94
  /** Returns the root of our note hash merkle tree. */
93
- async getNoteHashTreeRoot(): Promise<Fr> {
94
- const header = await this.node.getBlockHeader(this.blockHash);
95
- if (!header) {
96
- throw new Error(`No block header found for block hash ${this.blockHash}`);
97
- }
98
- return header.state.partial.noteHashTree.root;
95
+ getNoteHashTreeRoot(): Fr {
96
+ return this.blockHeader.state.partial.noteHashTree.root;
99
97
  }
100
98
 
101
99
  /**
@@ -126,14 +124,16 @@ export class PrivateKernelOracle {
126
124
  ProtocolContractAddress.ContractInstanceRegistry,
127
125
  delayedPublicMutableHashSlot,
128
126
  );
129
- const updatedClassIdWitness = await this.node.getPublicDataWitness(this.blockHash, hashLeafSlot);
127
+ const blockHash = await this.blockHeader.hash();
128
+
129
+ const updatedClassIdWitness = await this.node.getPublicDataWitness(blockHash, hashLeafSlot);
130
130
 
131
131
  if (!updatedClassIdWitness) {
132
132
  throw new Error(`No public data tree witness found for ${hashLeafSlot}`);
133
133
  }
134
134
 
135
135
  const readStorage = (storageSlot: Fr) =>
136
- this.node.getPublicStorageAt(this.blockHash, ProtocolContractAddress.ContractInstanceRegistry, storageSlot);
136
+ this.node.getPublicStorageAt(blockHash, ProtocolContractAddress.ContractInstanceRegistry, storageSlot);
137
137
  const delayedPublicMutableValues = await DelayedPublicMutableValues.readFromTree(
138
138
  delayedPublicMutableSlot,
139
139
  readStorage,