@aztec/pxe 0.0.1-commit.033589e → 0.0.1-commit.03b9a845c

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 (91) hide show
  1. package/dest/config/index.d.ts +2 -2
  2. package/dest/config/index.d.ts.map +1 -1
  3. package/dest/config/index.js +1 -1
  4. package/dest/contract_function_simulator/contract_function_simulator.d.ts +9 -3
  5. package/dest/contract_function_simulator/contract_function_simulator.d.ts.map +1 -1
  6. package/dest/contract_function_simulator/contract_function_simulator.js +25 -3
  7. package/dest/contract_function_simulator/execution_tagging_index_cache.d.ts +5 -5
  8. package/dest/contract_function_simulator/execution_tagging_index_cache.d.ts.map +1 -1
  9. package/dest/contract_function_simulator/execution_tagging_index_cache.js +17 -9
  10. package/dest/contract_function_simulator/index.d.ts +2 -1
  11. package/dest/contract_function_simulator/index.d.ts.map +1 -1
  12. package/dest/contract_function_simulator/index.js +1 -0
  13. package/dest/contract_function_simulator/noir-structs/event_validation_request.d.ts +2 -3
  14. package/dest/contract_function_simulator/noir-structs/event_validation_request.d.ts.map +1 -1
  15. package/dest/contract_function_simulator/noir-structs/event_validation_request.js +5 -4
  16. package/dest/contract_function_simulator/noir-structs/log_retrieval_response.d.ts +1 -1
  17. package/dest/contract_function_simulator/noir-structs/log_retrieval_response.d.ts.map +1 -1
  18. package/dest/contract_function_simulator/noir-structs/log_retrieval_response.js +1 -3
  19. package/dest/contract_function_simulator/noir-structs/message_tx_context.d.ts +16 -0
  20. package/dest/contract_function_simulator/noir-structs/message_tx_context.d.ts.map +1 -0
  21. package/dest/contract_function_simulator/noir-structs/message_tx_context.js +57 -0
  22. package/dest/contract_function_simulator/noir-structs/note_validation_request.d.ts +2 -4
  23. package/dest/contract_function_simulator/noir-structs/note_validation_request.d.ts.map +1 -1
  24. package/dest/contract_function_simulator/noir-structs/note_validation_request.js +3 -5
  25. package/dest/contract_function_simulator/oracle/interfaces.d.ts +14 -9
  26. package/dest/contract_function_simulator/oracle/interfaces.d.ts.map +1 -1
  27. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.d.ts +9 -0
  28. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.d.ts.map +1 -0
  29. package/dest/contract_function_simulator/oracle/legacy_oracle_mappings.js +42 -0
  30. package/dest/contract_function_simulator/oracle/oracle.d.ts +8 -7
  31. package/dest/contract_function_simulator/oracle/oracle.d.ts.map +1 -1
  32. package/dest/contract_function_simulator/oracle/oracle.js +44 -18
  33. package/dest/contract_function_simulator/oracle/private_execution.js +4 -2
  34. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts +8 -33
  35. package/dest/contract_function_simulator/oracle/private_execution_oracle.d.ts.map +1 -1
  36. package/dest/contract_function_simulator/oracle/private_execution_oracle.js +7 -39
  37. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts +33 -13
  38. package/dest/contract_function_simulator/oracle/utility_execution_oracle.d.ts.map +1 -1
  39. package/dest/contract_function_simulator/oracle/utility_execution_oracle.js +108 -24
  40. package/dest/contract_sync/contract_sync_service.d.ts +5 -3
  41. package/dest/contract_sync/contract_sync_service.d.ts.map +1 -1
  42. package/dest/contract_sync/contract_sync_service.js +47 -30
  43. package/dest/messages/message_context_service.d.ts +17 -0
  44. package/dest/messages/message_context_service.d.ts.map +1 -0
  45. package/dest/messages/message_context_service.js +36 -0
  46. package/dest/oracle_version.d.ts +2 -2
  47. package/dest/oracle_version.js +2 -2
  48. package/dest/pxe.d.ts +6 -3
  49. package/dest/pxe.d.ts.map +1 -1
  50. package/dest/pxe.js +33 -19
  51. package/dest/storage/metadata.d.ts +1 -1
  52. package/dest/storage/metadata.js +1 -1
  53. package/dest/storage/tagging_store/sender_tagging_store.d.ts +26 -25
  54. package/dest/storage/tagging_store/sender_tagging_store.d.ts.map +1 -1
  55. package/dest/storage/tagging_store/sender_tagging_store.js +141 -115
  56. package/dest/tagging/index.d.ts +2 -2
  57. package/dest/tagging/index.d.ts.map +1 -1
  58. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts +1 -1
  59. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.d.ts.map +1 -1
  60. package/dest/tagging/sender_sync/sync_sender_tagging_indexes.js +10 -1
  61. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.d.ts +4 -3
  62. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.d.ts.map +1 -1
  63. package/dest/tagging/sender_sync/utils/get_status_change_of_pending.js +20 -10
  64. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts +2 -1
  65. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.d.ts.map +1 -1
  66. package/dest/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.js +24 -11
  67. package/package.json +16 -16
  68. package/src/config/index.ts +1 -1
  69. package/src/contract_function_simulator/contract_function_simulator.ts +36 -4
  70. package/src/contract_function_simulator/execution_tagging_index_cache.ts +16 -11
  71. package/src/contract_function_simulator/index.ts +1 -0
  72. package/src/contract_function_simulator/noir-structs/event_validation_request.ts +8 -5
  73. package/src/contract_function_simulator/noir-structs/log_retrieval_response.ts +1 -4
  74. package/src/contract_function_simulator/noir-structs/message_tx_context.ts +55 -0
  75. package/src/contract_function_simulator/noir-structs/note_validation_request.ts +3 -6
  76. package/src/contract_function_simulator/oracle/interfaces.ts +17 -17
  77. package/src/contract_function_simulator/oracle/legacy_oracle_mappings.ts +135 -0
  78. package/src/contract_function_simulator/oracle/oracle.ts +53 -43
  79. package/src/contract_function_simulator/oracle/private_execution.ts +3 -3
  80. package/src/contract_function_simulator/oracle/private_execution_oracle.ts +9 -57
  81. package/src/contract_function_simulator/oracle/utility_execution_oracle.ts +163 -31
  82. package/src/contract_sync/contract_sync_service.ts +67 -38
  83. package/src/messages/message_context_service.ts +45 -0
  84. package/src/oracle_version.ts +2 -2
  85. package/src/pxe.ts +51 -18
  86. package/src/storage/metadata.ts +1 -1
  87. package/src/storage/tagging_store/sender_tagging_store.ts +182 -135
  88. package/src/tagging/index.ts +1 -1
  89. package/src/tagging/sender_sync/sync_sender_tagging_indexes.ts +19 -1
  90. package/src/tagging/sender_sync/utils/get_status_change_of_pending.ts +26 -11
  91. package/src/tagging/sender_sync/utils/load_and_store_new_tagging_indexes.ts +19 -9
