@aztec/pxe 0.0.1-commit.5914bae → 0.0.1-commit.59a0419c6

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 (49) 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 +2 -2
  5. package/dest/config/index.d.ts.map +1 -1
  6. package/dest/config/index.js +8 -15
  7. package/dest/config/package_info.js +1 -1
  8. package/dest/contract_function_simulator/contract_function_simulator.d.ts +1 -1
  9. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  10. package/dest/contract_function_simulator/contract_function_simulator.js +3 -5
  11. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +10 -8
  12. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  13. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +20 -15
  14. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +3 -4
  15. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  16. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +2 -3
  17. package/dest/contract_function_simulator/pick_notes.d.ts +1 -1
  18. package/dest/contract_function_simulator/pick_notes.d.ts.map +1 -1
  19. package/dest/contract_function_simulator/pick_notes.js +6 -0
  20. package/dest/events/event_service.d.ts +1 -1
  21. package/dest/events/event_service.d.ts.map +1 -1
  22. package/dest/events/event_service.js +10 -1
  23. package/dest/private_kernel/private_kernel_oracle.d.ts +5 -5
  24. package/dest/private_kernel/private_kernel_oracle.d.ts.map +1 -1
  25. package/dest/private_kernel/private_kernel_oracle.js +12 -15
  26. package/dest/pxe.d.ts +17 -5
  27. package/dest/pxe.d.ts.map +1 -1
  28. package/dest/pxe.js +49 -23
  29. package/dest/storage/private_event_store/private_event_store.d.ts +1 -1
  30. package/dest/storage/private_event_store/private_event_store.d.ts.map +1 -1
  31. package/dest/storage/private_event_store/private_event_store.js +3 -0
  32. package/dest/storage/private_event_store/stored_private_event.js +1 -1
  33. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts +1 -1
  34. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts.map +1 -1
  35. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.js +1 -1
  36. package/package.json +16 -16
  37. package/src/block_synchronizer/block_synchronizer.ts +16 -2
  38. package/src/config/index.ts +3 -9
  39. package/src/config/package_info.ts +1 -1
  40. package/src/contract_function_simulator/contract_function_simulator.ts +3 -5
  41. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +31 -15
  42. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +2 -3
  43. package/src/contract_function_simulator/pick_notes.ts +8 -0
  44. package/src/events/event_service.ts +13 -1
  45. package/src/private_kernel/private_kernel_oracle.ts +14 -14
  46. package/src/pxe.ts +82 -27
  47. package/src/storage/private_event_store/private_event_store.ts +4 -0
  48. package/src/storage/private_event_store/stored_private_event.ts +1 -1
  49. package/src/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.ts +3 -1
@@ -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';
@@ -20,6 +21,7 @@ import type { PrivateEventStore } from '../storage/private_event_store/private_e
20
21
  export class BlockSynchronizer implements L2BlockStreamEventHandler {
21
22
  private log: Logger;
22
23
  private isSyncing: Promise<void> | undefined;
24
+ private readonly eventQueue = new SerialQueue();
23
25
  protected readonly blockStream: L2BlockStream;
24
26
 
25
27
  constructor(
@@ -35,6 +37,7 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
35
37
  ) {
36
38
  this.log = createLogger('pxe:block_synchronizer', bindings);
37
39
  this.blockStream = this.createBlockStream(config);
40
+ this.eventQueue.start();
38
41
  }
39
42
 
40
43
  protected createBlockStream(config: Partial<BlockSynchronizerConfig>): L2BlockStream {
@@ -52,8 +55,12 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
52
55
  );
53
56
  }
54
57
 
55
- /** Handle events emitted by the block stream. */
56
- public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
58
+ /** Handle events emitted by the block stream. Serialized to prevent concurrent mutations to anchor state. */
59
+ public handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
60
+ return this.eventQueue.put(() => this.doHandleBlockStreamEvent(event));
61
+ }
62
+
63
+ private async doHandleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
57
64
  await this.l2TipsStore.handleBlockStreamEvent(event);
