@aztec/pxe 0.0.1-commit.fff30aa → 0.0.1-dev

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 (162) hide show
  1. package/dest/bin/check_oracle_version.js +1 -1
  2. package/dest/block_synchronizer/block_synchronizer.d.ts +5 -3
  3. package/dest/block_synchronizer/block_synchronizer.d.ts.map +1 -1
  4. package/dest/block_synchronizer/block_synchronizer.js +10 -2
  5. package/dest/config/package_info.js +1 -1
  6. package/dest/contract_function_simulator/contract_function_simulator.d.ts +4 -1
  7. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  8. package/dest/contract_function_simulator/contract_function_simulator.js +18 -10
  9. package/dest/contract_function_simulator/oracle/interfaces.d.ts +5 -2
  10. package/dest/contract_function_simulator/oracle/interfaces.d.ts.map +1 -1
  11. package/dest/contract_function_simulator/oracle/oracle.d.ts +4 -1
  12. package/dest/contract_function_simulator/oracle/oracle.d.ts.map +1 -1
  13. package/dest/contract_function_simulator/oracle/oracle.js +53 -1
  14. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +10 -14
  15. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  16. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +16 -14
  17. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +19 -2
  18. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  19. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +108 -5
  20. package/dest/contract_sync/contract_sync_service.d.ts +2 -1
  21. package/dest/contract_sync/contract_sync_service.d.ts.map +1 -1
  22. package/dest/contract_sync/contract_sync_service.js +41 -7
  23. package/dest/contract_sync/helpers.d.ts +2 -3
  24. package/dest/contract_sync/helpers.d.ts.map +1 -1
  25. package/dest/contract_sync/helpers.js +12 -19
  26. package/dest/entrypoints/client/bundle/utils.d.ts +1 -1
  27. package/dest/entrypoints/client/bundle/utils.d.ts.map +1 -1
  28. package/dest/entrypoints/client/bundle/utils.js +2 -1
  29. package/dest/entrypoints/client/lazy/utils.d.ts +1 -1
  30. package/dest/entrypoints/client/lazy/utils.d.ts.map +1 -1
  31. package/dest/entrypoints/client/lazy/utils.js +2 -1
  32. package/dest/entrypoints/pxe_creation_options.d.ts +4 -1
  33. package/dest/entrypoints/pxe_creation_options.d.ts.map +1 -1
  34. package/dest/entrypoints/server/index.d.ts +2 -1
  35. package/dest/entrypoints/server/index.d.ts.map +1 -1
  36. package/dest/entrypoints/server/index.js +1 -0
  37. package/dest/entrypoints/server/utils.d.ts +1 -1
  38. package/dest/entrypoints/server/utils.d.ts.map +1 -1
  39. package/dest/entrypoints/server/utils.js +2 -1
  40. package/dest/events/event_service.d.ts +13 -5
  41. package/dest/events/event_service.d.ts.map +1 -1
  42. package/dest/events/event_service.js +28 -7
  43. package/dest/hooks/authorize_utility_call.d.ts +37 -0
  44. package/dest/hooks/authorize_utility_call.d.ts.map +1 -0
  45. package/dest/hooks/authorize_utility_call.js +4 -0
  46. package/dest/hooks/execution_hooks.d.ts +42 -0
  47. package/dest/hooks/execution_hooks.d.ts.map +1 -0
  48. package/dest/hooks/execution_hooks.js +9 -0
  49. package/dest/hooks/index.d.ts +4 -0
  50. package/dest/hooks/index.d.ts.map +1 -0
  51. package/dest/hooks/index.js +1 -0
  52. package/dest/logs/log_service.d.ts +1 -1
  53. package/dest/logs/log_service.d.ts.map +1 -1
  54. package/dest/logs/log_service.js +20 -35
  55. package/dest/messages/message_context_service.d.ts +1 -1
  56. package/dest/messages/message_context_service.d.ts.map +1 -1
  57. package/dest/messages/message_context_service.js +12 -10
  58. package/dest/notes/note_service.d.ts +25 -3
  59. package/dest/notes/note_service.d.ts.map +1 -1
  60. package/dest/notes/note_service.js +80 -65
  61. package/dest/oracle_version.d.ts +2 -2
  62. package/dest/oracle_version.js +2 -2
  63. package/dest/private_kernel/private_kernel_execution_prover.d.ts +2 -1
  64. package/dest/private_kernel/private_kernel_execution_prover.d.ts.map +1 -1
  65. package/dest/private_kernel/private_kernel_execution_prover.js +18 -4
  66. package/dest/private_kernel/private_kernel_oracle.d.ts +1 -1
  67. package/dest/private_kernel/private_kernel_oracle.d.ts.map +1 -1
  68. package/dest/private_kernel/private_kernel_oracle.js +7 -2
  69. package/dest/pxe.d.ts +20 -5
  70. package/dest/pxe.d.ts.map +1 -1
  71. package/dest/pxe.js +61 -59
  72. package/dest/storage/backwards_compatibility_tests/kv_store_snapshot.d.ts +42 -0
  73. package/dest/storage/backwards_compatibility_tests/kv_store_snapshot.d.ts.map +1 -0
  74. package/dest/storage/backwards_compatibility_tests/kv_store_snapshot.js +93 -0
  75. package/dest/storage/backwards_compatibility_tests/schema_tests.d.ts +15 -0
  76. package/dest/storage/backwards_compatibility_tests/schema_tests.d.ts.map +1 -0
  77. package/dest/storage/backwards_compatibility_tests/schema_tests.js +588 -0
  78. package/dest/storage/backwards_compatibility_tests/store_spy.d.ts +19 -0
  79. package/dest/storage/backwards_compatibility_tests/store_spy.d.ts.map +1 -0
  80. package/dest/storage/backwards_compatibility_tests/store_spy.js +63 -0
  81. package/dest/storage/open_pxe_stores.d.ts +31 -0
  82. package/dest/storage/open_pxe_stores.d.ts.map +1 -0
  83. package/dest/storage/open_pxe_stores.js +26 -0
  84. package/dest/tagging/get_all_logs_by_tags.d.ts +1 -1
  85. package/dest/tagging/get_all_logs_by_tags.d.ts.map +1 -1
  86. package/dest/tagging/get_all_logs_by_tags.js +3 -0
  87. package/dest/tagging/index.d.ts +4 -3
  88. package/dest/tagging/index.d.ts.map +1 -1
  89. package/dest/tagging/index.js +3 -2
  90. package/dest/tagging/persist_sender_tagging_index_ranges.d.ts +29 -0
  91. package/dest/tagging/persist_sender_tagging_index_ranges.d.ts.map +1 -0
  92. package/dest/tagging/persist_sender_tagging_index_ranges.js +42 -0
  93. package/dest/tagging/recipient_sync/sync_tagged_private_logs.d.ts +56 -0
  94. package/dest/tagging/recipient_sync/sync_tagged_private_logs.d.ts.map +1 -0
  95. package/dest/tagging/recipient_sync/sync_tagged_private_logs.js +158 -0
  96. package/dest/tagging/reconcile_tagging_index_ranges.d.ts +36 -0
  97. package/dest/tagging/reconcile_tagging_index_ranges.d.ts.map +1 -0
  98. package/dest/tagging/reconcile_tagging_index_ranges.js +74 -0
  99. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts +1 -1
  100. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts.map +1 -1
  101. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.js +19 -8
  102. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.d.ts +11 -6
  103. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.d.ts.map +1 -1
  104. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.js +21 -0
  105. package/package.json +17 -16
  106. package/src/bin/check_oracle_version.ts +1 -1
  107. package/src/block_synchronizer/block_synchronizer.ts +11 -3
  108. package/src/config/package_info.ts +1 -1
  109. package/src/contract_function_simulator/contract_function_simulator.ts +32 -9
  110. package/src/contract_function_simulator/oracle/interfaces.ts +12 -1
  111. package/src/contract_function_simulator/oracle/oracle.ts +73 -1
  112. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +25 -21
  113. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +165 -29
  114. package/src/contract_sync/contract_sync_service.ts +55 -23
  115. package/src/contract_sync/helpers.ts +11 -23
  116. package/src/entrypoints/client/bundle/utils.ts +1 -0
  117. package/src/entrypoints/client/lazy/utils.ts +1 -0
  118. package/src/entrypoints/pxe_creation_options.ts +4 -0
  119. package/src/entrypoints/server/index.ts +1 -0
  120. package/src/entrypoints/server/utils.ts +1 -0
  121. package/src/events/event_service.ts +52 -17
  122. package/src/hooks/authorize_utility_call.ts +40 -0
  123. package/src/hooks/execution_hooks.ts +48 -0
  124. package/src/hooks/index.ts +7 -0
  125. package/src/logs/log_service.ts +46 -71
  126. package/src/messages/message_context_service.ts +25 -24
  127. package/src/notes/note_service.ts +115 -91
  128. package/src/oracle_version.ts +2 -2
  129. package/src/private_kernel/private_kernel_execution_prover.ts +29 -3
  130. package/src/private_kernel/private_kernel_oracle.ts +15 -5
  131. package/src/pxe.ts +116 -53
  132. package/src/storage/backwards_compatibility_tests/__snapshots__/AddressStore.json +22 -0
  133. package/src/storage/backwards_compatibility_tests/__snapshots__/AnchorBlockStore.json +3 -0
  134. package/src/storage/backwards_compatibility_tests/__snapshots__/CapsuleStore.json +16 -0
  135. package/src/storage/backwards_compatibility_tests/__snapshots__/ContractStore.json +28 -0
  136. package/src/storage/backwards_compatibility_tests/__snapshots__/KeyStore.json +52 -0
  137. package/src/storage/backwards_compatibility_tests/__snapshots__/L2TipsKVStore.json +42 -0
  138. package/src/storage/backwards_compatibility_tests/__snapshots__/NoteStore.json +36 -0
  139. package/src/storage/backwards_compatibility_tests/__snapshots__/PrivateEventStore.json +44 -0
  140. package/src/storage/backwards_compatibility_tests/__snapshots__/RecipientTaggingStore.json +18 -0
  141. package/src/storage/backwards_compatibility_tests/__snapshots__/SenderAddressBookStore.json +16 -0
  142. package/src/storage/backwards_compatibility_tests/__snapshots__/SenderTaggingStore.json +22 -0
  143. package/src/storage/backwards_compatibility_tests/__snapshots__/opened_stores.json +97 -0
  144. package/src/storage/backwards_compatibility_tests/kv_store_snapshot.ts +122 -0
  145. package/src/storage/backwards_compatibility_tests/schema_tests.ts +707 -0
  146. package/src/storage/backwards_compatibility_tests/store_spy.ts +73 -0
  147. package/src/storage/open_pxe_stores.ts +47 -0
  148. package/src/tagging/get_all_logs_by_tags.ts +4 -0
  149. package/src/tagging/index.ts +3 -2
  150. package/src/tagging/persist_sender_tagging_index_ranges.ts +57 -0
  151. package/src/tagging/recipient_sync/sync_tagged_private_logs.ts +236 -0
  152. package/src/tagging/reconcile_tagging_index_ranges.ts +102 -0
  153. package/src/tagging/sender_sync/sync_sender_tagging_indexes.ts +33 -10
  154. package/src/tagging/sender_sync/utils/get_status_change_of_pending.ts +23 -8
  155. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts +0 -14
  156. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.d.ts.map +0 -1
  157. package/dest/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.js +0 -85
  158. package/dest/tagging/recipient_sync/utils/load_logs_for_range.d.ts +0 -14
  159. package/dest/tagging/recipient_sync/utils/load_logs_for_range.d.ts.map +0 -1
  160. package/dest/tagging/recipient_sync/utils/load_logs_for_range.js +0 -33
  161. package/src/tagging/recipient_sync/load_private_logs_for_sender_recipient_pair.ts +0 -130
  162. package/src/tagging/recipient_sync/utils/load_logs_for_range.ts +0 -44
