@aztec/pxe 0.0.1-commit.cb6bed7c2 → 0.0.1-commit.cbf2c2d5d

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 (87) hide show
  1. package/dest/contract_function_simulator/contract_function_simulator.d.ts +9 -3
  2. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  3. package/dest/contract_function_simulator/contract_function_simulator.js +25 -3
  4. package/dest/contract_function_simulator/execution_tagging_index_cache.d.ts +5 -5
  5. package/dest/contract_function_simulator/execution_tagging_index_cache.d.ts.map +1 -1
  6. package/dest/contract_function_simulator/execution_tagging_index_cache.js +17 -9
  7. package/dest/contract_function_simulator/index.d.ts +2 -1
  8. package/dest/contract_function_simulator/index.d.ts.map +1 -1
  9. package/dest/contract_function_simulator/index.js +1 -0
  10. package/dest/contract_function_simulator/noir-structs/event_validation_request.d.ts +2 -3
  11. package/dest/contract_function_simulator/noir-structs/event_validation_request.d.ts.map +1 -1
  12. package/dest/contract_function_simulator/noir-structs/event_validation_request.js +5 -4
  13. package/dest/contract_function_simulator/noir-structs/log_retrieval_response.d.ts +1 -1
  14. package/dest/contract_function_simulator/noir-structs/log_retrieval_response.d.ts.map +1 -1
  15. package/dest/contract_function_simulator/noir-structs/log_retrieval_response.js +1 -3
  16. package/dest/contract_function_simulator/noir-structs/message_tx_context.d.ts +16 -0
  17. package/dest/contract_function_simulator/noir-structs/message_tx_context.d.ts.map +1 -0
  18. package/dest/contract_function_simulator/noir-structs/message_tx_context.js +57 -0
  19. package/dest/contract_function_simulator/noir-structs/note_validation_request.d.ts +2 -4
  20. package/dest/contract_function_simulator/noir-structs/note_validation_request.d.ts.map +1 -1
  21. package/dest/contract_function_simulator/noir-structs/note_validation_request.js +3 -5
  22. package/dest/contract_function_simulator/oracle/interfaces.d.ts +8 -7
  23. package/dest/contract_function_simulator/oracle/interfaces.d.ts.map +1 -1
  24. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.d.ts +9 -0
  25. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.d.ts.map +1 -0
  26. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.js +42 -0
  27. package/dest/contract_function_simulator/oracle/oracle.d.ts +8 -7
  28. package/dest/contract_function_simulator/oracle/oracle.d.ts.map +1 -1
  29. package/dest/contract_function_simulator/oracle/oracle.js +44 -18
  30. package/dest/contract_function_simulator/oracle/private_execution.js +4 -2
  31. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +8 -33
  32. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  33. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +7 -39
  34. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +19 -3
  35. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  36. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +60 -3
  37. package/dest/contract_sync/contract_sync_service.d.ts +5 -3
  38. package/dest/contract_sync/contract_sync_service.d.ts.map +1 -1
  39. package/dest/contract_sync/contract_sync_service.js +47 -30
  40. package/dest/messages/message_context_service.d.ts +17 -0
  41. package/dest/messages/message_context_service.d.ts.map +1 -0
  42. package/dest/messages/message_context_service.js +36 -0
  43. package/dest/oracle_version.d.ts +2 -2
  44. package/dest/oracle_version.js +2 -2
  45. package/dest/pxe.d.ts +6 -3
  46. package/dest/pxe.d.ts.map +1 -1
  47. package/dest/pxe.js +33 -19
  48. package/dest/storage/metadata.d.ts +1 -1
  49. package/dest/storage/metadata.js +1 -1
  50. package/dest/storage/tagging_store/sender_tagging_store.d.ts +26 -25
  51. package/dest/storage/tagging_store/sender_tagging_store.d.ts.map +1 -1
  52. package/dest/storage/tagging_store/sender_tagging_store.js +141 -115
  53. package/dest/tagging/index.d.ts +2 -2
  54. package/dest/tagging/index.d.ts.map +1 -1
  55. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts +1 -1
  56. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts.map +1 -1
  57. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.js +10 -1
  58. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.d.ts +4 -3
  59. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.d.ts.map +1 -1
  60. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.js +20 -10
  61. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts +2 -1
  62. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts.map +1 -1
  63. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.js +24 -11
  64. package/package.json +16 -16
  65. package/src/contract_function_simulator/contract_function_simulator.ts +36 -4
  66. package/src/contract_function_simulator/execution_tagging_index_cache.ts +16 -11
  67. package/src/contract_function_simulator/index.ts +1 -0
  68. package/src/contract_function_simulator/noir-structs/event_validation_request.ts +8 -5
  69. package/src/contract_function_simulator/noir-structs/log_retrieval_response.ts +1 -4
  70. package/src/contract_function_simulator/noir-structs/message_tx_context.ts +55 -0
  71. package/src/contract_function_simulator/noir-structs/note_validation_request.ts +3 -6
  72. package/src/contract_function_simulator/oracle/interfaces.ts +12 -15
  73. package/src/contract_function_simulator/oracle/legacy_oracle_mappings.ts +135 -0
  74. package/src/contract_function_simulator/oracle/oracle.ts +53 -43
  75. package/src/contract_function_simulator/oracle/private_execution.ts +3 -3
  76. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +9 -57
  77. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +93 -3
  78. package/src/contract_sync/contract_sync_service.ts +67 -38
  79. package/src/messages/message_context_service.ts +45 -0
  80. package/src/oracle_version.ts +2 -2
  81. package/src/pxe.ts +51 -18
  82. package/src/storage/metadata.ts +1 -1
  83. package/src/storage/tagging_store/sender_tagging_store.ts +182 -135
  84. package/src/tagging/index.ts +1 -1
  85. package/src/tagging/sender_sync/sync_sender_tagging_indexes.ts +19 -1
  86. package/src/tagging/sender_sync/utils/get_status_change_of_pending.ts +26 -11
  87. package/src/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.ts +19 -9