58
65
 
59
66
  switch (event.type) {
@@ -167,6 +174,13 @@ export class BlockSynchronizer implements L2BlockStreamEventHandler {
167
174
  }
168
175
  }
169
176
 
177
+ /** Stops the block synchronizer, waiting for any in-progress sync and queued events to complete. */
178
+ public async stop() {
179
+ await this.isSyncing;
180
+ await this.blockStream.stop();
181
+ await this.eventQueue.end();
182
+ }
183
+
170
184
  private async doSync() {
171
185
  let currentHeader;
172
186
 
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  type ConfigMappingsType,
3
3
  booleanConfigHelper,
4
+ enumConfigHelper,
4
5
  getConfigFromMappings,
5
6
  numberConfigHelper,
6
7
  parseBooleanEnv,
7
8
  } from '@aztec/foundation/config';
8
- import { type DataStoreConfig, dataConfigMappings } from '@aztec/kv-store/config';
9
9
  import { type ChainConfig, chainConfigMappings } from '@aztec/stdlib/config';
10
+ import { type DataStoreConfig, dataConfigMappings } from '@aztec/stdlib/kv-store';
10
11
 
11
12
  export { getPackageInfo } from './package_info.js';
12
13
 
@@ -58,14 +59,7 @@ export const pxeConfigMappings: ConfigMappingsType<PXEConfig> = {
58
59
  syncChainTip: {
59
60
  env: 'PXE_SYNC_CHAIN_TIP',
60
61
  description: 'Which chain tip to sync to (proposed, checkpointed, proven, finalized)',
61
- defaultValue: 'proposed',
62
- parseEnv: (val: string) => {
63
- const allowedValues = ['proposed', 'checkpointed', 'proven', 'finalized'];
64
- if (allowedValues.includes(val)) {
65
- return val;
66
- }
67
- throw new Error(`Invalid value for PXE_SYNC_CHAIN_TIP: ${val}. Allowed values are: ${allowedValues.join(', ')}`);
68
- },
62
+ ...enumConfigHelper(['proposed', 'checkpointed', 'proven', 'finalized'], 'proposed'),
69
63
  },
70
64
  };
71
65
 
@@ -1,3 +1,3 @@
1
1
  export function getPackageInfo() {
2
- return { version: '4.2.0', name: '@aztec/pxe' };
2
+ return { version: '5.0.0', name: '@aztec/pxe' };
3
3
  }
@@ -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.
@@ -384,7 +383,6 @@ export class ContractFunctionSimulator {
384
383
  throw createSimulationError(err instanceof Error ? err : new Error('Unknown error during private execution'));
385
384
  }
386
385
  }
387
- // docs:end:execute_utility_function
388
386
 
389
387
  /**
390
388
  * Returns the execution statistics collected during the simulator run.
@@ -846,9 +844,9 @@ function meterGasUsed(data: PrivateToRollupAccumulatedData | PrivateToPublicAccu
846
844
  meteredL2Gas += numPrivatelogs * L2_GAS_PER_PRIVATE_LOG;
847
845
 
848
846
  const numContractClassLogs = arrayNonEmptyLength(data.contractClassLogsHashes, log => log.isEmpty());
849
- // Every contract class log emits its length and contract address as additional fields
847
+ // Every contract class log emits its contract address as an additional field
850
848
  meteredDAFields += data.contractClassLogsHashes.reduce(
851
- (acc, log) => (!log.isEmpty() ? acc + log.logHash.length + 2 : acc),
849
+ (acc, log) => (!log.isEmpty() ? acc + log.logHash.length + 1 : acc),
852
850
  0,
853
851
  );
854
852
  meteredL2Gas += numContractClassLogs * L2_GAS_PER_CONTRACT_CLASS_LOG;
@@ -81,8 +81,11 @@ 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;
85
- private senderForTags?: AztecAddress;
84
+ private readonly initialSideEffectCounter: number;
85
+ /** Sender for tags passed in at oracle construction time. Returned by `getSenderForTags` unless overridden. */
86
+ private readonly defaultSenderForTags: AztecAddress | undefined;
87
+ /** Per-call sender-for-tags override, set by `setSenderForTags`. Takes precedence over `defaultSenderForTags`. */
88
+ private currentSenderForTags: AztecAddress | undefined;
86
89
  private readonly simulator?: CircuitSimulator;