@@ -1,5 +1,6 @@
1
1
  import type { ARCHIVE_HEIGHT, NOTE_HASH_TREE_HEIGHT } from '@aztec/constants';
2
2
  import type { BlockNumber } from '@aztec/foundation/branded-types';
3
+ import { uniqueBy } from '@aztec/foundation/collection';
3
4
  import { Aes128 } from '@aztec/foundation/crypto/aes128';
4
5
  import { Fr } from '@aztec/foundation/curves/bn254';
5
6
  import { Point } from '@aztec/foundation/curves/grumpkin';
@@ -7,6 +8,15 @@ import { LogLevels, type Logger, createLogger } from '@aztec/foundation/log';
7
8
  import type { MembershipWitness } from '@aztec/foundation/trees';
8
9
  import type { KeyStore } from '@aztec/key-store';
9
10
  import { isProtocolContract } from '@aztec/protocol-contracts';
11
+ import {
12
+ type CircuitSimulator,
13
+ ExecutionError,
14
+ extractCallStack,
15
+ resolveAssertionMessageFromError,
16
+ toACVMWitness,
17
+ witnessMapToFields,
18
+ } from '@aztec/simulator/client';
19
+ import { type FunctionCall, FunctionSelector } from '@aztec/stdlib/abi';
10
20
  import type { AuthWitness } from '@aztec/stdlib/auth-witness';