@@ -6,6 +6,7 @@ import { Point } from '@aztec/foundation/curves/grumpkin';
6
6
  import { LogLevels, type Logger, createLogger } from '@aztec/foundation/log';
7
7
  import type { MembershipWitness } from '@aztec/foundation/trees';
8
8
  import type { KeyStore } from '@aztec/key-store';
9
+ import { isProtocolContract } from '@aztec/protocol-contracts';
9
10
  import type { AuthWitness } from '@aztec/stdlib/auth-witness';
10
11
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
11
12
  import { BlockHash } from '@aztec/stdlib/block';
@@ -18,12 +19,14 @@ import { deriveEcdhSharedSecret } from '@aztec/stdlib/logs';
18
19
  import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging';
19
20
  import type { NoteStatus } from '@aztec/stdlib/note';
20
21
  import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
21
- import type { BlockHeader, Capsule } from '@aztec/stdlib/tx';
22
+ import type { BlockHeader, Capsule, OffchainEffect } from '@aztec/stdlib/tx';
22
23
 
23
24
  import type { AccessScopes } from '../../access_scopes.js';
24
25
  import { createContractLogger, logContractMessage } from '../../contract_logging.js';
26
+ import type { ContractSyncService } from '../../contract_sync/contract_sync_service.js';
25
27
  import { EventService } from '../../events/event_service.js';
26
28
  import { LogService } from '../../logs/log_service.js';
29
+ import { MessageContextService } from '../../messages/message_context_service.js';
27
30
  import { NoteService } from '../../notes/note_service.js';
28
31
  import { ORACLE_VERSION } from '../../oracle_version.js';
29
32
  import type { AddressStore } from '../../storage/address_store/address_store.js';
@@ -36,6 +39,7 @@ import type { SenderAddressBookStore } from '../../storage/tagging_store/sender_
36
39
  import { EventValidationRequest } from '../noir-structs/event_validation_request.js';
37
40
  import { LogRetrievalRequest } from '../noir-structs/log_retrieval_request.js';
38
41
  import { LogRetrievalResponse } from '../noir-structs/log_retrieval_response.js';
42
+ import { MessageTxContext } from '../noir-structs/message_tx_context.js';
39
43
  import { NoteValidationRequest } from '../noir-structs/note_validation_request.js';
40
44
  import { UtilityContext } from '../noir-structs/utility_context.js';
41
45
  import { pickNotes } from '../pick_notes.js';