87
90
 
88
91
  constructor(args: PrivateExecutionOracleArgs) {
@@ -100,13 +103,18 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
100
103
  this.taggingIndexCache = args.taggingIndexCache;
101
104
  this.senderTaggingStore = args.senderTaggingStore;
102
105
  this.totalPublicCalldataCount = args.totalPublicCalldataCount ?? 0;
103
- this.sideEffectCounter = args.sideEffectCounter ?? 0;
104
- this.senderForTags = args.senderForTags;
106
+ this.initialSideEffectCounter = args.sideEffectCounter ?? 0;
107
+ this.defaultSenderForTags = args.senderForTags;
105
108
  this.simulator = args.simulator;
106
109
  }
107
110
 
108
111
  public getPrivateContextInputs(): PrivateContextInputs {
109
- return new PrivateContextInputs(this.callContext, this.anchorBlockHeader, this.txContext, this.sideEffectCounter);
112
+ return new PrivateContextInputs(
113
+ this.callContext,
114
+ this.anchorBlockHeader,
115
+ this.txContext,
116
+ this.initialSideEffectCounter,
117
+ );
110
118
  }
111
119
 
112
120
  // We still need this function until we can get user-defined ordering of structs for fn arguments
@@ -173,11 +181,10 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
173
181
  * for a tag in order to emit a log. Constrained tagging should not use this as there is no
174
182
  * guarantee that the recipient knows about the sender, and hence about the shared secret.
175
183
  *
176
- * The value persists through nested calls, meaning all calls down the stack will use the same
177
- * 'senderForTags' value (unless it is replaced).
184
+ * Returns `currentSenderForTags` if set (via `setSenderForTags`), otherwise `defaultSenderForTags`.
178
185
  */
179
186
  public getSenderForTags(): Promise<AztecAddress | undefined> {
180
- return Promise.resolve(this.senderForTags);
187
+ return Promise.resolve(this.currentSenderForTags ?? this.defaultSenderForTags);
181
188
  }
182
189
 
183
190
  /**
@@ -187,12 +194,14 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
187
194
  * for a tag in order to emit a log. Constrained tagging should not use this as there is no
188
195
  * guarantee that the recipient knows about the sender, and hence about the shared secret.
189
196
  *
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).
197
+ * Overrides `defaultSenderForTags` for the remainder of this call. Each oracle instance is
198
+ * independent, so this has no effect on any other call in the execution.
193
199
  */
194
200
  public setSenderForTags(senderForTags: AztecAddress): Promise<void> {
195
- this.senderForTags = senderForTags;
201
+ this.logger.debug(
202
+ `Sender for tags switched to ${senderForTags} by contract ${this.contractAddress} (default was ${this.defaultSenderForTags})`,
203
+ );
204
+ this.currentSenderForTags = senderForTags;
196
205
  return Promise.resolve();
197
206
  }
198
207
 
@@ -216,7 +225,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
216
225
  this.logger.warn(`Computing a tag for invalid recipient ${recipient} - returning a random tag instead`, {
217
226
  contractAddress: this.contractAddress,
218
227
  });
219
- return new Tag(Fr.random());
228
+ return Tag.random();
220
229
  }
221
230
 
222
231
  const index = await this.#getIndexToUseForSecret(extendedSecret);
@@ -571,9 +580,9 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
571
580
  jobId: this.jobId,
572
581
  totalPublicCalldataCount: this.totalPublicCalldataCount,
573
582
  sideEffectCounter,
574
- scopes: this.scopes,
575
583
  log: this.logger,