11
21
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
12
22
  import { BlockHash, type L2TipsProvider } from '@aztec/stdlib/block';
@@ -19,11 +29,12 @@ import { MessageContext, deriveAppSiloedSharedSecret } from '@aztec/stdlib/logs'
19
29
  import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging';
20
30
  import type { NoteStatus } from '@aztec/stdlib/note';
21
31
  import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
22
- import type { BlockHeader, Capsule, OffchainEffect } from '@aztec/stdlib/tx';
32
+ import type { BlockHeader, Capsule, IndexedTxEffect, OffchainEffect, TxEffect, TxHash } from '@aztec/stdlib/tx';
23
33
 
24
34
  import { createContractLogger, logContractMessage, stripAztecnrLogPrefix } from '../../contract_logging.js';
25
35
  import type { ContractSyncService } from '../../contract_sync/contract_sync_service.js';
26
36
  import { EventService } from '../../events/event_service.js';
37
+ import type { ExecutionHooks } from '../../hooks/index.js';
27
38
  import { LogService } from '../../logs/log_service.js';
28
39
  import { MessageContextService } from '../../messages/message_context_service.js';
29
40
  import { NoteService } from '../../notes/note_service.js';
@@ -44,6 +55,7 @@ import { UtilityContext } from '../noir-structs/utility_context.js';
44
55
  import { pickNotes } from '../pick_notes.js';
45
56
  import type { IMiscOracle, IUtilityExecutionOracle, NoteData } from './interfaces.js';
46
57
  import { MessageLoadOracleInputs } from './message_load_oracle_inputs.js';
58
+ import { Oracle } from './oracle.js';
47
59
 
48
60
  /** Args for UtilityExecutionOracle constructor. */