@@ -58,6 +62,8 @@ export type UtilityExecutionOracleArgs = {
58
62
  senderAddressBookStore: SenderAddressBookStore;
59
63
  capsuleStore: CapsuleStore;
60
64
  privateEventStore: PrivateEventStore;
65
+ messageContextService: MessageContextService;
66
+ contractSyncService: ContractSyncService;
61
67
  jobId: string;
62
68
  log?: ReturnType<typeof createLogger>;
63
69
  scopes: AccessScopes;
@@ -71,6 +77,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
71
77
  isUtility = true as const;
72
78
 
73
79
  private contractLogger: Logger | undefined;
80
+ private offchainEffects: OffchainEffect[] = [];
74
81
 
75
82
  protected readonly contractAddress: AztecAddress;
76
83
  protected readonly authWitnesses: AuthWitness[];
@@ -85,6 +92,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
85
92
  protected readonly senderAddressBookStore: SenderAddressBookStore;
86
93
  protected readonly capsuleStore: CapsuleStore;
87
94
  protected readonly privateEventStore: PrivateEventStore;
95
+ protected readonly messageContextService: MessageContextService;
96
+ protected readonly contractSyncService: ContractSyncService;
88
97
  protected readonly jobId: string;
89
98
  protected logger: ReturnType<typeof createLogger>;
90
99
  protected readonly scopes: AccessScopes;
@@ -103,12 +112,29 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
103
112
  this.senderAddressBookStore = args.senderAddressBookStore;
104
113
  this.capsuleStore = args.capsuleStore;
105
114
  this.privateEventStore = args.privateEventStore;
115
+ this.messageContextService = args.messageContextService;
116
+ this.contractSyncService = args.contractSyncService;
106
117
  this.jobId = args.jobId;
107
118
  this.logger = args.log ?? createLogger('simulator:client_view_context');
108
119
  this.scopes = args.scopes;
109
120
  }
110
121
 
111
122
  public assertCompatibleOracleVersion(version: number): void {
123
+ // TODO(F-416): Remove this hack on v5 when protocol contracts are redeployed.
124
+ // Protocol contracts/canonical contracts shipped with committed bytecode that cannot be changed. Assert they use
125
+ // the expected pinned version or the current one. We want to allow for both the pinned and the current versions
126
+ // because we want this code to work with both the pinned and unpinned version since some branches do not have the
127
+ // pinned contracts (like e.g. next)
128
+ const LEGACY_ORACLE_VERSION = 12;
129
+ if (isProtocolContract(this.contractAddress)) {
130
+ if (version !== LEGACY_ORACLE_VERSION && version !== ORACLE_VERSION) {
131
+ throw new Error(
132
+ `Expected legacy oracle version ${LEGACY_ORACLE_VERSION} or current oracle version ${ORACLE_VERSION} for alpha payload contract at ${this.contractAddress}, got ${version}.`,
133
+ );
134
+ }
135
+ return;
136
+ }
137
+
112
138
  if (version !== ORACLE_VERSION) {
113
139
  throw new Error(`Incompatible oracle version. Expected version ${ORACLE_VERSION}, got ${version}.`);
114
140
  }
@@ -454,6 +480,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
454
480
  contractAddress: AztecAddress,
455
481
  noteValidationRequestsArrayBaseSlot: Fr,
456
482
  eventValidationRequestsArrayBaseSlot: Fr,
483
+ maxNotePackedLen: number,
484
+ maxEventSerializedLen: number,
457
485
  ) {
458
486
  // TODO(#10727): allow other contracts to store notes
459
487
  if (!this.contractAddress.equals(contractAddress)) {
@@ -464,11 +492,11 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
464
492
  // faster as we don't need to wait for the network round-trip.
465
493
  const noteValidationRequests = (
466
494
  await this.capsuleStore.readCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot, this.jobId)
467
- ).map(NoteValidationRequest.fromFields);
495
+ ).map(fields => NoteValidationRequest.fromFields(fields, maxNotePackedLen));
468
496
 
469
497
  const eventValidationRequests = (
470
498
  await this.capsuleStore.readCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot, this.jobId)
471
- ).map(EventValidationRequest.fromFields);
499
+ ).map(fields => EventValidationRequest.fromFields(fields, maxEventSerializedLen));
472
500
 
473
501
  const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockHeader, this.jobId);
474
502
  const noteStorePromises = noteValidationRequests.map(request =>
@@ -548,6 +576,47 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
548
576
  );
549
577
  }
550
578
 
