@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
@@ -11,7 +11,7 @@
11
11
  /// if AZTEC_NR_MINOR > PXE_MINOR because if a contract is updated to use a newer Aztec.nr dependency without actually
12
12
  /// using any of the new oracles then there is no reason to throw.
13
13
  export const ORACLE_VERSION_MAJOR = 22;
14
- export const ORACLE_VERSION_MINOR = 1;
14
+ export const ORACLE_VERSION_MINOR = 2;
15
15
 
16
16
  /// This hash is computed from the Oracle interface and is used to detect when that interface changes. When it does,
17
17
  /// you need to either:
@@ -19,4 +19,4 @@ export const ORACLE_VERSION_MINOR = 1;
19
19
  /// - increment only `ORACLE_VERSION_MINOR` if the change is additive (a new oracle was added).
20
20
  ///
21
21
  /// These constants must be kept in sync between this file and `noir-projects/aztec-nr/aztec/src/oracle/version.nr`.
22
- export const ORACLE_INTERFACE_HASH = 'efafa0db2cc1f94e26d794d0079c8f71115261df0c3d0fa8cb5b64f17a12db92';
22
+ export const ORACLE_INTERFACE_HASH = '193fe3f9fee6a84d26803e636c9746dd805a4f389d44a0618de75c2c5eb4912e';
@@ -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,
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. */
@@ -368,17 +380,24 @@ export class PXE {
368
380
 
369
381
  // Executes the entrypoint private function, as well as all nested private
370
382
  // functions that might arise.
371
- async #executePrivate(
372
- contractFunctionSimulator: ContractFunctionSimulator,
373
- txRequest: TxExecutionRequest,
374
- scopes: AztecAddress[],
375
- jobId: string,
376
- ): 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> {
377
398
  const { origin: contractAddress, functionSelector } = txRequest;
378
399
 
379
400
  try {
380
- const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
381
-
382
401
  await this.contractSyncService.ensureContractSynced(
383
402
  contractAddress,
384
403
  functionSelector,
@@ -395,6 +414,7 @@ export class PXE {
395
414
  anchorBlockHeader,
396
415
  scopes,
397
416
  jobId,
417
+ senderForTags,
398
418
  });
399
419
  this.log.debug(`Private simulation completed for ${contractAddress.toString()}:${functionSelector}`);
400
420
  return result;
@@ -483,11 +503,10 @@ export class PXE {
483
503
  txExecutionRequest: TxExecutionRequest,
484
504
  proofCreator: PrivateKernelProver,
485
505
  privateExecutionResult: PrivateExecutionResult,
506
+ anchorBlockHeader: BlockHeader,
486
507
  config: PrivateKernelExecutionProverConfig,
487
508
  ): Promise<PrivateKernelExecutionProofOutput<PrivateKernelTailCircuitPublicInputs>> {
488
- const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
489
- const anchorBlockHash = await anchorBlockHeader.hash();
490
- const kernelOracle = new PrivateKernelOracle(this.contractStore, this.keyStore, this.node, anchorBlockHash);
509
+ const kernelOracle = new PrivateKernelOracle(this.contractStore, this.keyStore, this.node, anchorBlockHeader);
491
510
  const kernelTraceProver = new PrivateKernelExecutionProver(
492
511
  kernelOracle,
493
512
  proofCreator,
@@ -581,8 +600,8 @@ export class PXE {
581
600
  if (wasAdded) {
582
601
  this.log.info(`Added sender:\n ${sender.toString()}`);
583
602
  // Wipe the entire sync cache: the new sender's tagged logs could contain notes/events for any contract, so
584
- // all contracts must re-sync to discover them.
585
- 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()));
586
605
  } else {
587
606
  this.log.info(`Sender:\n "${sender.toString()}"\n already registered.`);
588
607
  }
@@ -742,7 +761,7 @@ export class PXE {
742
761
  * @throws If contract code not found, or public simulation reverts.
743
762
  * Also throws if simulatePublic is true and public simulation reverts.
744
763
  */
745
- public proveTx(txRequest: TxExecutionRequest, scopes: AztecAddress[]): Promise<TxProvingResult> {
764
+ public proveTx(txRequest: TxExecutionRequest, { scopes, senderForTags }: ProveTxOpts): Promise<TxProvingResult> {
746
765
  let privateExecutionResult: PrivateExecutionResult;
747
766
  // We disable proving concurrently mostly out of caution, since it accesses some of our stores. Proving is so
748
767
  // computationally demanding that it'd be rare for someone to try to do it concurrently regardless.
@@ -751,16 +770,24 @@ export class PXE {
751
770
  try {
752
771
  const syncTimer = new Timer();
753
772
  await this.blockStateSynchronizer.sync();
773
+ const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
754
774
  const syncTime = syncTimer.ms();
755
775
  const contractFunctionSimulator = this.#getSimulatorForTx();
756
- 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
+ });
757
784
 
758
785
  const {
759
786
  publicInputs,
760
787
  chonkProof,
761
788
  executionSteps,
762
789
  timings: { proving } = {},
763
- } = await this.#prove(txRequest, this.proofCreator, privateExecutionResult, {
790
+ } = await this.#prove(txRequest, this.proofCreator, privateExecutionResult, anchorBlockHeader, {
764
791
  simulate: false,
765
792
  skipFeeEnforcement: false,
766
793
  profileMode: 'none',
@@ -823,7 +850,7 @@ export class PXE {
823
850
  */
824
851
  public profileTx(
825
852
  txRequest: TxExecutionRequest,
826
- { profileMode, skipProofGeneration = true, scopes }: ProfileTxOpts,
853
+ { profileMode, skipProofGeneration = true, scopes, senderForTags }: ProfileTxOpts,
827
854
  ): Promise<TxProfileResult> {
828
855
  // We disable concurrent profiles for consistency with simulateTx.
829
856
  return this.#putInJobQueue(async jobId => {
@@ -843,15 +870,24 @@ export class PXE {
843
870
  );
844
871
  const syncTimer = new Timer();
845
872
  await this.blockStateSynchronizer.sync();
873
+ const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
846
874
  const syncTime = syncTimer.ms();
847
875
 
848
876
  const contractFunctionSimulator = this.#getSimulatorForTx();
849
- 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
+ });
850
885
 
851
886
  const { executionSteps, timings: { proving } = {} } = await this.#prove(
852
887
  txRequest,
853
888
  this.proofCreator,
854
889
  privateExecutionResult,
890
+ anchorBlockHeader,
855
891
  {
856
892
  simulate: skipProofGeneration,
857
893
  skipFeeEnforcement: false,
@@ -919,6 +955,7 @@ export class PXE {
919
955
  skipKernels = true,
920
956
  overrides,
921
957
  scopes,
958
+ senderForTags,
922
959
  }: SimulateTxOpts,
923
960
  ): Promise<TxSimulationResult> {
924
961
  // We disable concurrent simulations since those might execute oracles which read and write to the PXE stores (e.g.
@@ -941,6 +978,7 @@ export class PXE {
941
978
  );
942
979
  const syncTimer = new Timer();
943
980
  await this.blockStateSynchronizer.sync();
981
+ const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
944
982
  const syncTime = syncTimer.ms();
945
983
 
946
984
  const overriddenContracts = overrides?.contracts ? new Set(Object.keys(overrides.contracts)) : undefined;
@@ -960,7 +998,14 @@ export class PXE {
960
998
  }
961
999
 
962
1000
  // Execution of private functions only; no proving, and no kernel logic.
963
- 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
+ });
964
1009
 
965
1010
  let publicInputs: PrivateKernelTailCircuitPublicInputs | undefined;
966
1011
  let executionSteps: PrivateExecutionStep[] = [];
@@ -973,11 +1018,17 @@ export class PXE {
973
1018
  ));
974
1019
  } else {
975
1020
  // Kernel logic, plus proving of all private functions and kernels.
976
- ({ publicInputs, executionSteps } = await this.#prove(txRequest, this.proofCreator, privateExecutionResult, {
977
- simulate: true,
978
- skipFeeEnforcement,
979
- profileMode: 'none',
980
- }));
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
+ ));
981
1032
  }