49
61
  export type UtilityExecutionOracleArgs = {
@@ -67,6 +79,10 @@ export type UtilityExecutionOracleArgs = {
67
79
  jobId: string;
68
80
  log?: ReturnType<typeof createLogger>;
69
81
  scopes: AztecAddress[];
82
+ simulator: CircuitSimulator;
83
+ hooks?: ExecutionHooks;
84
+ /** Needed to trigger contract synchronization before nested cross-contract calls. */
85
+ utilityExecutor: (call: FunctionCall, scopes: AztecAddress[]) => Promise<void>;
70
86
  };
71
87
 
72
88
  /**
@@ -103,6 +119,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
103
119
  protected readonly jobId: string;
104
120
  protected logger: ReturnType<typeof createLogger>;
105
121
  protected readonly scopes: AztecAddress[];
122
+ protected readonly simulator: CircuitSimulator;
123
+ protected readonly hooks: ExecutionHooks | undefined;
124
+ protected readonly utilityExecutor: (call: FunctionCall, scopes: AztecAddress[]) => Promise<void>;
106
125
 
107
126
  constructor(args: UtilityExecutionOracleArgs) {
108
127
  this.contractAddress = args.contractAddress;
@@ -124,6 +143,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
124
143
  this.jobId = args.jobId;
125
144
  this.logger = args.log ?? createLogger('simulator:client_view_context');
126
145
  this.scopes = args.scopes;
146
+ this.simulator = args.simulator;
147
+ this.hooks = args.hooks;
148
+ this.utilityExecutor = args.utilityExecutor;
127
149
  }
128
150
 
129
151
  public assertCompatibleOracleVersion(major: number, minor: number): void {
@@ -289,6 +311,11 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
289
311
  throw new Error(`Block number ${blockNumber} is higher than current block ${anchorBlockNumber}`);
290
312
  }
291
313
 
314
+ // Most contracts query state at the "current" block, which is the anchor. Skip the RPC when we can.
315
+ if (blockNumber === anchorBlockNumber) {
316
+ return this.anchorBlockHeader;
317
+ }
318
+
292
319
  const block = await this.aztecNode.getBlock(blockNumber);
293
320
  return block?.header;
294
321
  }
@@ -626,36 +653,18 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
626
653
  eventValidationRequests: EventValidationRequest[],
627
654
  scope: AztecAddress,
628
655
  ) {
629
- const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockHeader, this.jobId);
630
- const noteStorePromises = noteValidationRequests.map(request =>
631
- noteService.validateAndStoreNote(
632
- request.contractAddress,
633
- request.owner,
634
- request.storageSlot,
635
- request.randomness,
636
- request.noteNonce,
637
- request.content,
638
- request.noteHash,
639
- request.nullifier,
640
- request.txHash,
641
- scope,
642
- ),
643
- );
656
+ const txEffects = await this.#fetchTxEffects([
657
+ ...noteValidationRequests.map(r => r.txHash),
658
+ ...eventValidationRequests.map(r => r.txHash),
659
+ ]);
644
660
 
661
+ const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockHeader, this.jobId);
645
662
  const eventService = new EventService(this.anchorBlockHeader, this.aztecNode, this.privateEventStore, this.jobId);
646
- const eventStorePromises = eventValidationRequests.map(request =>
647
- eventService.validateAndStoreEvent(
648
- request.contractAddress,
649
- request.eventTypeId,
650
- request.randomness,
651
- request.serializedEvent,
652
- request.eventCommitment,
653
- request.txHash,
654
- scope,
655
- ),
656
- );
657
663
 
658
- await Promise.all([...noteStorePromises, ...eventStorePromises]);
664
+ await Promise.all([
665
+ noteService.validateAndStoreNotes(noteValidationRequests, scope, txEffects),
666
+ eventService.validateAndStoreEvents(eventValidationRequests, scope, txEffects),
667
+ ]);
659
668
  }
660
669
 
661
670
  public async getLogsByTag(
@@ -785,6 +794,23 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
785
794
  return this.ephemeralArrayService.newArray(maybeMessageContexts.map(MessageContext.toSerializedOption));
786
795
  }
787
796
 
797
+ /**
798
+ * Fetches the effects of a transaction by its hash. Returns null if the tx is not found or is beyond the anchor
799
+ * block.
800
+ */
801
+ public async getTxEffect(txHash: TxHash): Promise<TxEffect | null> {
802
+ if (txHash.hash.isZero()) {
803
+ throw new Error('Invalid tx hash passed into aztec_utl_getTxEffect oracle handler');
804
+ }
805
+
806
+ const txEffect = await this.aztecNode.getTxEffect(txHash);
807
+ if (!txEffect || txEffect.l2BlockNumber > this.anchorBlockHeader.getBlockNumber()) {
808
+ return null;
809
+ }
810
+
811
+ return txEffect.data;
812
+ }
813
+
788
814
  public setCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[], scope: AztecAddress): void {
789
815
  if (!contractAddress.equals(this.contractAddress)) {
790
816
  // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB
@@ -894,13 +920,123 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
894
920
  return Promise.resolve();
895
921
  }
896
922
 
923
+ /** Executes another utility function from within this one and returns its serialized return values. */
924
+ public async callUtilityFunction(
925
+ targetContractAddress: AztecAddress,
926
+ functionSelector: FunctionSelector,
927
+ args: Fr[],
928
+ ): Promise<Fr[]> {
929
+ const targetArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(
930
+ targetContractAddress,
931
+ functionSelector,
932
+ );
933
+
934
+ if (!targetContractAddress.equals(this.contractAddress)) {
935
+ const request = {
936
+ caller: this.contractAddress,
937
+ target: targetContractAddress,
938
+ functionSelector,
939
+ functionName: targetArtifact.name,
940
+ args,
941
+ callerContext: ('isPrivate' in this ? 'private' : 'utility') as 'private' | 'utility',
942
+ };
943
+
944
+ const response = this.hooks
945
+ ? await this.hooks.authorizeUtilityCall(request)
946
+ : { authorized: false, reason: 'No execution hooks configured' };
947
+
948
+ if (!response.authorized) {
949
+ const reason = response.reason ? `: ${response.reason}` : '';
950
+ throw new Error(
951
+ `Cross-contract utility call denied${reason}. ${this.contractAddress} attempted to call ` +
952
+ `${targetContractAddress}:${functionSelector} (${targetArtifact.name}). ` +
953
+ `See https://docs.aztec.network/errors/11`,
954
+ );
955
+ }
956
+
957
+ await this.contractSyncService.ensureContractSynced(
958
+ targetContractAddress,
959
+ functionSelector,
960
+ this.utilityExecutor,
961
+ this.anchorBlockHeader,
962
+ this.jobId,
963
+ this.scopes,
964
+ );
965
+ }
966
+
967
+ this.logger.debug(
968
+ `Calling nested utility function ${targetContractAddress}:${functionSelector} from ${this.contractAddress}`,
969
+ );
970
+
971
+ const nestedOracle = new UtilityExecutionOracle({
972
+ contractAddress: targetContractAddress,
973
+ authWitnesses: this.authWitnesses,
974
+ capsules: this.capsules,
975
+ anchorBlockHeader: this.anchorBlockHeader,
976
+ contractStore: this.contractStore,
977
+ noteStore: this.noteStore,
978
+ keyStore: this.keyStore,
979
+ addressStore: this.addressStore,
980
+ aztecNode: this.aztecNode,
981
+ recipientTaggingStore: this.recipientTaggingStore,
982
+ senderAddressBookStore: this.senderAddressBookStore,
983
+ capsuleService: this.capsuleService,
984
+ privateEventStore: this.privateEventStore,
985
+ messageContextService: this.messageContextService,
986
+ contractSyncService: this.contractSyncService,
987
+ l2TipsStore: this.l2TipsStore,
988
+ jobId: this.jobId,
989
+ scopes: this.scopes,
990
+ simulator: this.simulator,
991
+ hooks: this.hooks,
992
+ utilityExecutor: this.utilityExecutor,
993
+ log: this.logger,
994
+ });
995
+
996
+ const initialWitness = toACVMWitness(0, args);
997
+ const acvmCallback = new Oracle(nestedOracle);
998
+ const acirExecutionResult = await this.simulator
999
+ .executeUserCircuit(initialWitness, targetArtifact, acvmCallback.toACIRCallback())
1000
+ .catch((err: Error) => {
1001
+ err.message = resolveAssertionMessageFromError(err, targetArtifact);
1002
+ throw new ExecutionError(
1003
+ err.message,
1004
+ { contractAddress: targetContractAddress, functionSelector },
1005
+ extractCallStack(err, targetArtifact.debug),
1006
+ { cause: err },
1007
+ );
1008
+ });
1009
+
1010
+ return witnessMapToFields(acirExecutionResult.returnWitness);
1011
+ }
1012
+
897
1013
  /** Returns offchain effects collected during execution. */
898
1014
  public getOffchainEffects(): OffchainEffect[] {
899
1015
  return this.offchainEffects;
900
1016
  }
901
1017
 
1018
+ /**
1019
+ * Fetches tx effects for the given hashes in parallel, deduplicating repeated hashes so each tx is only requested
1020
+ * once. Returns a map keyed by `TxHash.toString()`; hashes for which the node has no tx effect are omitted.
1021
+ */
1022
+ async #fetchTxEffects(txHashes: TxHash[]): Promise<Map<string, IndexedTxEffect>> {
1023
+ const uniqueTxHashes = uniqueBy(txHashes, h => h.toString());
1024
+ const fetched = await Promise.all(uniqueTxHashes.map(h => this.aztecNode.getTxEffect(h)));
1025
+ return new Map(
1026
+ uniqueTxHashes
1027
+ .map((h, i): [string, IndexedTxEffect | undefined] => [h.toString(), fetched[i]])
1028
+ .filter((entry): entry is [string, IndexedTxEffect] => entry[1] !== undefined),
1029
+ );
1030
+ }
1031
+
902
1032
  /** Runs a query concurrently with a validation that the block hash is not ahead of the anchor block. */