579
+ public async utilityResolveMessageContexts(
580
+ contractAddress: AztecAddress,
581
+ messageContextRequestsArrayBaseSlot: Fr,
582
+ messageContextResponsesArrayBaseSlot: Fr,
583
+ ) {
584
+ try {
585
+ if (!this.contractAddress.equals(contractAddress)) {
586
+ throw new Error(`Got a message context request from ${contractAddress}, expected ${this.contractAddress}`);
587
+ }
588
+ const requestCapsules = await this.capsuleStore.readCapsuleArray(
589
+ contractAddress,
590
+ messageContextRequestsArrayBaseSlot,
591
+ this.jobId,
592
+ );
593
+
594
+ const txHashes = requestCapsules.map((fields, i) => {
595
+ if (fields.length !== 1) {
596
+ throw new Error(
597
+ `Malformed message context request at index ${i}: expected 1 field (tx hash), got ${fields.length}`,
598
+ );
599
+ }
600
+ return fields[0];
601
+ });
602
+
603
+ const maybeMessageContexts = await this.messageContextService.resolveMessageContexts(
604
+ txHashes,
605
+ this.anchorBlockHeader.getBlockNumber(),
606
+ );
607
+
608
+ // Leave response in response capsule array.
609
+ await this.capsuleStore.setCapsuleArray(
610
+ contractAddress,
611
+ messageContextResponsesArrayBaseSlot,
612
+ maybeMessageContexts.map(MessageTxContext.toSerializedOption),
613
+ this.jobId,
614
+ );
615
+ } finally {
616
+ await this.capsuleStore.setCapsuleArray(contractAddress, messageContextRequestsArrayBaseSlot, [], this.jobId);
617
+ }
618
+ }
619
+
551
620
  public storeCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[]): Promise<void> {
552
621
  if (!contractAddress.equals(this.contractAddress)) {
553
622
  // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB
@@ -586,6 +655,17 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
586
655
  return this.capsuleStore.copyCapsule(this.contractAddress, srcSlot, dstSlot, numEntries, this.jobId);
587
656
  }
588
657
 
658
+ /**
659
+ * Clears cached sync state for a contract for a set of scopes, forcing re-sync on the next query so that newly
660
+ * stored notes or events are discovered.
661
+ */
662
+ public invalidateContractSyncCache(contractAddress: AztecAddress, scopes: AztecAddress[]): void {
663
+ if (!contractAddress.equals(this.contractAddress)) {
664
+ throw new Error(`Contract ${this.contractAddress} cannot invalidate sync cache of ${contractAddress}`);
665
+ }
666
+ this.contractSyncService.invalidateContractForScopes(contractAddress, scopes);
667
+ }
668
+
589
669
  // TODO(#11849): consider replacing this oracle with a pure Noir implementation of aes decryption.
590
670
  public aes128Decrypt(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise<Buffer> {
591
671
  const aes128 = new Aes128();
@@ -607,4 +687,14 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
607
687
  const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM);
608
688
  return deriveEcdhSharedSecret(addressSecret, ephPk);
609
689
  }
690
+
691
+ public emitOffchainEffect(data: Fr[]): Promise<void> {
692
+ this.offchainEffects.push({ data, contractAddress: this.contractAddress });
693
+ return Promise.resolve();
694
+ }
695
+
696
+ /** Returns offchain effects collected during execution. */
697
+ public getOffchainEffects(): OffchainEffect[] {
698
+ return this.offchainEffects;
699
+ }
610
700
  }