576
- senderForTags: this.senderForTags,
584
+ scopes: this.scopes,
585
+ senderForTags: this.defaultSenderForTags,
577
586
  simulator: this.simulator!,
578
587
  l2TipsStore: this.l2TipsStore,
579
588
  });
@@ -588,6 +597,9 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
588
597
  functionSelector,
589
598
  );
590
599
 
600
+ // Propagate the nested call's calldata count so the parent sees its increments on subsequent enqueues.
601
+ this.totalPublicCalldataCount = privateExecutionOracle.getTotalPublicCalldataCount();
602
+
591
603
  if (isStaticCall) {
592
604
  this.#checkValidStaticCall(childExecutionResult);
593
605
  }
@@ -621,6 +633,10 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
621
633
  return Promise.resolve();
622
634
  }
623
635
 
636
+ public getTotalPublicCalldataCount(): number {
637
+ return this.totalPublicCalldataCount;
638
+ }
639
+
624
640
  public notifyRevertiblePhaseStart(minRevertibleSideEffectCounter: number): Promise<void> {
625
641
  return this.noteCache.setMinRevertibleSideEffectCounter(minRevertibleSideEffectCounter);
626
642
  }
@@ -333,10 +333,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
333
333
  }
334
334
 
335
335
  /**
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.
336
+ * Returns an auth witness for the given message hash from the list of transient witnesses for this transaction.
338
337
  * @param messageHash - Hash of the message to authenticate.
339
- * @returns Authentication witness for the requested message hash.
338
+ * @returns Authentication witness for the requested message hash, or undefined if not found.
340
339
  */