903
1033
  async #queryWithBlockHashNotAfterAnchor<T>(blockHash: BlockHash, query: () => Promise<T>): Promise<T> {
1034
+ // Most contracts query state at the "current" block, which is the anchor. Skip the validation when we can.
1035
+ const anchorHash = await this.anchorBlockHeader.hash();
1036
+ if (blockHash.equals(anchorHash)) {
1037
+ return query();
1038
+ }
1039
+
904
1040
  const [response] = await Promise.all([
905
1041
  query(),
906
1042
  (async () => {
@@ -911,7 +1047,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
911
1047
 
912
1048
  if (header.getBlockNumber() > this.anchorBlockHeader.getBlockNumber()) {
913
1049
  throw new Error(
914
- `Made a node query with a reference block hash ${blockHash} with block number ${header.getBlockNumber()}, which is ahead of the anchor block number ${this.anchorBlockHeader.getBlockNumber()} (from anchor block hash ${await this.anchorBlockHeader.hash()}).`,
1050
+ `Made a node query with a reference block hash ${blockHash} with block number ${header.getBlockNumber()}, which is ahead of the anchor block number ${this.anchorBlockHeader.getBlockNumber()} (from anchor block hash ${anchorHash}).`,
915
1051
  );
916
1052
  }
917
1053
  })(),
@@ -1,14 +1,16 @@
1
1
  import type { Logger } from '@aztec/foundation/log';
2
2
  import { Semaphore } from '@aztec/foundation/queue';
3
+ import { isProtocolContract } from '@aztec/protocol-contracts';
3
4
  import type { FunctionCall, FunctionSelector } from '@aztec/stdlib/abi';
4
5
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
6
  import type { AztecNode } from '@aztec/stdlib/interfaces/client';
6
7
  import type { BlockHeader } from '@aztec/stdlib/tx';
7
8
 
8
9
  import type { StagedStore } from '../job_coordinator/job_coordinator.js';
10
+ import { NoteService } from '../notes/note_service.js';
9
11
  import type { ContractStore } from '../storage/contract_store/contract_store.js';
10
12
  import type { NoteStore } from '../storage/note_store/note_store.js';
11
- import { syncState, verifyCurrentClassId } from './helpers.js';
13
+ import { syncScope, verifyCurrentClassId } from './helpers.js';
12
14
 
13
15
  /** Maximum number of scope syncs running concurrently across the PXE. */
14
16
  const MAX_CONCURRENT_SCOPE_SYNCS = 5;
@@ -27,6 +29,10 @@ export class ContractSyncService implements StagedStore {
27
29
  // The value is a promise that resolves when the contract is synced.
28
30
  private syncedContracts: Map<string, Promise<void>> = new Map();
29
31
 
32
+ // Tracks class ID verification per contract. Keyed by contract address only (no scope), since
33
+ // class ID verification is scope-independent. Cleared on wipe/discard.
34
+ private classIdVerificationCache: Map<string, Promise<void>> = new Map();
35
+
30
36
  // Per-job excluded contract addresses - these contracts should not be synced.
31
37
  private excludedFromSync: Map<string, Set<string>> = new Map();
32
38
 
@@ -68,22 +74,8 @@ export class ContractSyncService implements StagedStore {
68
74
  return;
69
75
  }
70
76
 
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
- ),
77
+ this.#startSyncIfNeeded(contractAddress, scopes, anchorBlockHeader, jobId, scope =>
78
+ syncScope(contractAddress, this.contractStore, functionToInvokeAfterSync, utilityExecutor, scope),
87
79
  );
88
80
 
89
81
  await this.#awaitSync(contractAddress, scopes);
@@ -101,6 +93,7 @@ export class ContractSyncService implements StagedStore {
101
93
  wipe(): void {
102
94
  this.log.debug(`Wiping contract sync cache (${this.syncedContracts.size} entries)`);
103
95
  this.syncedContracts.clear();
96
+ this.classIdVerificationCache.clear();
104
97
  }
105
98
 
106
99
  commit(jobId: string): Promise<void> {
@@ -113,6 +106,7 @@ export class ContractSyncService implements StagedStore {
113
106
  // We clear the synced contracts cache here because, when the job is discarded, any associated database writes from
114
107
  // the sync are also undone.
115
108
  this.syncedContracts.clear();
109
+ this.classIdVerificationCache.clear();
116
110
  this.excludedFromSync.delete(jobId);
117
111
  return Promise.resolve();
118
112
  }
@@ -122,14 +116,16 @@ export class ContractSyncService implements StagedStore {
122
116
  }
123
117
 
124
118
  /**
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.
119
+ * For each unsynced scope, creates a promise that waits on:
120
+ * 1. Class ID verification (cached per contract, scope-independent).
121
+ * 2. Note nullifier sync (shared, batched across all unsynced scopes).
122
+ * 3. Per-scope sync (individual, semaphore-bounded).
128
123
  */
129
124
  #startSyncIfNeeded(
130
125
  contractAddress: AztecAddress,
131
126
  scopes: AztecAddress[],
132
- verifyFn: () => Promise<void>,
127
+ anchorBlockHeader: BlockHeader,
128
+ jobId: string,
133
129
  syncScopeFn: (scope: AztecAddress) => Promise<void>,
134
130
  ): void {
135
131
  const scopesToSync = scopes.filter(scope => !this.syncedContracts.has(toKey(contractAddress, scope)));
@@ -138,11 +134,13 @@ export class ContractSyncService implements StagedStore {
138
134
  }
139
135
 
140
136
  this.log.debug(`Syncing contract ${contractAddress} for ${scopesToSync.length} scope(s)`);
141
- const verifyPromise = verifyFn();
137
+
138
+ const verifyPromise = this.#verifyClassId(contractAddress, anchorBlockHeader);
139
+ const syncNullifiersPromise = this.#syncNoteNullifiers(contractAddress, anchorBlockHeader, jobId, scopesToSync);
142
140
 
143
141
  for (const scope of scopesToSync) {
144
142
  const key = toKey(contractAddress, scope);
145
- const promise = Promise.all([verifyPromise, this.#runBounded(() => syncScopeFn(scope))])
143
+ const promise = Promise.all([verifyPromise, syncNullifiersPromise, this.#runBounded(() => syncScopeFn(scope))])
146
144
  .then(() => {})
147
145
  .catch(err => {
148
146
  this.syncedContracts.delete(key);
@@ -152,6 +150,40 @@ export class ContractSyncService implements StagedStore {
152
150
  }
153
151
  }
154
152
 
153
+ /** Verifies the local class ID matches the on-chain value (cached, evicts on failure so retries re-verify). */
154
+ #verifyClassId(contractAddress: AztecAddress, anchorBlockHeader: BlockHeader): Promise<void> {
155
+ const contractKey = contractAddress.toString();
156
+ const cached = this.classIdVerificationCache.get(contractKey);
157
+ if (cached) {
158
+ return cached;
159
+ }
160
+ const promise = verifyCurrentClassId(contractAddress, this.aztecNode, this.contractStore, anchorBlockHeader).catch(
161
+ err => {
162
+ this.classIdVerificationCache.delete(contractKey);
163
+ throw err;
164
+ },
165
+ );
166
+ this.classIdVerificationCache.set(contractKey, promise);
167
+ return promise;
168
+ }
169
+
170
+ /** Syncs note nullifiers across all unsynced scopes in a single batched call. */
171
+ async #syncNoteNullifiers(
172
+ contractAddress: AztecAddress,
173
+ anchorBlockHeader: BlockHeader,
174
+ jobId: string,
175
+ scopes: AztecAddress[],
176
+ ): Promise<void> {
177
+ // Protocol contracts don't have private state to sync
178
+ if (isProtocolContract(contractAddress)) {
179
+ return;
180
+ }
181
+ // This runs in parallel with per-scope sync (which also writes to the note store). That's safe because
182
+ // the note store handles concurrent operations.
183
+ const noteService = new NoteService(this.noteStore, this.aztecNode, anchorBlockHeader, jobId);
184
+ await noteService.syncNoteNullifiers(contractAddress, scopes);
185
+ }
186
+
155
187
  /** Runs fn while holding a slot in #syncSlot, bounding total concurrent scope syncs. */
156
188
  async #runBounded<T>(fn: () => Promise<T>): Promise<T> {
157
189
  await this.#syncSlot.acquire();
@@ -6,9 +6,7 @@ import { DelayedPublicMutableValues, DelayedPublicMutableValuesWithHash } from '
6
6
  import type { AztecNode } from '@aztec/stdlib/interfaces/client';
7
7
  import type { BlockHeader } from '@aztec/stdlib/tx';
8
8
 
9
- import { NoteService } from '../notes/note_service.js';
10
9
  import type { ContractStore } from '../storage/contract_store/contract_store.js';
11
- import type { NoteStore } from '../storage/note_store/note_store.js';
12
10
 
13
11
  /**
14
12
  * Read the current class id of a contract from the execution data provider or AztecNode. If not found, class id
@@ -38,36 +36,26 @@ export async function readCurrentClassId(
38
36
  return currentClassId;
39
37
  }
40
38
 
41
- export async function syncState(
39
+ export async function syncScope(
42
40
  contractAddress: AztecAddress,
43
41
  contractStore: ContractStore,
44
42
  functionToInvokeAfterSync: FunctionSelector | null,
45
43
  utilityExecutor: (privateSyncCall: FunctionCall, scopes: AztecAddress[]) => Promise<any>,
46
- noteStore: NoteStore,
47
- aztecNode: AztecNode,
48
- anchorBlockHeader: BlockHeader,
49
- jobId: string,
50
44
  scope: AztecAddress,
51
45
  ) {
52
46
  // Protocol contracts don't have private state to sync
53
- if (!isProtocolContract(contractAddress)) {
54
- const syncStateFunctionCall = await contractStore.getFunctionCall('sync_state', [scope], contractAddress);
55
- if (functionToInvokeAfterSync && functionToInvokeAfterSync.equals(syncStateFunctionCall.selector)) {
56
- throw new Error(
57
- 'Forbidden `sync_state` invocation. `sync_state` can only be invoked by PXE, manual execution can lead to inconsistencies.',
58
- );
59
- }
60
-
61
- const noteService = new NoteService(noteStore, aztecNode, anchorBlockHeader, jobId);
62
- const scopes: AztecAddress[] = [scope];
47
+ if (isProtocolContract(contractAddress)) {
48
+ return;
49
+ }
63
50
 
64
- // Both sync_state and syncNoteNullifiers interact with the note store, but running them in parallel is safe
65
- // because note store is designed to handle concurrent operations.
66
- await Promise.all([
67
- utilityExecutor(syncStateFunctionCall, scopes),
68
- noteService.syncNoteNullifiers(contractAddress, scopes),
69
- ]);
51
+ const syncStateFunctionCall = await contractStore.getFunctionCall('sync_state', [scope], contractAddress);
52
+ if (functionToInvokeAfterSync && functionToInvokeAfterSync.equals(syncStateFunctionCall.selector)) {
53
+ throw new Error(
54
+ 'Forbidden `sync_state` invocation. `sync_state` can only be invoked by PXE, manual execution can lead to inconsistencies.',
55
+ );
70
56
  }
57
+
58
+ await utilityExecutor(syncStateFunctionCall, [scope]);
71
59
  }
72
60
 
73
61
  /**
@@ -59,6 +59,7 @@ export async function createPXE(
59
59
  protocolContractsProvider,
60
60
  config,
61
61
  loggerOrSuffix: pxeLogger,
62
+ hooks: options.hooks,
62
63
  });
63
64
  return pxe;
64
65
  }
@@ -59,6 +59,7 @@ export async function createPXE(
59
59
  protocolContractsProvider,
60
60
  config,
61
61
  loggerOrSuffix: pxeLogger,
62
+ hooks: options.hooks,
62
63
  });
63
64
  return pxe;
64
65
  }
@@ -4,6 +4,8 @@ import type { AztecAsyncKVStore } from '@aztec/kv-store';
4
4
  import type { CircuitSimulator } from '@aztec/simulator/client';
5
5
  import type { PrivateKernelProver } from '@aztec/stdlib/interfaces/client';
6
6
 
7
+ import type { ExecutionHooks } from '../hooks/index.js';
8
+
7
9
  export type PXECreationOptions = {
8
10
  loggers?: { store?: Logger; pxe?: Logger; prover?: Logger };
9
11
  /** Actor label to include in log output (e.g., 'pxe-0', 'pxe-test'). */
@@ -11,6 +13,8 @@ export type PXECreationOptions = {
11
13
  proverOrOptions?: PrivateKernelProver | BBPrivateKernelProverOptions;
12
14
  store?: AztecAsyncKVStore;
13
15
  simulator?: CircuitSimulator;
16
+ /** Optional hooks to observe and influence contract execution. */
17
+ hooks?: ExecutionHooks;
14
18
  };
15
19
 
16
20
  /** Checks if the given value implements the PrivateKernelProver interface via duck-typing. */
@@ -1,5 +1,6 @@
1
1
  export * from '../../notes_filter.js';
2
2
  export * from '../../pxe.js';
3
+ export * from '../../hooks/index.js';
3
4
  export * from '../../config/index.js';
4
5
  export * from '../../error_enriching.js';
5
6
  export * from '../../storage/index.js';
@@ -65,6 +65,7 @@ export async function createPXE(
65
65
  protocolContractsProvider,
66
66
  config: configWithContracts,
67
67
  loggerOrSuffix: pxeLogger,
68
+ hooks: options.hooks,
68
69
  });
69
70
  return pxe;
70
71
  }
@@ -1,11 +1,10 @@
1
- import type { Fr } from '@aztec/foundation/curves/bn254';
2
1
  import { createLogger } from '@aztec/foundation/log';
3
- import type { EventSelector } from '@aztec/stdlib/abi';
4
2
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
- import { siloNullifier } from '@aztec/stdlib/hash';
3
+ import { computePrivateEventCommitment, siloNullifier } from '@aztec/stdlib/hash';
6
4
  import type { AztecNode } from '@aztec/stdlib/interfaces/server';
7
- import type { BlockHeader, TxHash } from '@aztec/stdlib/tx';
5
+ import type { BlockHeader, IndexedTxEffect } from '@aztec/stdlib/tx';
8
6
 
7
+ import type { EventValidationRequest } from '../contract_function_simulator/noir-structs/event_validation_request.js';
9
8
  import { PrivateEventStore } from '../storage/private_event_store/private_event_store.js';
10
9
 
11
10
  export class EventService {
@@ -17,26 +16,62 @@ export class EventService {
17
16
  private readonly log = createLogger('pxe:event_service'),
18
17
  ) {}
19
18
 
20
- public async validateAndStoreEvent(
21
- contractAddress: AztecAddress,
22
- selector: EventSelector,
23
- randomness: Fr,
24
- content: Fr[],
25
- eventCommitment: Fr,
26
- txHash: TxHash,
19
+ /**
20
+ * Validates and stores a batch of private events against pre-fetched tx effects.
21
+ *
22
+ * @param requests - The events to validate and store.
23
+ * @param scope - The scope under which the events are being stored.
24
+ * @param txEffects - Pre-fetched tx effects keyed by `TxHash.toString()`. Must contain entries for every request's
25
+ * txHash; missing entries are treated as a node bug and cause an error.
26
+ */
27
+ public async validateAndStoreEvents(
28
+ requests: EventValidationRequest[],
27
29
  scope: AztecAddress,
30
+ txEffects: Map<string, IndexedTxEffect>,
28
31
  ): Promise<void> {
32
+ if (requests.length === 0) {
33
+ return;
34
+ }
35
+
36
+ const anchorBlockNumber = this.anchorBlockHeader.getBlockNumber();
37
+
38
+ await Promise.all(requests.map(req => this.#validateAndStoreEvent(req, scope, txEffects, anchorBlockNumber)));
39
+ }
40
+
41
+ async #validateAndStoreEvent(
42
+ request: EventValidationRequest,
43
+ scope: AztecAddress,
44
+ txEffects: Map<string, IndexedTxEffect>,
45
+ anchorBlockNumber: number,
46
+ ): Promise<void> {
47
+ const {
48
+ contractAddress,
49
+ eventTypeId: selector,
50
+ randomness,
51
+ serializedEvent: content,
52
+ eventCommitment,
53
+ txHash,
54
+ } = request;
55
+
56
+ // Defense-in-depth: the built-in private-event path derives this commitment from content before enqueueing, but
57
+ // unconstrained PXE-side code (e.g. a custom message handler) can reach this oracle with arbitrary
58
+ // (content, commitment) pairs. Without this check it could bind arbitrary content to a legitimate tx nullifier,
59
+ // causing PXE to surface fabricated event data.
60
+ const recomputedCommitment = await computePrivateEventCommitment(randomness, selector.toField(), content);
61
+ if (!recomputedCommitment.equals(eventCommitment)) {
62
+ this.log.warn(
63
+ `Skipping event whose content does not hash to the provided commitment. contract=${contractAddress}, selector=${selector}, eventCommitment=${eventCommitment}, txHash=${txHash}, recomputedCommitment=${recomputedCommitment}`,
64
+ );
65
+ return;
66
+ }
67
+
29
68
  // While using 'latest' block number would be fine for private events since they cannot be accessed from Aztec.nr
30
69
  // (and thus we're less concerned about being ahead of the synced block), we use the synced block number to
31
70
  // maintain consistent behavior in the PXE. Additionally, events should never be ahead of the synced block here
32
71
  // since `fetchTaggedLogs` only processes logs up to the synced block.
33
- const [siloedEventCommitment, txEffect] = await Promise.all([
34
- siloNullifier(contractAddress, eventCommitment),
35
- this.aztecNode.getTxEffect(txHash),
36
- ]);
37
-
38
- const anchorBlockNumber = this.anchorBlockHeader.getBlockNumber();
72
+ const siloedEventCommitment = await siloNullifier(contractAddress, eventCommitment);
39
73
 
74
+ const txEffect = txEffects.get(txHash.toString());
40
75
  if (!txEffect) {
41
76
  // We error out instead of just logging a warning and skipping the event because this would indicate a bug. This
42
77
  // is because the node has already served info about this tx either when obtaining the log (TxScopedL2Log contain