@@ -20,12 +20,12 @@ export class ContractSyncService implements StagedStore {
20
20
  readonly storeName = 'contract_sync';
21
21
 
22
22
  // Tracks contracts synced since last wipe. The cache is keyed per individual scope address
23
- // (`contractAddress:scopeAddress`), or `contractAddress:*` for undefined scopes (all accounts).
23
+ // (`contractAddress:scopeAddress`), or `contractAddress:*` for all scopes (all accounts).
24
24
  // The value is a promise that resolves when the contract is synced.
25
25
  private syncedContracts: Map<string, Promise<void>> = new Map();
26
26
 
27
- // Per-job overridden contract addresses - these contracts should not be synced.
28
- private overriddenContracts: Map<string, Set<string>> = new Map();
27
+ // Per-job excluded contract addresses - these contracts should not be synced.
28
+ private excludedFromSync: Map<string, Set<string>> = new Map();
29
29
 
30
30
  constructor(
31
31
  private aztecNode: AztecNode,
@@ -35,8 +35,8 @@ export class ContractSyncService implements StagedStore {
35
35
  ) {}
36
36
 
37
37
  /** Sets contracts that should be skipped during sync for a specific job. */
38
- setOverriddenContracts(jobId: string, addresses: Set<string>): void {
39
- this.overriddenContracts.set(jobId, addresses);
38
+ setExcludedFromSync(jobId: string, addresses: Set<string>): void {
39
+ this.excludedFromSync.set(jobId, addresses);
40
40
  }
41
41
 
42
42
  /**
@@ -56,47 +56,34 @@ export class ContractSyncService implements StagedStore {
56
56
  jobId: string,
57
57
  scopes: AccessScopes,
58
58
  ): Promise<void> {
59
- // Skip sync if this contract has an override for this job (overrides are keyed by contract address only)
60
- const overrides = this.overriddenContracts.get(jobId);
61
- if (overrides?.has(contractAddress.toString())) {
59
+ if (this.#shouldSkipSync(jobId, contractAddress)) {
62
60
  return;
63
61
  }
64
62
 
65
- // Skip sync if we already synced for "all scopes", or if we have an empty list of scopes
66
- const allScopesKey = toKey(contractAddress, 'ALL_SCOPES');
67
- const allScopesExisting = this.syncedContracts.get(allScopesKey);
68
- if (allScopesExisting || (scopes !== 'ALL_SCOPES' && scopes.length == 0)) {
69
- return;
70
- }
71
-
72
- const unsyncedScopes =
73
- scopes === 'ALL_SCOPES'
74
- ? scopes
75
- : scopes.filter(scope => !this.syncedContracts.has(toKey(contractAddress, scope)));
76
- const unsyncedScopesKeys = toKeys(contractAddress, unsyncedScopes);
77
-
78
- if (unsyncedScopesKeys.length > 0) {
79
- // Start sync and store the promise for all unsynced scopes
80
- const promise = this.#doSync(
63
+ this.#startSyncIfNeeded(contractAddress, scopes, scopesToSync =>
64
+ this.#syncContract(
81
65
  contractAddress,
82
66
  functionToInvokeAfterSync,
83
67
  utilityExecutor,
84
68
  anchorBlockHeader,
85
69
  jobId,
86
- unsyncedScopes,
87
- ).catch(err => {
88
- // There was an error syncing the contract, so we remove it from the cache so that it can be retried.
89
- unsyncedScopesKeys.forEach(key => this.syncedContracts.delete(key));
90
- throw err;
91
- });
92
- unsyncedScopesKeys.forEach(key => this.syncedContracts.set(key, promise));
93
- }
70
+ scopesToSync,
71
+ ),
72
+ );
94
73
 
95
- const promises = toKeys(contractAddress, scopes).map(key => this.syncedContracts.get(key)!);
96
- await Promise.all(promises);
74
+ await this.#awaitSync(contractAddress, scopes);
97
75
  }
98
76
 
99
- async #doSync(
77
+ /** Clears sync cache entries for the given scopes of a contract. Also clears the ALL_SCOPES entry. */
78
+ invalidateContractForScopes(contractAddress: AztecAddress, scopes: AztecAddress[]): void {
79
+ if (scopes.length === 0) {
80
+ return;
81
+ }
82
+ scopes.forEach(scope => this.syncedContracts.delete(toKey(contractAddress, scope)));
83
+ this.syncedContracts.delete(toKey(contractAddress, 'ALL_SCOPES'));
84
+ }
85
+
86
+ async #syncContract(
100
87
  contractAddress: AztecAddress,
101
88
  functionToInvokeAfterSync: FunctionSelector | null,
102
89
  utilityExecutor: (call: FunctionCall, scopes: AccessScopes) => Promise<any>,
@@ -129,8 +116,8 @@ export class ContractSyncService implements StagedStore {
129
116
  }
130
117
 
131
118
  commit(jobId: string): Promise<void> {
132
- // Clear overridden contracts for this job
133
- this.overriddenContracts.delete(jobId);
119
+ // Clear excluded contracts for this job
120
+ this.excludedFromSync.delete(jobId);
134
121
  return Promise.resolve();
135
122
  }
136
123
 
@@ -138,9 +125,51 @@ export class ContractSyncService implements StagedStore {
138
125
  // We clear the synced contracts cache here because, when the job is discarded, any associated database writes from
139
126
  // the sync are also undone.
140
127
  this.syncedContracts.clear();
141
- this.overriddenContracts.delete(jobId);
128
+ this.excludedFromSync.delete(jobId);
142
129
  return Promise.resolve();
143
130
  }
131
+ /** Returns true if sync should be skipped for this contract */
132
+ #shouldSkipSync(jobId: string, contractAddress: AztecAddress): boolean {
133
+ return !!this.excludedFromSync.get(jobId)?.has(contractAddress.toString());
134
+ }
135
+
136
+ /** If there are unsynced scopes, starts sync and stores the promise in cache with error cleanup. */
137
+ #startSyncIfNeeded(
138
+ contractAddress: AztecAddress,
139
+ scopes: AccessScopes,
140
+ syncFn: (scopesToSync: AccessScopes) => Promise<void>,
141
+ ): void {
142
+ const scopesToSync = this.#getScopesToSync(contractAddress, scopes);
143
+ const keys = toKeys(contractAddress, scopesToSync);
144
+ if (keys.length === 0) {
145
+ return;
146
+ }
147
+ const promise = syncFn(scopesToSync).catch(err => {
148
+ keys.forEach(key => this.syncedContracts.delete(key));
149
+ throw err;
150
+ });
151
+ keys.forEach(key => this.syncedContracts.set(key, promise));
152
+ }
153
+
154
+ /** Filters out scopes that are already cached, returning only those that still need syncing. */
155
+ #getScopesToSync(contractAddress: AztecAddress, scopes: AccessScopes): AccessScopes {
156
+ if (this.syncedContracts.has(toKey(contractAddress, 'ALL_SCOPES'))) {
157
+ // If we are already syncing all scopes, then return an empty list
158
+ return [];
159
+ }
160
+ if (scopes === 'ALL_SCOPES') {
161
+ return 'ALL_SCOPES';
162
+ }
163
+ return scopes.filter(scope => !this.syncedContracts.has(toKey(contractAddress, scope)));
164
+ }
165
+
166
+ /** Collects all relevant scope promises (including in-flight ones from concurrent calls) and awaits them. */
167
+ async #awaitSync(contractAddress: AztecAddress, scopes: AccessScopes): Promise<void> {
168
+ const promises = toKeys(contractAddress, scopes)
169
+ .map(key => this.syncedContracts.get(key))
170
+ .filter(p => p !== undefined);
171
+ await Promise.all(promises);
172
+ }
144
173
  }