@@ -6,24 +6,27 @@ 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';
12
- import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract';
13
+ import type { CompleteAddress, ContractInstance, PartialAddress } from '@aztec/stdlib/contract';
13
14
  import { siloNullifier } from '@aztec/stdlib/hash';
14
15
  import type { AztecNode } from '@aztec/stdlib/interfaces/server';
15
16
  import type { KeyValidationRequest } from '@aztec/stdlib/kernel';
16
- import { computeAddressSecret } from '@aztec/stdlib/keys';
17
+ import { type PublicKeys, computeAddressSecret } from '@aztec/stdlib/keys';
17
18
  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
  }
@@ -147,16 +173,18 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
147
173
 
148
174
  /**
149
175
  * Fetches the index and sibling path of a leaf at a given block from the note hash tree.
150
- * @param anchorBlockHash - The hash of a block that contains the note hash tree root in which to find the membership
151
- * witness.
176
+ * @param blockHash - The hash of a block that contains the note hash tree root in which to find the
177
+ * membership witness.
152
178
  * @param noteHash - The note hash to find in the note hash tree.
153
179
  * @returns The membership witness containing the leaf index and sibling path
154
180
  */
155
181
  public getNoteHashMembershipWitness(
156
- anchorBlockHash: BlockHash,
182
+ blockHash: BlockHash,
157
183
  noteHash: Fr,
158
184
  ): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