982
1033
 
983
1034
  const privateSimulationResult = new PrivateSimulationResult(privateExecutionResult, publicInputs);
@@ -1180,6 +1231,7 @@ export class PXE {
1180
1231
  */
1181
1232
  public async stop(): Promise<void> {
1182
1233
  await this.jobQueue.end();
1234
+ await this.blockStateSynchronizer.stop();
1183
1235
  await this.db.close();
1184
1236
  }
1185
1237
  }
@@ -23,7 +23,7 @@ export class AnchorBlockStore {
23
23
  }
24
24
 
25
25
  async getBlockHeader(): Promise<BlockHeader> {
26
- const headerBuffer = await this.#synchronizedHeader.getAsync();
26
+ const headerBuffer = await this.#store.transactionAsync(() => this.#synchronizedHeader.getAsync());
27
27
  if (!headerBuffer) {
28
28
  throw new Error(`Trying to get block header with a not-yet-synchronized PXE - this should never happen`);
29
29
  }
@@ -148,7 +148,17 @@ export class CapsuleStore implements StagedStore {
148
148
  * @param slot - The slot in the database to read.
149
149
  * @returns The stored data or `null` if no data is stored under the slot.
150
150
  */
151
- async getCapsule(contractAddress: AztecAddress, slot: Fr, jobId: string, scope: AztecAddress): Promise<Fr[] | null> {
151
+ getCapsule(contractAddress: AztecAddress, slot: Fr, jobId: string, scope: AztecAddress): Promise<Fr[] | null> {
152
+ return this.#store.transactionAsync(() => this.#getCapsuleInternal(contractAddress, slot, jobId, scope));
153
+ }
154
+
155
+ /** Same as getCapsule but without its own transaction, for use inside an existing transactionAsync. */
156
+ async #getCapsuleInternal(
157
+ contractAddress: AztecAddress,
158
+ slot: Fr,
159
+ jobId: string,
160
+ scope: AztecAddress,
161
+ ): Promise<Fr[] | null> {
152
162
  const dataBuffer = await this.#getFromStage(jobId, dbSlotToKey(contractAddress, slot, scope));
153
163
  if (!dataBuffer) {
154
164
  this.logger.trace(`Data not found for contract ${contractAddress.toString()} and slot ${slot.toString()}`);
@@ -240,7 +250,7 @@ export class CapsuleStore implements StagedStore {
240
250
  // and not using a transaction here would heavily impact performance.
241
251
  return this.#store.transactionAsync(async () => {
242
252
  // Load current length, defaulting to 0 if not found
243
- const lengthData = await this.getCapsule(contractAddress, baseSlot, jobId, scope);
253
+ const lengthData = await this.#getCapsuleInternal(contractAddress, baseSlot, jobId, scope);
244
254
  const currentLength = lengthData ? lengthData[0].toNumber() : 0;
245
255
 
246
256
  // Store each capsule at consecutive slots after baseSlot + 1 + currentLength
@@ -263,14 +273,14 @@ export class CapsuleStore implements StagedStore {
263
273
  // of jobs: different calls running concurrently on the same contract may cause trouble.
264
274
  return this.#store.transactionAsync(async () => {
265
275
  // Load length, defaulting to 0 if not found
266
- const maybeLength = await this.getCapsule(contractAddress, baseSlot, jobId, scope);
276
+ const maybeLength = await this.#getCapsuleInternal(contractAddress, baseSlot, jobId, scope);
267
277
  const length = maybeLength ? maybeLength[0].toBigInt() : 0n;
268
278
 
269
279
  const values: Fr[][] = [];
270
280
 
271
281
  // Read each capsule at consecutive slots after baseSlot
272
282
  for (let i = 0; i < length; i++) {
273
- const currentValue = await this.getCapsule(contractAddress, arraySlot(baseSlot, i), jobId, scope);
283
+ const currentValue = await this.#getCapsuleInternal(contractAddress, arraySlot(baseSlot, i), jobId, scope);
274
284
  if (currentValue == undefined) {
275
285
  throw new Error(
276
286
  `Expected non-empty value at capsule array in base slot ${baseSlot} at index ${i} for contract ${contractAddress}`,
@@ -295,7 +305,7 @@ export class CapsuleStore implements StagedStore {
295
305
  // of jobs: different calls running concurrently on the same contract may cause trouble.
296
306
  return this.#store.transactionAsync(async () => {
297
307
  // Load current length, defaulting to 0 if not found
298
- const maybeLength = await this.getCapsule(contractAddress, baseSlot, jobId, scope);
308
+ const maybeLength = await this.#getCapsuleInternal(contractAddress, baseSlot, jobId, scope);
299
309
  const originalLength = maybeLength ? maybeLength[0].toNumber() : 0;
300
310
 
301
311
  // Set the new length
@@ -168,12 +168,14 @@ export class ContractStore {
168
168
  }
169
169
 
170
170
  async addContractInstance(contract: ContractInstanceWithAddress): Promise<void> {
171
- this.#contractClassIdMap.set(contract.address.toString(), contract.currentContractClassId);
171
+ await this.#store.transactionAsync(async () => {
172
+ await this.#contractInstances.set(
173
+ contract.address.toString(),
174
+ new SerializableContractInstance(contract).toBuffer(),
175
+ );
176
+ });
172
177
 
173
- await this.#contractInstances.set(
174
- contract.address.toString(),
175
- new SerializableContractInstance(contract).toBuffer(),
176
- );
178
+ this.#contractClassIdMap.set(contract.address.toString(), contract.currentContractClassId);
177
179
  }
178
180
 
179
181
  // Private getters
@@ -246,7 +248,7 @@ export class ContractStore {
246
248
  contractClassId: Fr,
247
249
  ): Promise<(ContractClassWithId & ContractClassIdPreimage) | undefined> {
248
250
  const key = contractClassId.toString();
249
- const buf = await this.#contractClassData.getAsync(key);
251
+ const buf = await this.#store.transactionAsync(() => this.#contractClassData.getAsync(key));
250
252
  if (!buf) {
251
253
  return undefined;
252
254
  }
@@ -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
 
@@ -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);