145
174
 
146
175
  function toKeys(contract: AztecAddress, scopes: AccessScopes) {
@@ -0,0 +1,45 @@
1
+ import { Fr } from '@aztec/foundation/curves/bn254';
2
+ import type { AztecNode } from '@aztec/stdlib/interfaces/server';
3
+ import { TxHash } from '@aztec/stdlib/tx';
4
+
5
+ import { MessageTxContext } from '../contract_function_simulator/noir-structs/message_tx_context.js';
6
+
7
+ /** Resolves transaction hashes into the context needed to process messages. */
8
+ export class MessageContextService {
9
+ constructor(private readonly aztecNode: AztecNode) {}
10
+
11
+ /**
12
+ * Resolves a list of tx hashes into their message contexts.
13
+ *
14
+ * For each tx hash, looks up the corresponding tx effect and extracts the note hashes and first nullifier needed to
15
+ * process messages that originated from that transaction. Returns `null` for tx hashes that are zero, not yet
16
+ * available, or in blocks beyond the anchor block.
17
+ */
18
+ resolveMessageContexts(txHashes: Fr[], anchorBlockNumber: number): Promise<(MessageTxContext | null)[]> {
19
+ // TODO: optimize, we might be hitting the node to get the same txHash repeatedly
20
+ return Promise.all(
21
+ txHashes.map(async txHashField => {
22
+ // A zero tx hash indicates a tx-less offchain message (e.g. one not tied to any onchain transaction).
23
+ // These messages don't have a transaction context to resolve, so we return null.
24
+ if (txHashField.isZero()) {
25
+ return null;
26
+ }
27
+
28
+ const txHash = TxHash.fromField(txHashField);
29
+ const txEffect = await this.aztecNode.getTxEffect(txHash);
30
+ if (!txEffect || txEffect.l2BlockNumber > anchorBlockNumber) {
31
+ return null;
32
+ }
33
+
34
+ // Every tx has at least one nullifier (the first nullifier derived from the tx hash). Hitting this condition
35
+ // would mean a buggy node, but since we need to access data.nullifiers[0], the defensive check does no harm.
36
+ const data = txEffect.data;
37
+ if (data.nullifiers.length === 0) {
38
+ throw new Error(`Tx effect for ${txHash} has no nullifiers`);
39
+ }
40
+
41
+ return new MessageTxContext(data.txHash, data.noteHashes, data.nullifiers[0]);
42
+ }),
43
+ );
44
+ }
45
+ }
@@ -4,9 +4,9 @@
4
4
  ///
5
5
  /// @dev Whenever a contract function or Noir test is run, the `aztec_utl_assertCompatibleOracleVersion` oracle is called
6
6
  /// and if the oracle version is incompatible an error is thrown.
7
- export const ORACLE_VERSION = 14;
7
+ export const ORACLE_VERSION = 18;
8
8
 
9
9
  /// This hash is computed as by hashing the Oracle interface and it is used to detect when the Oracle interface changes,
10
10
  /// which in turn implies that you need to update the ORACLE_VERSION constant in this file and in
11
11
  /// `noir-projects/aztec-nr/aztec/src/oracle/version.nr`.
12
- export const ORACLE_INTERFACE_HASH = '9fb918682455c164ce8dd3acb71c751e2b9b2fc48913604069c9ea885fa378ca';
12
+ export const ORACLE_INTERFACE_HASH = '57e5b07c6d55fb167ef90f8d0f410f9bdb5b154a31159c624a061be40b02a2c2';
package/src/pxe.ts CHANGED
@@ -68,6 +68,7 @@ import { PXEDebugUtils } from './debug/pxe_debug_utils.js';
68
68
  import { enrichPublicSimulationError, enrichSimulationError } from './error_enriching.js';
69
69
  import { PrivateEventFilterValidator } from './events/private_event_filter_validator.js';
70
70
  import { JobCoordinator } from './job_coordinator/job_coordinator.js';
71
+ import { MessageContextService } from './messages/message_context_service.js';
71
72
  import {
72
73
  PrivateKernelExecutionProver,
73
74
  type PrivateKernelExecutionProverConfig,
@@ -106,7 +107,9 @@ export type SimulateTxOpts = {
106
107
  skipTxValidation?: boolean;
107
108
  /** If false, fees are enforced. */
108
109
  skipFeeEnforcement?: boolean;
109
- /** State overrides for the simulation, such as contract instances and artifacts. */
110
+ /** If true, kernel logic is emulated in TS for simulation */
111
+ skipKernels?: boolean;
112
+ /** State overrides for the simulation, such as contract instances and artifacts. Requires skipKernels: true */
110
113
  overrides?: SimulationOverrides;
111
114
  /** Addresses whose private state and keys are accessible during private execution */
112
115
  scopes: AccessScopes;
@@ -158,6 +161,7 @@ export class PXE {
158
161
  private addressStore: AddressStore,
159
162
  private privateEventStore: PrivateEventStore,
160
163
  private contractSyncService: ContractSyncService,
164
+ private messageContextService: MessageContextService,
161
165
  private simulator: CircuitSimulator,
162
166
  private proverEnabled: boolean,
163
167
  private proofCreator: PrivateKernelProver,
@@ -213,6 +217,8 @@ export class PXE {
213
217
  noteStore,
214
218
  createLogger('pxe:contract_sync', bindings),
215
219
  );
220
+ const messageContextService = new MessageContextService(node);
221
+
216
222
  const synchronizer = new BlockSynchronizer(
217
223
  node,
218
224
  store,
@@ -254,6 +260,7 @@ export class PXE {
254
260
  addressStore,
255
261
  privateEventStore,
256
262
  contractSyncService,
263
+ messageContextService,
257
264
  simulator,
258
265
  proverEnabled,
259
266
  proofCreator,
@@ -295,6 +302,7 @@ export class PXE {
295
302
  privateEventStore: this.privateEventStore,
296
303
  simulator: this.simulator,
297
304
  contractSyncService: this.contractSyncService,
305
+ messageContextService: this.messageContextService,
298
306
  });
299
307
  }
300
308
 
@@ -414,7 +422,14 @@ export class PXE {
414
422
  ) {
415
423
  try {
416
424
  const anchorBlockHeader = await this.anchorBlockStore.getBlockHeader();
417
- return contractFunctionSimulator.runUtility(call, authWitnesses ?? [], anchorBlockHeader, scopes, jobId);
425
+ const { result, offchainEffects } = await contractFunctionSimulator.runUtility(
426
+ call,
427
+ authWitnesses ?? [],
428
+ anchorBlockHeader,
429
+ scopes,
430
+ jobId,
431
+ );
432
+ return { result, offchainEffects };
418
433
  } catch (err) {
419
434
  if (err instanceof SimulationError) {
420
435
  await enrichSimulationError(err, this.contractStore, this.log);
@@ -554,6 +569,9 @@ export class PXE {
554
569
 
555
570
  if (wasAdded) {
556
571
  this.log.info(`Added sender:\n ${sender.toString()}`);
572
+ // Wipe the entire sync cache: the new sender's tagged logs could contain notes/events for any contract, so
573
+ // all contracts must re-sync to discover them.
574
+ this.contractSyncService.wipe();
557
575
  } else {
558
576
  this.log.info(`Sender:\n "${sender.toString()}"\n already registered.`);
559
577
  }
@@ -766,17 +784,17 @@ export class PXE {
766
784
  // transaction before this one is included in a block from this PXE, and that transaction contains a log with
767
785
  // a tag derived from the same secret, we would reuse the tag and the transactions would be linked. Hence
768
786
  // storing the tags here prevents linkage of txs sent from the same PXE.
769
- const preTagsUsedInTheTx = privateExecutionResult.entrypoint.preTags;
770
- if (preTagsUsedInTheTx.length > 0) {
787
+ const taggingIndexRangesUsedInTheTx = privateExecutionResult.entrypoint.taggingIndexRanges;
788
+ if (taggingIndexRangesUsedInTheTx.length > 0) {
771
789
  // TODO(benesjan): The following is an expensive operation. Figure out a way to avoid it.
772
790
  const txHash = (await txProvingResult.toTx()).txHash;
773
791
 
774
- await this.senderTaggingStore.storePendingIndexes(preTagsUsedInTheTx, txHash, jobId);
775
- this.log.debug(`Stored used pre-tags as sender for the tx`, {
776
- preTagsUsedInTheTx,
792
+ await this.senderTaggingStore.storePendingIndexes(taggingIndexRangesUsedInTheTx, txHash, jobId);
793
+ this.log.debug(`Stored used tagging index ranges as sender for the tx`, {
794
+ taggingIndexRangesUsedInTheTx,
777
795
  });
778
796
  } else {
779
- this.log.debug(`No pre-tags used in the tx`);
797
+ this.log.debug(`No tagging index ranges used in the tx`);
780
798
  }
781
799
 
782
800
  return txProvingResult;
@@ -883,7 +901,14 @@ export class PXE {
883
901
  */
884
902
  public simulateTx(
885
903
  txRequest: TxExecutionRequest,
886
- { simulatePublic, skipTxValidation = false, skipFeeEnforcement = false, overrides, scopes }: SimulateTxOpts,
904
+ {
905
+ simulatePublic,
906
+ skipTxValidation = false,
907
+ skipFeeEnforcement = false,
908
+ skipKernels = true,
909
+ overrides,
910
+ scopes,
911
+ }: SimulateTxOpts,
887
912
  ): Promise<TxSimulationResult> {
888
913
  // We disable concurrent simulations since those might execute oracles which read and write to the PXE stores (e.g.
889
914
  // to the capsules), and we need to prevent concurrent runs from interfering with one another (e.g. attempting to
@@ -907,17 +932,20 @@ export class PXE {
907
932
  await this.blockStateSynchronizer.sync();
908
933
  const syncTime = syncTimer.ms();
909
934
 
910
- const contractFunctionSimulator = this.#getSimulatorForTx(overrides);
911
- // Temporary: in case there are overrides, we have to skip the kernels or validations
912
- // will fail. Consider handing control to the user/wallet on whether they want to run them
913
- // or not.
914
935
  const overriddenContracts = overrides?.contracts ? new Set(Object.keys(overrides.contracts)) : undefined;
915
936
  const hasOverriddenContracts = overriddenContracts !== undefined && overriddenContracts.size > 0;
916
- const skipKernels = hasOverriddenContracts;
917
937
 
918
- // Set overridden contracts on the sync service so it knows to skip syncing them
938
+ if (hasOverriddenContracts && !skipKernels) {
939
+ throw new Error(
940
+ 'Simulating with overridden contracts is not compatible with kernel execution. Please set skipKernels to true when simulating with overridden contracts.',
941
+ );
942
+ }
943
+ const contractFunctionSimulator = this.#getSimulatorForTx(overrides);
944
+
919
945
  if (hasOverriddenContracts) {
920
- this.contractSyncService.setOverriddenContracts(jobId, overriddenContracts);
946
+ // Overridden contracts don't have a sync function, so calling sync on them would fail.
947
+ // We exclude them so the sync service skips them entirely.
948
+ this.contractSyncService.setExcludedFromSync(jobId, overriddenContracts);
921
949
  }
922
950
 
923
951
  // Execution of private functions only; no proving, and no kernel logic.
@@ -1049,7 +1077,7 @@ export class PXE {
1049
1077
  scopes,
1050
1078
  );
1051
1079
 
1052
- const executionResult = await this.#executeUtility(
1080
+ const { result: executionResult, offchainEffects } = await this.#executeUtility(
1053
1081
  contractFunctionSimulator,
1054
1082
  call,
1055
1083
  authwits ?? [],
@@ -1070,7 +1098,12 @@ export class PXE {
1070
1098
  };
1071
1099
 
1072
1100
  const simulationStats = contractFunctionSimulator.getStats();
1073
- return { result: executionResult, stats: { timings, nodeRPCCalls: simulationStats.nodeRPCCalls } };
1101
+ return {
1102
+ result: executionResult,
1103
+ offchainEffects,
1104
+ anchorBlockTimestamp: anchorBlockHeader.globalVariables.timestamp,
1105
+ stats: { timings, nodeRPCCalls: simulationStats.nodeRPCCalls },
1106
+ };
1074
1107
  } catch (err: any) {
1075
1108
  const { to, name, args } = call;
1076
1109
  const stringifiedArgs = args.map(arg => arg.toString()).join(', ');
@@ -1 +1 @@
1
- export const PXE_DATA_SCHEMA_VERSION = 3;
1
+ export const PXE_DATA_SCHEMA_VERSION = 4;