341
340
  public getAuthWitness(messageHash: Fr): Promise<Fr[] | undefined> {
342
341
  return Promise.resolve(this.authWitnesses.find(w => w.requestHash.equals(messageHash))?.witness);
@@ -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.
@@ -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
@@ -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,
package/src/pxe.ts CHANGED
@@ -89,6 +89,14 @@ export type PackedPrivateEvent = InTx & {
89
89
  eventSelector: EventSelector;
90
90
  };
91
91
 
92
+ /** Options for PXE.proveTx. */
93
+ export type ProveTxOpts = {
94
+ /** Addresses whose private state and keys are accessible during private execution. */
95
+ scopes: AztecAddress[];
96
+ /** Sender address used to derive discovery tags for private messages (notes, events, logs) this tx emits. */
97
+ senderForTags?: AztecAddress;
98
+ };
99
+
92
100
  /** Options for PXE.profileTx. */
93
101
  export type ProfileTxOpts = {
94
102
  /** The profiling mode to use. */
@@ -97,6 +105,8 @@ export type ProfileTxOpts = {
97
105
  skipProofGeneration?: boolean;
98
106
  /** Addresses whose private state and keys are accessible during private execution. */
99
107
  scopes: AztecAddress[];
108
+ /** Sender address used to derive discovery tags for private messages (notes, events, logs) this tx emits. */
109
+ senderForTags?: AztecAddress;
100
110
  };
101
111
 
102
112
  /** Options for PXE.simulateTx. */
@@ -113,6 +123,8 @@ export type SimulateTxOpts = {
113
123
  overrides?: SimulationOverrides;
114
124
  /** Addresses whose private state and keys are accessible during private execution */
115
125
  scopes: AztecAddress[];
126
+ /** Sender address used to derive discovery tags for private messages (notes, events, logs) this tx emits. */
127
+ senderForTags?: AztecAddress;
116
128
  };
117
129
 
118
130
  /** Options for PXE.executeUtility. */
@@ -148,6 +160,7 @@ export type PXECreateArgs = {
148
160
  export class PXE {
149
161
  private constructor(
150
162
  private node: AztecNode,
163
+ private db: AztecAsyncKVStore,
151
164
  private blockStateSynchronizer: BlockSynchronizer,
152
165
  private keyStore: KeyStore,
153
166
  private contractStore: ContractStore,
@@ -247,6 +260,7 @@ export class PXE {
247
260
 
248
261
  const pxe = new PXE(
249
262
  node,
263
+ store,
250
264
  synchronizer,
251
265
  keyStore,
252
266
  contractStore,
@@ -366,17 +380,24 @@ export class PXE {
366
380
 
367
381
  // Executes the entrypoint private function, as well as all nested private
368
382
  // functions that might arise.
369
- async #executePrivate(
370
- contractFunctionSimulator: ContractFunctionSimulator,
371
- txRequest: TxExecutionRequest,
372
- scopes: AztecAddress[],
373
- jobId: string,
374
- ): Promise<PrivateExecutionResult> {
383
+ async #executePrivate({
384
+ contractFunctionSimulator,
385
+ txRequest,
386
+ anchorBlockHeader,
387
+ scopes,
388
+ jobId,
389
+ senderForTags,
390
+ }: {
391
+ contractFunctionSimulator: ContractFunctionSimulator;
392
+ txRequest: TxExecutionRequest;
393
+ anchorBlockHeader: BlockHeader;
394
+ scopes: AztecAddress[];
395
+ jobId: string;
396
+ senderForTags?: AztecAddress;
397
+ }): Promise<PrivateExecutionResult> {
375
398
  const { origin: contractAddress, functionSelector } = txRequest;
376
399
 
377
400
  try {
378
- const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
379
-
380
401
  await this.contractSyncService.ensureContractSynced(
381
402
  contractAddress,
382
403
  functionSelector,
@@ -393,6 +414,7 @@ export class PXE {
393
414
  anchorBlockHeader,
394
415
  scopes,
395
416
  jobId,
417
+ senderForTags,
396
418
  });
397
419
  this.log.debug(`Private simulation completed for ${contractAddress.toString()}:${functionSelector}`);
398
420
  return result;
@@ -481,11 +503,10 @@ export class PXE {
481
503
  txExecutionRequest: TxExecutionRequest,
482
504
  proofCreator: PrivateKernelProver,
483
505
  privateExecutionResult: PrivateExecutionResult,
506
+ anchorBlockHeader: BlockHeader,
484
507
  config: PrivateKernelExecutionProverConfig,
485
508
  ): Promise<PrivateKernelExecutionProofOutput<PrivateKernelTailCircuitPublicInputs>> {
486
- const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
487
- const anchorBlockHash = await anchorBlockHeader.hash();
488
- const kernelOracle = new PrivateKernelOracle(this.contractStore, this.keyStore, this.node, anchorBlockHash);
509
+ const kernelOracle = new PrivateKernelOracle(this.contractStore, this.keyStore, this.node, anchorBlockHeader);
489
510
  const kernelTraceProver = new PrivateKernelExecutionProver(
490
511
  kernelOracle,
491
512
  proofCreator,
@@ -579,8 +600,8 @@ export class PXE {
579
600
  if (wasAdded) {
580
601
  this.log.info(`Added sender:\n ${sender.toString()}`);
581
602
  // Wipe the entire sync cache: the new sender's tagged logs could contain notes/events for any contract, so
582
- // all contracts must re-sync to discover them.
583
- this.contractSyncService.wipe();
603
+ // all contracts must re-sync to discover them. Queued to avoid wiping while a job is in flight.
604
+ await this.#putInJobQueue(() => Promise.resolve(this.contractSyncService.wipe()));
584
605
  } else {
585
606
  this.log.info(`Sender:\n "${sender.toString()}"\n already registered.`);
586
607
  }
@@ -740,7 +761,7 @@ export class PXE {
740
761
  * @throws If contract code not found, or public simulation reverts.
741
762
  * Also throws if simulatePublic is true and public simulation reverts.
742
763
  */
743
- public proveTx(txRequest: TxExecutionRequest, scopes: AztecAddress[]): Promise<TxProvingResult> {
764
+ public proveTx(txRequest: TxExecutionRequest, { scopes, senderForTags }: ProveTxOpts): Promise<TxProvingResult> {
744
765
  let privateExecutionResult: PrivateExecutionResult;
745
766
  // We disable proving concurrently mostly out of caution, since it accesses some of our stores. Proving is so
746
767
  // computationally demanding that it'd be rare for someone to try to do it concurrently regardless.
@@ -749,16 +770,24 @@ export class PXE {
749
770
  try {
750
771
  const syncTimer = new Timer();
751
772
  await this.blockStateSynchronizer.sync();
773
+ const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
752
774
  const syncTime = syncTimer.ms();
753
775
  const contractFunctionSimulator = this.#getSimulatorForTx();
754
- privateExecutionResult = await this.#executePrivate(contractFunctionSimulator, txRequest, scopes, jobId);
776
+ privateExecutionResult = await this.#executePrivate({
777
+ contractFunctionSimulator,
778
+ txRequest,
779
+ anchorBlockHeader,
780
+ scopes,
781
+ jobId,
782
+ senderForTags,
783
+ });
755
784
 
756
785
  const {
757
786
  publicInputs,
758
787
  chonkProof,
759
788
  executionSteps,
760
789
  timings: { proving } = {},
761
- } = await this.#prove(txRequest, this.proofCreator, privateExecutionResult, {
790
+ } = await this.#prove(txRequest, this.proofCreator, privateExecutionResult, anchorBlockHeader, {
762
791
  simulate: false,
763
792
  skipFeeEnforcement: false,
764
793
  profileMode: 'none',
@@ -821,7 +850,7 @@ export class PXE {
821
850
  */
822
851
  public profileTx(
823
852
  txRequest: TxExecutionRequest,
824
- { profileMode, skipProofGeneration = true, scopes }: ProfileTxOpts,
853
+ { profileMode, skipProofGeneration = true, scopes, senderForTags }: ProfileTxOpts,
825
854
  ): Promise<TxProfileResult> {
826
855
  // We disable concurrent profiles for consistency with simulateTx.
827
856
  return this.#putInJobQueue(async jobId => {
@@ -841,15 +870,24 @@ export class PXE {
841
870
  );
842
871
  const syncTimer = new Timer();
843
872
  await this.blockStateSynchronizer.sync();
873
+ const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
844
874
  const syncTime = syncTimer.ms();
845
875
 
846
876
  const contractFunctionSimulator = this.#getSimulatorForTx();
847
- const privateExecutionResult = await this.#executePrivate(contractFunctionSimulator, txRequest, scopes, jobId);
877
+ const privateExecutionResult = await this.#executePrivate({
878
+ contractFunctionSimulator,
879
+ txRequest,
880
+ anchorBlockHeader,
881
+ scopes,
882
+ jobId,
883
+ senderForTags,
884
+ });
848
885
 
849
886
  const { executionSteps, timings: { proving } = {} } = await this.#prove(
850
887
  txRequest,
851
888
  this.proofCreator,
852
889
  privateExecutionResult,
890
+ anchorBlockHeader,
853
891
  {
854
892
  simulate: skipProofGeneration,
855
893
  skipFeeEnforcement: false,
@@ -917,6 +955,7 @@ export class PXE {
917
955
  skipKernels = true,
918
956
  overrides,
919
957
  scopes,
958
+ senderForTags,
920
959
  }: SimulateTxOpts,
921
960
  ): Promise<TxSimulationResult> {
922
961
  // We disable concurrent simulations since those might execute oracles which read and write to the PXE stores (e.g.
@@ -939,6 +978,7 @@ export class PXE {
939
978
  );
940
979
  const syncTimer = new Timer();
941
980
  await this.blockStateSynchronizer.sync();
981
+ const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
942
982
  const syncTime = syncTimer.ms();
943
983
 
944
984
  const overriddenContracts = overrides?.contracts ? new Set(Object.keys(overrides.contracts)) : undefined;
@@ -958,7 +998,14 @@ export class PXE {
958
998
  }
959
999
 
960
1000
  // Execution of private functions only; no proving, and no kernel logic.
961
- const privateExecutionResult = await this.#executePrivate(contractFunctionSimulator, txRequest, scopes, jobId);
1001
+ const privateExecutionResult = await this.#executePrivate({
1002
+ contractFunctionSimulator,
1003
+ txRequest,
1004
+ anchorBlockHeader,
1005
+ scopes,
1006
+ jobId,
1007
+ senderForTags,
1008
+ });
962
1009
 
963
1010
  let publicInputs: PrivateKernelTailCircuitPublicInputs | undefined;
964
1011
  let executionSteps: PrivateExecutionStep[] = [];
@@ -971,11 +1018,17 @@ export class PXE {
971
1018
  ));
972
1019
  } else {
973
1020
  // Kernel logic, plus proving of all private functions and kernels.
974
- ({ publicInputs, executionSteps } = await this.#prove(txRequest, this.proofCreator, privateExecutionResult, {
975
- simulate: true,
976
- skipFeeEnforcement,
977
- profileMode: 'none',
978
- }));
1021
+ ({ publicInputs, executionSteps } = await this.#prove(
1022
+ txRequest,
1023
+ this.proofCreator,
1024
+ privateExecutionResult,
1025
+ anchorBlockHeader,
1026
+ {
1027
+ simulate: true,
1028
+ skipFeeEnforcement,
1029
+ profileMode: 'none',
1030
+ },
1031
+ ));
979
1032
  }
980
1033
 
981
1034
  const privateSimulationResult = new PrivateSimulationResult(privateExecutionResult, publicInputs);
@@ -1174,9 +1227,11 @@ export class PXE {
1174
1227
  }
1175
1228
 
1176
1229
  /**
1177
- * Stops the PXE's job queue.
1230
+ * Stops the PXE's job queue and closes the backing store.
1178
1231
  */
1179
- public stop(): Promise<void> {
1180
- return this.jobQueue.end();
1232
+ public async stop(): Promise<void> {
1233
+ await this.jobQueue.end();
1234
+ await this.blockStateSynchronizer.stop();
1235
+ await this.db.close();
1181
1236
  }
1182
1237
  }
@@ -234,6 +234,10 @@ export class PrivateEventStore implements StagedStore {
234
234
  * IMPORTANT: This method must be called within a transaction to ensure atomicity.
235
235
  */
236
236
  public async rollback(blockNumber: number, synchedBlockNumber: number): Promise<void> {
237
+ if (this.#eventsForJob.size > 0) {
238
+ throw new Error('PXE private event store rollback is not allowed while jobs are running');
239
+ }
240
+
237
241
  // First pass: collect all event IDs for all blocks, starting reads during iteration to keep tx alive.
238
242
  const eventsByBlock: Map<number, { eventId: string; eventReadPromise: Promise<Buffer | undefined> }[]> = new Map();
239
243
 
@@ -49,7 +49,7 @@ export class StoredPrivateEvent {
49
49
  const msgContentLength = reader.readNumber();
50
50
  const msgContent = reader.readArray(msgContentLength, Fr);
51
51
  const l2BlockNumber = reader.readNumber();
52
- const l2BlockHash = new BlockHash(Fr.fromBuffer(reader));
52
+ const l2BlockHash = BlockHash.fromBuffer(reader);
53
53
  const txHash = TxHash.fromBuffer(reader);
54
54
  const txIndexInBlock = reader.readNumber();
55
55
  const eventIndexInTx = reader.readNumber();
@@ -113,7 +113,9 @@ export async function loadPrivateLogsForSenderRecipientPair(
113
113
 
114
114
  if (highestAgedIndex !== undefined && highestAgedIndex > highestFinalizedIndex) {
115
115
  // This is just a sanity check as this should never happen.
116
- throw new Error('Highest aged index lower than highest finalized index invariant violated');
116
+ throw new Error(
117
+ `Highest aged index (${highestAgedIndex}) must not exceed highest finalized index (${highestFinalizedIndex})`,
118
+ );
117
119
  }
118
120
 
119
121
  await taggingStore.updateHighestFinalizedIndex(secret, highestFinalizedIndex, jobId);