159
- return this.aztecNode.getNoteHashMembershipWitness(anchorBlockHash, noteHash);
185
+ return this.#queryWithBlockHashNotAfterAnchor(blockHash, () =>
186
+ this.aztecNode.getNoteHashMembershipWitness(blockHash, noteHash),
187
+ );
160
188
  }
161
189
 
162
190
  /**
@@ -165,16 +193,21 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
165
193
  * Block hashes are the leaves of the archive tree. Each time a new block is added to the chain,
166
194
  * its block hash is appended as a new leaf to the archive tree.
167
195
  *
168
- * @param anchorBlockHash - The hash of a block that contains the archive tree root in which to find the membership
196
+ * @param referenceBlockHash - The hash of a block that contains the archive tree root in which to find the membership
169
197
  * witness.
170
198
  * @param blockHash - The block hash to find in the archive tree.
171
199
  * @returns The membership witness containing the leaf index and sibling path
172
200
  */
173
201
  public getBlockHashMembershipWitness(
174
- anchorBlockHash: BlockHash,
202
+ referenceBlockHash: BlockHash,
175
203
  blockHash: BlockHash,
176
204
  ): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
177
- return this.aztecNode.getBlockHashMembershipWitness(anchorBlockHash, blockHash);
205
+ // Note that we validate that the reference block hash is at or before the anchor block - we don't test the block
206
+ // hash at all. If the block hash did not exist by the reference block hash, then the node will not return the
207
+ // membership witness as there is none.
208
+ return this.#queryWithBlockHashNotAfterAnchor(referenceBlockHash, () =>
209
+ this.aztecNode.getBlockHashMembershipWitness(referenceBlockHash, blockHash),
210
+ );
178
211
  }
179
212
 
180
213
  /**
@@ -187,7 +220,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
187
220
  blockHash: BlockHash,
188
221
  nullifier: Fr,
189
222
  ): Promise<NullifierMembershipWitness | undefined> {
190
- return this.aztecNode.getNullifierMembershipWitness(blockHash, nullifier);
223
+ return this.#queryWithBlockHashNotAfterAnchor(blockHash, () =>
224
+ this.aztecNode.getNullifierMembershipWitness(blockHash, nullifier),
225
+ );
191
226
  }
192
227
 
193
228
  /**
@@ -203,7 +238,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
203
238
  blockHash: BlockHash,
204
239
  nullifier: Fr,
205
240
  ): Promise<NullifierMembershipWitness | undefined> {
206
- return this.aztecNode.getLowNullifierMembershipWitness(blockHash, nullifier);
241
+ return this.#queryWithBlockHashNotAfterAnchor(blockHash, () =>
242
+ this.aztecNode.getLowNullifierMembershipWitness(blockHash, nullifier),
243
+ );
207
244
  }
208
245
 
209
246
  /**
@@ -213,7 +250,9 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
213
250
  * @returns - The witness
214
251
  */
215
252
  public getPublicDataWitness(blockHash: BlockHash, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
216
- return this.aztecNode.getPublicDataWitness(blockHash, leafSlot);
253
+ return this.#queryWithBlockHashNotAfterAnchor(blockHash, () =>
254
+ this.aztecNode.getPublicDataWitness(blockHash, leafSlot),
255
+ );
217
256
  }
218
257
 
219
258
  /**
@@ -232,12 +271,18 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
232
271
  }
233
272
 
234
273
  /**
235
- * Retrieve the complete address associated to a given address.
274
+ * Retrieve the public keys and partial address associated to a given address.
236
275
  * @param account - The account address.
237
- * @returns A complete address associated with the input address, or `undefined` if not registered.
276
+ * @returns The public keys and partial address, or `undefined` if the account is not registered.
238
277
  */
239
- public tryGetPublicKeysAndPartialAddress(account: AztecAddress): Promise<CompleteAddress | undefined> {
240
- return this.addressStore.getCompleteAddress(account);
278
+ public async tryGetPublicKeysAndPartialAddress(
279
+ account: AztecAddress,
280
+ ): Promise<{ publicKeys: PublicKeys; partialAddress: PartialAddress } | undefined> {
281
+ const completeAddress = await this.addressStore.getCompleteAddress(account);
282
+ if (!completeAddress) {
283
+ return undefined;
284
+ }
285
+ return { publicKeys: completeAddress.publicKeys, partialAddress: completeAddress.partialAddress };
241
286
  }
242
287
 
243
288
  protected async getCompleteAddressOrFail(account: AztecAddress): Promise<CompleteAddress> {
@@ -348,7 +393,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
348
393
  }
349
394
 
350
395
  /**
351
- * Fetches a message from the executionStore, given its key.
396
+ * Returns the membership witness of an un-nullified L1 to L2 message.
352
397
  * @param contractAddress - Address of a contract by which the message was emitted.
353
398
  * @param messageHash - Hash of the message.
354
399
  * @param secret - Secret used to compute a nullifier.
@@ -361,6 +406,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
361
406
  contractAddress,
362
407
  messageHash,
363
408
  secret,
409
+ await this.anchorBlockHeader.hash(),
364
410
  );
365
411
 
366
412
  return new MessageLoadOracleInputs(messageIndex, siblingPath);
@@ -373,25 +419,27 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
373
419
  * @param startStorageSlot - The starting storage slot.
374
420
  * @param numberOfElements - Number of elements to read from the starting storage slot.
375
421
  */
376
- public async storageRead(
422
+ public storageRead(
377
423
  blockHash: BlockHash,
378
424
  contractAddress: AztecAddress,
379
425
  startStorageSlot: Fr,
380
426
  numberOfElements: number,
381
427
  ) {
382
- const slots = Array(numberOfElements)
383
- .fill(0)
384
- .map((_, i) => new Fr(startStorageSlot.value + BigInt(i)));
428
+ return this.#queryWithBlockHashNotAfterAnchor(blockHash, async () => {
429
+ const slots = Array(numberOfElements)
430
+ .fill(0)
431
+ .map((_, i) => new Fr(startStorageSlot.value + BigInt(i)));
385
432
 
386
- const values = await Promise.all(
387
- slots.map(storageSlot => this.aztecNode.getPublicStorageAt(blockHash, contractAddress, storageSlot)),
388
- );
433
+ const values = await Promise.all(
434
+ slots.map(storageSlot => this.aztecNode.getPublicStorageAt(blockHash, contractAddress, storageSlot)),
435
+ );
389
436
 
390
- this.logger.debug(
391
- `Oracle storage read: slots=[${slots.map(slot => slot.toString()).join(', ')}] address=${contractAddress.toString()} values=[${values.join(', ')}]`,
392
- );
437
+ this.logger.debug(
438
+ `Oracle storage read: slots=[${slots.map(slot => slot.toString()).join(', ')}] address=${contractAddress.toString()} values=[${values.join(', ')}]`,
439
+ );
393
440
 
394
- return values;
441
+ return values;
442
+ });
395
443
  }
396
444
 
397
445
  /**
@@ -448,6 +496,8 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
448
496
  contractAddress: AztecAddress,
449
497
  noteValidationRequestsArrayBaseSlot: Fr,
450
498
  eventValidationRequestsArrayBaseSlot: Fr,
499
+ maxNotePackedLen: number,
500
+ maxEventSerializedLen: number,
451
501
  ) {
452
502
  // TODO(#10727): allow other contracts to store notes
453
503
  if (!this.contractAddress.equals(contractAddress)) {
@@ -458,11 +508,11 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
458
508
  // faster as we don't need to wait for the network round-trip.
459
509
  const noteValidationRequests = (
460
510
  await this.capsuleStore.readCapsuleArray(contractAddress, noteValidationRequestsArrayBaseSlot, this.jobId)
461
- ).map(NoteValidationRequest.fromFields);
511
+ ).map(fields => NoteValidationRequest.fromFields(fields, maxNotePackedLen));
462
512
 
463
513
  const eventValidationRequests = (
464
514
  await this.capsuleStore.readCapsuleArray(contractAddress, eventValidationRequestsArrayBaseSlot, this.jobId)
465
- ).map(EventValidationRequest.fromFields);
515
+ ).map(fields => EventValidationRequest.fromFields(fields, maxEventSerializedLen));
466
516
 
467
517
  const noteService = new NoteService(this.noteStore, this.aztecNode, this.anchorBlockHeader, this.jobId);
468
518
  const noteStorePromises = noteValidationRequests.map(request =>
@@ -542,6 +592,47 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
542
592
  );
543
593
  }
544
594
 
595
+ public async utilityResolveMessageContexts(
596
+ contractAddress: AztecAddress,
597
+ messageContextRequestsArrayBaseSlot: Fr,
598
+ messageContextResponsesArrayBaseSlot: Fr,
599
+ ) {
600
+ try {
601
+ if (!this.contractAddress.equals(contractAddress)) {
602
+ throw new Error(`Got a message context request from ${contractAddress}, expected ${this.contractAddress}`);
603
+ }
604
+ const requestCapsules = await this.capsuleStore.readCapsuleArray(
605
+ contractAddress,
606
+ messageContextRequestsArrayBaseSlot,
607
+ this.jobId,
608
+ );
609
+
610
+ const txHashes = requestCapsules.map((fields, i) => {
611
+ if (fields.length !== 1) {
612
+ throw new Error(
613
+ `Malformed message context request at index ${i}: expected 1 field (tx hash), got ${fields.length}`,
614
+ );
615
+ }
616
+ return fields[0];
617
+ });
618
+
619
+ const maybeMessageContexts = await this.messageContextService.resolveMessageContexts(
620
+ txHashes,
621
+ this.anchorBlockHeader.getBlockNumber(),
622
+ );
623
+
624
+ // Leave response in response capsule array.
625
+ await this.capsuleStore.setCapsuleArray(
626
+ contractAddress,
627
+ messageContextResponsesArrayBaseSlot,
628
+ maybeMessageContexts.map(MessageTxContext.toSerializedOption),
629
+ this.jobId,
630
+ );
631
+ } finally {
632
+ await this.capsuleStore.setCapsuleArray(contractAddress, messageContextRequestsArrayBaseSlot, [], this.jobId);
633
+ }
634
+ }
635
+
545
636
  public storeCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[]): Promise<void> {
546
637
  if (!contractAddress.equals(this.contractAddress)) {
547
638
  // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB
@@ -580,6 +671,17 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
580
671
  return this.capsuleStore.copyCapsule(this.contractAddress, srcSlot, dstSlot, numEntries, this.jobId);
581
672
  }
582
673
 
674
+ /**
675
+ * Clears cached sync state for a contract for a set of scopes, forcing re-sync on the next query so that newly
676
+ * stored notes or events are discovered.
677
+ */
678
+ public invalidateContractSyncCache(contractAddress: AztecAddress, scopes: AztecAddress[]): void {
679
+ if (!contractAddress.equals(this.contractAddress)) {
680
+ throw new Error(`Contract ${this.contractAddress} cannot invalidate sync cache of ${contractAddress}`);
681
+ }
682
+ this.contractSyncService.invalidateContractForScopes(contractAddress, scopes);
683
+ }
684
+
583
685
  // TODO(#11849): consider replacing this oracle with a pure Noir implementation of aes decryption.
584
686
  public aes128Decrypt(ciphertext: Buffer, iv: Buffer, symKey: Buffer): Promise<Buffer> {
585
687
  const aes128 = new Aes128();
@@ -601,4 +703,34 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
601
703
  const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM);
602
704
  return deriveEcdhSharedSecret(addressSecret, ephPk);
603
705
  }
706
+
707
+ public emitOffchainEffect(data: Fr[]): Promise<void> {
708
+ this.offchainEffects.push({ data, contractAddress: this.contractAddress });
709
+ return Promise.resolve();
710
+ }
711
+
712
+ /** Returns offchain effects collected during execution. */
713
+ public getOffchainEffects(): OffchainEffect[] {
714
+ return this.offchainEffects;
715
+ }
716
+
717
+ /** Runs a query concurrently with a validation that the block hash is not ahead of the anchor block. */
718
+ async #queryWithBlockHashNotAfterAnchor<T>(blockHash: BlockHash, query: () => Promise<T>): Promise<T> {
719
+ const [response] = await Promise.all([
720
+ query(),
721
+ (async () => {
722
+ const header = await this.aztecNode.getBlockHeader(blockHash);
723
+ if (!header) {
724
+ throw new Error(`Could not find block header for block hash ${blockHash}`);
725
+ }
726
+
727
+ if (header.getBlockNumber() > this.anchorBlockHeader.getBlockNumber()) {
728
+ throw new Error(
729
+ `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()}).`,
730
+ );
731
+ }
732
+ })(),
733
+ ]);
734
+ return response;
735
+ }
604
736
  }
@@ -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';