@aztec/pxe 0.71.0 → 0.73.0

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 (43) hide show
  1. package/dest/bin/index.js +1 -3
  2. package/dest/contract_data_oracle/index.js +4 -4
  3. package/dest/contract_data_oracle/private_functions_tree.d.ts +7 -6
  4. package/dest/contract_data_oracle/private_functions_tree.d.ts.map +1 -1
  5. package/dest/contract_data_oracle/private_functions_tree.js +25 -14
  6. package/dest/database/kv_pxe_database.d.ts +3 -3
  7. package/dest/database/kv_pxe_database.d.ts.map +1 -1
  8. package/dest/database/kv_pxe_database.js +8 -9
  9. package/dest/database/note_dao.d.ts +4 -4
  10. package/dest/database/note_dao.d.ts.map +1 -1
  11. package/dest/database/note_dao.js +5 -5
  12. package/dest/database/outgoing_note_dao.d.ts +1 -1
  13. package/dest/database/outgoing_note_dao.d.ts.map +1 -1
  14. package/dest/database/outgoing_note_dao.js +3 -3
  15. package/dest/database/pxe_database_test_suite.d.ts.map +1 -1
  16. package/dest/database/pxe_database_test_suite.js +70 -48
  17. package/dest/kernel_oracle/index.d.ts.map +1 -1
  18. package/dest/kernel_oracle/index.js +5 -5
  19. package/dest/kernel_prover/kernel_prover.js +5 -5
  20. package/dest/pxe_http/pxe_http_server.d.ts.map +1 -1
  21. package/dest/pxe_http/pxe_http_server.js +2 -1
  22. package/dest/pxe_service/error_enriching.js +4 -4
  23. package/dest/pxe_service/pxe_service.d.ts +16 -11
  24. package/dest/pxe_service/pxe_service.d.ts.map +1 -1
  25. package/dest/pxe_service/pxe_service.js +62 -53
  26. package/dest/pxe_service/test/pxe_test_suite.js +13 -13
  27. package/dest/simulator_oracle/index.d.ts.map +1 -1
  28. package/dest/simulator_oracle/index.js +36 -21
  29. package/package.json +15 -15
  30. package/src/bin/index.ts +0 -3
  31. package/src/contract_data_oracle/index.ts +3 -3
  32. package/src/contract_data_oracle/private_functions_tree.ts +28 -20
  33. package/src/database/kv_pxe_database.ts +19 -14
  34. package/src/database/note_dao.ts +7 -7
  35. package/src/database/outgoing_note_dao.ts +5 -5
  36. package/src/database/pxe_database_test_suite.ts +71 -56
  37. package/src/kernel_oracle/index.ts +4 -4
  38. package/src/kernel_prover/kernel_prover.ts +4 -4
  39. package/src/pxe_http/pxe_http_server.ts +1 -0
  40. package/src/pxe_service/error_enriching.ts +3 -3
  41. package/src/pxe_service/pxe_service.ts +86 -64
  42. package/src/pxe_service/test/pxe_test_suite.ts +12 -12
  43. package/src/simulator_oracle/index.ts +52 -23
@@ -5,7 +5,8 @@ import {
5
5
  type EventMetadataDefinition,
6
6
  type ExtendedNote,
7
7
  type FunctionCall,
8
- type GetUnencryptedLogsResponse,
8
+ type GetContractClassLogsResponse,
9
+ type GetPublicLogsResponse,
9
10
  type InBlock,
10
11
  L1EventPayload,
11
12
  type L2Block,
@@ -145,13 +146,33 @@ export class PXEService implements PXE {
145
146
  return this.db.getContractInstance(address);
146
147
  }
147
148
 
148
- public async getContractClass(id: Fr): Promise<ContractClassWithId | undefined> {
149
+ public async getContractClassMetadata(
150
+ id: Fr,
151
+ includeArtifact: boolean = false,
152
+ ): Promise<{
153
+ contractClass: ContractClassWithId | undefined;
154
+ isContractClassPubliclyRegistered: boolean;
155
+ artifact: ContractArtifact | undefined;
156
+ }> {
149
157
  const artifact = await this.db.getContractArtifact(id);
150
- return artifact && getContractClassFromArtifact(artifact);
158
+
159
+ return {
160
+ contractClass: artifact && (await getContractClassFromArtifact(artifact)),
161
+ isContractClassPubliclyRegistered: await this.#isContractClassPubliclyRegistered(id),
162
+ artifact: includeArtifact ? artifact : undefined,
163
+ };
151
164
  }
152
165
 
153
- public getContractArtifact(id: Fr): Promise<ContractArtifact | undefined> {
154
- return this.db.getContractArtifact(id);
166
+ public async getContractMetadata(address: AztecAddress): Promise<{
167
+ contractInstance: ContractInstanceWithAddress | undefined;
168
+ isContractInitialized: boolean;
169
+ isContractPubliclyDeployed: boolean;
170
+ }> {
171
+ return {
172
+ contractInstance: await this.db.getContractInstance(address),
173
+ isContractInitialized: await this.#isContractInitialized(address),
174
+ isContractPubliclyDeployed: await this.#isContractPubliclyDeployed(address),
175
+ };
155
176
  }
156
177
 
157
178
  public async registerAccount(secretKey: Fr, partialAddress: PartialAddress): Promise<CompleteAddress> {
@@ -216,7 +237,7 @@ export class PXEService implements PXE {
216
237
  }
217
238
 
218
239
  public async registerContractClass(artifact: ContractArtifact): Promise<void> {
219
- const contractClassId = computeContractClassId(getContractClassFromArtifact(artifact));
240
+ const contractClassId = await computeContractClassId(await getContractClassFromArtifact(artifact));
220
241
  await this.db.addContractArtifact(contractClassId, artifact);
221
242
  this.log.info(`Added contract class ${artifact.name} with id ${contractClassId}`);
222
243
  }
@@ -227,14 +248,15 @@ export class PXEService implements PXE {
227
248
 
228
249
  if (artifact) {
229
250
  // If the user provides an artifact, validate it against the expected class id and register it
230
- const contractClass = getContractClassFromArtifact(artifact);
231
- const contractClassId = computeContractClassId(contractClass);
251
+ const contractClass = await getContractClassFromArtifact(artifact);
252
+ const contractClassId = await computeContractClassId(contractClass);
232
253
  if (!contractClassId.equals(instance.contractClassId)) {
233
254
  throw new Error(
234
255
  `Artifact does not match expected class id (computed ${contractClassId} but instance refers to ${instance.contractClassId})`,
235
256
  );
236
257
  }
237
- if (!computeContractAddressFromInstance(instance).equals(instance.address)) {
258
+ const computedAddress = await computeContractAddressFromInstance(instance);
259
+ if (!computedAddress.equals(instance.address)) {
238
260
  throw new Error('Added a contract in which the address does not match the contract instance.');
239
261
  }
240
262
 
@@ -278,13 +300,15 @@ export class PXEService implements PXE {
278
300
  const extendedNotes = noteDaos.map(async dao => {
279
301
  let owner = filter.owner;
280
302
  if (owner === undefined) {
281
- const completeAddresses = (await this.db.getCompleteAddresses()).find(completeAddress =>
282
- completeAddress.address.toAddressPoint().equals(dao.addressPoint),
283
- );
284
- if (completeAddresses === undefined) {
303
+ const completeAddresses = await this.db.getCompleteAddresses();
304
+ const completeAddressIndex = (
305
+ await Promise.all(completeAddresses.map(completeAddresses => completeAddresses.address.toAddressPoint()))
306
+ ).findIndex(addressPoint => addressPoint.equals(dao.addressPoint));
307
+ const completeAddress = completeAddresses[completeAddressIndex];
308
+ if (completeAddress === undefined) {
285
309
  throw new Error(`Cannot find complete address for addressPoint ${dao.addressPoint.toString()}`);
286
310
  }
287
- owner = completeAddresses.address;
311
+ owner = completeAddress.address;
288
312
  }
289
313
  return new UniqueNote(
290
314
  dao.note,
@@ -337,7 +361,7 @@ export class PXEService implements PXE {
337
361
  throw new Error('Note does not exist.');
338
362
  }
339
363
 
340
- const siloedNullifier = siloNullifier(note.contractAddress, innerNullifier!);
364
+ const siloedNullifier = await siloNullifier(note.contractAddress, innerNullifier!);
341
365
  const [nullifierIndex] = await this.node.findLeavesIndexes('latest', MerkleTreeId.NULLIFIER_TREE, [
342
366
  siloedNullifier,
343
367
  ]);
@@ -357,7 +381,7 @@ export class PXEService implements PXE {
357
381
  l2BlockNumber,
358
382
  l2BlockHash,
359
383
  index,
360
- owner.address.toAddressPoint(),
384
+ await owner.address.toAddressPoint(),
361
385
  note.noteTypeId,
362
386
  ),
363
387
  scope,
@@ -402,7 +426,7 @@ export class PXEService implements PXE {
402
426
  l2BlockNumber,
403
427
  l2BlockHash,
404
428
  index,
405
- note.owner.toAddressPoint(),
429
+ await note.owner.toAddressPoint(),
406
430
  note.noteTypeId,
407
431
  ),
408
432
  );
@@ -430,7 +454,7 @@ export class PXEService implements PXE {
430
454
  break;
431
455
  }
432
456
 
433
- const nonce = computeNoteHashNonce(firstNullifier, i);
457
+ const nonce = await computeNoteHashNonce(firstNullifier, i);
434
458
  const { uniqueNoteHash } = await this.simulator.computeNoteHashAndOptionallyANullifier(
435
459
  note.contractAddress,
436
460
  nonce,
@@ -522,8 +546,9 @@ export class PXEService implements PXE {
522
546
  }
523
547
  }
524
548
 
525
- this.log.info(`Simulation completed for ${simulatedTx.getTxHash()} in ${timer.ms()}ms`, {
526
- txHash: simulatedTx.getTxHash(),
549
+ const txHash = await simulatedTx.getTxHash();
550
+ this.log.info(`Simulation completed for ${txHash.toString()} in ${timer.ms()}ms`, {
551
+ txHash,
527
552
  ...txInfo,
528
553
  ...(profileResult ? { gateCounts: profileResult.gateCounts } : {}),
529
554
  ...(publicOutput
@@ -554,7 +579,7 @@ export class PXEService implements PXE {
554
579
  }
555
580
 
556
581
  public async sendTx(tx: Tx): Promise<TxHash> {
557
- const txHash = tx.getTxHash();
582
+ const txHash = await tx.getTxHash();
558
583
  if (await this.node.getTxEffect(txHash)) {
559
584
  throw new Error(`A settled tx with equal hash ${txHash.toString()} exists.`);
560
585
  }
@@ -608,12 +633,12 @@ export class PXEService implements PXE {
608
633
  }
609
634
 
610
635
  /**
611
- * Gets unencrypted logs based on the provided filter.
636
+ * Gets public logs based on the provided filter.
612
637
  * @param filter - The filter to apply to the logs.
613
638
  * @returns The requested logs.
614
639
  */
615
- public getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
616
- return this.node.getUnencryptedLogs(filter);
640
+ public getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
641
+ return this.node.getPublicLogs(filter);
617
642
  }
618
643
 
619
644
  /**
@@ -621,7 +646,7 @@ export class PXEService implements PXE {
621
646
  * @param filter - The filter to apply to the logs.
622
647
  * @returns The requested logs.
623
648
  */
624
- public getContractClassLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
649
+ public getContractClassLogs(filter: LogFilter): Promise<GetContractClassLogsResponse> {
625
650
  return this.node.getContractClassLogs(filter);
626
651
  }
627
652
 
@@ -641,7 +666,7 @@ export class PXEService implements PXE {
641
666
  return {
642
667
  name: functionDao.name,
643
668
  args: encodeArguments(functionDao, args),
644
- selector: FunctionSelector.fromNameAndParameters(functionDao.name, functionDao.parameters),
669
+ selector: await FunctionSelector.fromNameAndParameters(functionDao.name, functionDao.parameters),
645
670
  type: functionDao.functionType,
646
671
  to,
647
672
  isStatic: functionDao.isStatic,
@@ -687,7 +712,7 @@ export class PXEService implements PXE {
687
712
  async #registerProtocolContracts() {
688
713
  const registered: Record<string, string> = {};
689
714
  for (const name of protocolContractNames) {
690
- const { address, contractClass, instance, artifact } = getCanonicalProtocolContract(name);
715
+ const { address, contractClass, instance, artifact } = await getCanonicalProtocolContract(name);
691
716
  await this.db.addContractArtifact(contractClass.id, artifact);
692
717
  await this.db.addContractInstance(instance);
693
718
  registered[name] = address.toString();
@@ -820,20 +845,20 @@ export class PXEService implements PXE {
820
845
  });
821
846
  }
822
847
 
823
- public async isContractClassPubliclyRegistered(id: Fr): Promise<boolean> {
848
+ async #isContractClassPubliclyRegistered(id: Fr): Promise<boolean> {
824
849
  return !!(await this.node.getContractClass(id));
825
850
  }
826
851
 
827
- public async isContractPubliclyDeployed(address: AztecAddress): Promise<boolean> {
852
+ async #isContractPubliclyDeployed(address: AztecAddress): Promise<boolean> {
828
853
  return !!(await this.node.getContract(address));
829
854
  }
830
855
 
831
- public async isContractInitialized(address: AztecAddress): Promise<boolean> {
832
- const initNullifier = siloNullifier(address, address.toField());
856
+ async #isContractInitialized(address: AztecAddress): Promise<boolean> {
857
+ const initNullifier = await siloNullifier(address, address.toField());
833
858
  return !!(await this.node.getNullifierMembershipWitness('latest', initNullifier));
834
859
  }
835
860
 
836
- public async getEncryptedEvents<T>(
861
+ public async getPrivateEvents<T>(
837
862
  eventMetadataDef: EventMetadataDefinition,
838
863
  from: number,
839
864
  limit: number,
@@ -862,27 +887,31 @@ export class PXEService implements PXE {
862
887
  throw new Error('No registered account');
863
888
  }
864
889
 
865
- const preaddress = registeredAccount.getPreaddress();
890
+ const preaddress = await registeredAccount.getPreaddress();
866
891
 
867
- secretKey = computeAddressSecret(preaddress, secretKey);
892
+ secretKey = await computeAddressSecret(preaddress, secretKey);
868
893
  }
869
894
 
870
895
  return secretKey;
871
896
  }),
872
897
  );
873
898
 
874
- const visibleEvents = privateLogs.flatMap(log => {
875
- for (const sk of vsks) {
876
- // TODO: Verify that the first field of the log is the tag siloed with contract address.
877
- // Or use tags to query logs, like we do with notes.
878
- const decryptedEvent = L1EventPayload.decryptAsIncoming(log, sk);
879
- if (decryptedEvent !== undefined) {
880
- return [decryptedEvent];
881
- }
882
- }
899
+ const visibleEvents = (
900
+ await Promise.all(
901
+ privateLogs.map(async log => {
902
+ for (const sk of vsks) {
903
+ // TODO: Verify that the first field of the log is the tag siloed with contract address.
904
+ // Or use tags to query logs, like we do with notes.
905
+ const decryptedEvent = await L1EventPayload.decryptAsIncoming(log, sk);
906
+ if (decryptedEvent !== undefined) {
907
+ return [decryptedEvent];
908
+ }
909
+ }
883
910
 
884
- return [];
885
- });
911
+ return [];
912
+ }),
913
+ )
914
+ ).flat();
886
915
 
887
916
  const decodedEvents = visibleEvents
888
917
  .map(visibleEvent => {
@@ -892,11 +921,6 @@ export class PXEService implements PXE {
892
921
  if (!visibleEvent.eventTypeId.equals(eventMetadata.eventSelector)) {
893
922
  return undefined;
894
923
  }
895
- if (visibleEvent.event.items.length !== eventMetadata.fieldNames.length) {
896
- throw new Error(
897
- 'Something is weird here, we have matching EventSelectors, but the actual payload has mismatched length',
898
- );
899
- }
900
924
 
901
925
  return eventMetadata.decode(visibleEvent);
902
926
  })
@@ -905,34 +929,32 @@ export class PXEService implements PXE {
905
929
  return decodedEvents;
906
930
  }
907
931
 
908
- async getUnencryptedEvents<T>(eventMetadataDef: EventMetadataDefinition, from: number, limit: number): Promise<T[]> {
932
+ async getPublicEvents<T>(eventMetadataDef: EventMetadataDefinition, from: number, limit: number): Promise<T[]> {
909
933
  const eventMetadata = new EventMetadata<T>(eventMetadataDef);
910
- const { logs: unencryptedLogs } = await this.node.getUnencryptedLogs({
934
+ const { logs } = await this.node.getPublicLogs({
911
935
  fromBlock: from,
912
936
  toBlock: from + limit,
913
937
  });
914
938
 
915
- const decodedEvents = unencryptedLogs
916
- .map(unencryptedLog => {
917
- const unencryptedLogBuf = unencryptedLog.log.data;
939
+ const decodedEvents = logs
940
+ .map(log => {
941
+ // +1 for the event selector
942
+ const expectedLength = eventMetadata.fieldNames.length + 1;
943
+ const logFields = log.log.log.slice(0, expectedLength);
918
944
  // We are assuming here that event logs are the last 4 bytes of the event. This is not enshrined but is a function of aztec.nr raw log emission.
919
- if (
920
- !EventSelector.fromBuffer(unencryptedLogBuf.subarray(unencryptedLogBuf.byteLength - 4)).equals(
921
- eventMetadata.eventSelector,
922
- )
923
- ) {
945
+ if (!EventSelector.fromField(logFields[logFields.length - 1]).equals(eventMetadata.eventSelector)) {
924
946
  return undefined;
925
947
  }
926
-
927
- if (unencryptedLogBuf.byteLength !== eventMetadata.fieldNames.length * 32 + 32) {
948
+ // If any of the remaining fields, are non-zero, the payload does match expected:
949
+ if (log.log.log.slice(expectedLength + 1).find(f => !f.isZero())) {
928
950
  throw new Error(
929
951
  'Something is weird here, we have matching EventSelectors, but the actual payload has mismatched length',
930
952
  );
931
953
  }
932
954
 
933
- return eventMetadata.decode(unencryptedLog.log);
955
+ return eventMetadata.decode(log.log);
934
956
  })
935
- .filter(unencryptedLog => unencryptedLog !== undefined) as T[];
957
+ .filter(log => log !== undefined) as T[];
936
958
 
937
959
  return decodedEvents;
938
960
  }
@@ -35,7 +35,7 @@ export const pxeTestSuite = (testName: string, pxeSetup: () => Promise<PXE>) =>
35
35
  });
36
36
 
37
37
  it('successfully adds a contract', async () => {
38
- const contracts = [randomDeployedContract(), randomDeployedContract()];
38
+ const contracts = await Promise.all([randomDeployedContract(), randomDeployedContract()]);
39
39
  for (const contract of contracts) {
40
40
  await pxe.registerContract(contract);
41
41
  }
@@ -47,29 +47,29 @@ export const pxeTestSuite = (testName: string, pxeSetup: () => Promise<PXE>) =>
47
47
 
48
48
  it('registers a class and adds a contract for it', async () => {
49
49
  const artifact = randomContractArtifact();
50
- const contractClass = getContractClassFromArtifact(artifact);
50
+ const contractClass = await getContractClassFromArtifact(artifact);
51
51
  const contractClassId = contractClass.id;
52
- const instance = randomContractInstanceWithAddress({ contractClassId });
52
+ const instance = await randomContractInstanceWithAddress({ contractClassId });
53
53
 
54
54
  await pxe.registerContractClass(artifact);
55
- expect(await pxe.getContractClass(contractClassId)).toMatchObject(
55
+ expect((await pxe.getContractClassMetadata(contractClassId)).contractClass).toMatchObject(
56
56
  omit(contractClass, 'privateFunctionsRoot', 'publicBytecodeCommitment'),
57
57
  );
58
58
 
59
59
  await pxe.registerContract({ instance });
60
- expect(await pxe.getContractInstance(instance.address)).toEqual(instance);
60
+ expect((await pxe.getContractMetadata(instance.address)).contractInstance).toEqual(instance);
61
61
  });
62
62
 
63
63
  it('refuses to register a class with a mismatched address', async () => {
64
64
  const artifact = randomContractArtifact();
65
- const contractClass = getContractClassFromArtifact(artifact);
65
+ const contractClass = await getContractClassFromArtifact(artifact);
66
66
  const contractClassId = contractClass.id;
67
- const instance = randomContractInstanceWithAddress({ contractClassId });
67
+ const instance = await randomContractInstanceWithAddress({ contractClassId });
68
68
  await expect(
69
69
  pxe.registerContract({
70
70
  instance: {
71
71
  ...instance,
72
- address: AztecAddress.random(),
72
+ address: await AztecAddress.random(),
73
73
  },
74
74
  artifact,
75
75
  }),
@@ -77,13 +77,13 @@ export const pxeTestSuite = (testName: string, pxeSetup: () => Promise<PXE>) =>
77
77
  });
78
78
 
79
79
  it('refuses to register a contract with a class that has not been registered', async () => {
80
- const instance = randomContractInstanceWithAddress();
80
+ const instance = await randomContractInstanceWithAddress();
81
81
  await expect(pxe.registerContract({ instance })).rejects.toThrow(/Missing contract artifact/i);
82
82
  });
83
83
 
84
84
  it('refuses to register a contract with an artifact with mismatching class id', async () => {
85
85
  const artifact = randomContractArtifact();
86
- const instance = randomContractInstanceWithAddress();
86
+ const instance = await randomContractInstanceWithAddress();
87
87
  await expect(pxe.registerContract({ instance, artifact })).rejects.toThrow(/Artifact does not match/i);
88
88
  });
89
89
 
@@ -91,13 +91,13 @@ export const pxeTestSuite = (testName: string, pxeSetup: () => Promise<PXE>) =>
91
91
  // a larger setup and it's sufficiently tested in the e2e tests.
92
92
 
93
93
  it('throws when getting public storage for non-existent contract', async () => {
94
- const contract = AztecAddress.random();
94
+ const contract = await AztecAddress.random();
95
95
  await expect(async () => await pxe.getPublicStorageAt(contract, new Fr(0n))).rejects.toThrow(
96
96
  `Contract ${contract.toString()} is not deployed`,
97
97
  );
98
98
  });
99
99
 
100
- // Note: Not testing `getContractData` and `getUnencryptedLogs` here as these
100
+ // Note: Not testing `getContractData` and `getPublicLogs` here as these
101
101
  // functions only call AztecNode and these methods are frequently used by the e2e tests.
102
102
 
103
103
  it('successfully gets a block number', async () => {
@@ -27,6 +27,7 @@ import {
27
27
  MAX_NOTE_HASHES_PER_TX,
28
28
  PRIVATE_LOG_SIZE_IN_FIELDS,
29
29
  PrivateLog,
30
+ PublicLog,
30
31
  computeAddressSecret,
31
32
  computeTaggingSecretPoint,
32
33
  } from '@aztec/circuits.js';
@@ -38,6 +39,7 @@ import {
38
39
  encodeArguments,
39
40
  getFunctionArtifact,
40
41
  } from '@aztec/foundation/abi';
42
+ import { timesParallel } from '@aztec/foundation/collection';
41
43
  import { poseidon2Hash } from '@aztec/foundation/crypto';
42
44
  import { createLogger } from '@aztec/foundation/log';
43
45
  import { type KeyStore } from '@aztec/key-store';
@@ -329,7 +331,7 @@ export class SimulatorOracle implements DBOracle {
329
331
  async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
330
332
  const senderCompleteAddress = await this.getCompleteAddress(sender);
331
333
  const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
332
- const secretPoint = computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
334
+ const secretPoint = await computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
333
335
  // Silo the secret so it can't be used to track other app's notes
334
336
  const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]);
335
337
  return appSecret;
@@ -356,10 +358,12 @@ export class SimulatorOracle implements DBOracle {
356
358
  const senders = [...(await this.db.getSenderAddresses()), ...(await this.keyStore.getAccounts())].filter(
357
359
  (address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address)),
358
360
  );
359
- const appTaggingSecrets = senders.map(contact => {
360
- const sharedSecret = computeTaggingSecretPoint(recipientCompleteAddress, recipientIvsk, contact);
361
- return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
362
- });
361
+ const appTaggingSecrets = await Promise.all(
362
+ senders.map(async contact => {
363
+ const sharedSecret = await computeTaggingSecretPoint(recipientCompleteAddress, recipientIvsk, contact);
364
+ return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
365
+ }),
366
+ );
363
367
  const indexes = await this.db.getTaggingSecretsIndexesAsRecipient(appTaggingSecrets);
364
368
  return appTaggingSecrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i]));
365
369
  }
@@ -390,7 +394,7 @@ export class SimulatorOracle implements DBOracle {
390
394
  let [numConsecutiveEmptyLogs, currentIndex] = [0, oldIndex];
391
395
  do {
392
396
  // We compute the tags for the current window of indexes
393
- const currentTags = [...new Array(WINDOW_SIZE)].map((_, i) => {
397
+ const currentTags = await timesParallel(WINDOW_SIZE, i => {
394
398
  const indexedAppTaggingSecret = new IndexedTaggingSecret(appTaggingSecret, currentIndex + i);
395
399
  return indexedAppTaggingSecret.computeSiloedTag(recipient, contractAddress);
396
400
  });
@@ -486,8 +490,8 @@ export class SimulatorOracle implements DBOracle {
486
490
 
487
491
  while (secretsAndWindows.length > 0) {
488
492
  const secretsForTheWholeWindow = getIndexedTaggingSecretsForTheWindow(secretsAndWindows);
489
- const tagsForTheWholeWindow = secretsForTheWholeWindow.map(secret =>
490
- secret.computeSiloedTag(recipient, contractAddress),
493
+ const tagsForTheWholeWindow = await Promise.all(
494
+ secretsForTheWholeWindow.map(secret => secret.computeSiloedTag(recipient, contractAddress)),
491
495
  );
492
496
 
493
497
  // We store the new largest indexes we find in the iteration in the following map to later on construct
@@ -499,15 +503,31 @@ export class SimulatorOracle implements DBOracle {
499
503
 
500
504
  logsByTags.forEach((logsByTag, logIndex) => {
501
505
  if (logsByTag.length > 0) {
506
+ // Check that public logs have the correct contract address
507
+ const checkedLogsbyTag = logsByTag.filter(
508
+ l => !l.isFromPublic || PublicLog.fromBuffer(l.logData).contractAddress.equals(contractAddress),
509
+ );
510
+ if (checkedLogsbyTag.length < logsByTag.length) {
511
+ const discarded = logsByTag.filter(
512
+ log => checkedLogsbyTag.find(filteredLog => filteredLog.equals(log)) === undefined,
513
+ );
514
+ this.log.warn(
515
+ `Discarded ${
516
+ logsByTag.length - checkedLogsbyTag.length
517
+ } public logs with mismatched contract address ${contractAddress}:`,
518
+ discarded.map(l => PublicLog.fromBuffer(l.logData)),
519
+ );
520
+ }
521
+
502
522
  // The logs for the given tag exist so we store them for later processing
503
- logsForRecipient.push(...logsByTag);
523
+ logsForRecipient.push(...checkedLogsbyTag);
504
524
 
505
525
  // We retrieve the indexed tagging secret corresponding to the log as I need that to evaluate whether
506
526
  // a new largest index have been found.
507
527
  const secretCorrespondingToLog = secretsForTheWholeWindow[logIndex];
508
528
  const initialIndex = initialIndexesMap[secretCorrespondingToLog.appTaggingSecret.toString()];
509
529
 
510
- this.log.debug(`Found ${logsByTag.length} logs as recipient ${recipient}`, {
530
+ this.log.debug(`Found ${checkedLogsbyTag.length} logs as recipient ${recipient}`, {
511
531
  recipient,
512
532
  secret: secretCorrespondingToLog.appTaggingSecret,
513
533
  contractName,
@@ -588,7 +608,7 @@ export class SimulatorOracle implements DBOracle {
588
608
  const ivskM = await this.keyStore.getMasterSecretKey(
589
609
  recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey,
590
610
  );
591
- const addressSecret = computeAddressSecret(recipientCompleteAddress.getPreaddress(), ivskM);
611
+ const addressSecret = await computeAddressSecret(await recipientCompleteAddress.getPreaddress(), ivskM);
592
612
 
593
613
  // Since we could have notes with the same index for different txs, we need
594
614
  // to keep track of them scoping by txHash
@@ -597,8 +617,8 @@ export class SimulatorOracle implements DBOracle {
597
617
 
598
618
  for (const scopedLog of scopedLogs) {
599
619
  const payload = scopedLog.isFromPublic
600
- ? L1NotePayload.decryptAsIncomingFromPublic(scopedLog.logData, addressSecret)
601
- : L1NotePayload.decryptAsIncoming(PrivateLog.fromBuffer(scopedLog.logData), addressSecret);
620
+ ? await L1NotePayload.decryptAsIncomingFromPublic(PublicLog.fromBuffer(scopedLog.logData), addressSecret)
621
+ : await L1NotePayload.decryptAsIncoming(PrivateLog.fromBuffer(scopedLog.logData), addressSecret);
602
622
 
603
623
  if (!payload) {
604
624
  this.log.verbose('Unable to decrypt log');
@@ -703,7 +723,7 @@ export class SimulatorOracle implements DBOracle {
703
723
  })
704
724
  .filter(nullifier => nullifier !== undefined) as InBlock<Fr>[];
705
725
 
706
- const nullifiedNotes = await this.db.removeNullifiedNotes(foundNullifiers, recipient.toAddressPoint());
726
+ const nullifiedNotes = await this.db.removeNullifiedNotes(foundNullifiers, await recipient.toAddressPoint());
707
727
  nullifiedNotes.forEach(noteDao => {
708
728
  this.log.verbose(`Removed note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`, {
709
729
  contract: noteDao.contractAddress,
@@ -724,21 +744,30 @@ export class SimulatorOracle implements DBOracle {
724
744
  txHash: Fr,
725
745
  recipient: AztecAddress,
726
746
  ): Promise<NoteDao> {
747
+ // We need to validate that the note does indeed exist in the world state to avoid adding notes that are then
748
+ // impossible to prove.
749
+
727
750
  const receipt = await this.aztecNode.getTxReceipt(new TxHash(txHash));
728
751
  if (receipt === undefined) {
729
752
  throw new Error(`Failed to fetch tx receipt for tx hash ${txHash} when searching for note hashes`);
730
753
  }
731
- const { blockNumber, blockHash } = receipt;
732
754
 
733
- const uniqueNoteHash = computeUniqueNoteHash(nonce, siloNoteHash(contractAddress, noteHash));
734
- const siloedNullifier = siloNullifier(contractAddress, nullifier);
755
+ // Siloed and unique hashes are computed by us instead of relying on values sent by the contract to make sure
756
+ // we're not e.g. storing notes that belong to some other contract, which would constitute a security breach.
757
+ const uniqueNoteHash = await computeUniqueNoteHash(nonce, await siloNoteHash(contractAddress, noteHash));
758
+ const siloedNullifier = await siloNullifier(contractAddress, nullifier);
735
759
 
760
+ // We store notes by their index in the global note hash tree, which has the convenient side effect of validating
761
+ // note existence in said tree. Note that while this is technically a historical query, we perform it at the latest
762
+ // locally synced block number which *should* be recent enough to be available. We avoid querying at 'latest' since
763
+ // we want to avoid accidentally processing notes that only exist ahead in time of the locally synced state.
764
+ const syncedBlockNumber = await this.db.getBlockNumber();
736
765
  const uniqueNoteHashTreeIndex = (
737
- await this.aztecNode.findLeavesIndexes(blockNumber!, MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash])
766
+ await this.aztecNode.findLeavesIndexes(syncedBlockNumber!, MerkleTreeId.NOTE_HASH_TREE, [uniqueNoteHash])
738
767
  )[0];
739
768
  if (uniqueNoteHashTreeIndex === undefined) {
740
769
  throw new Error(
741
- `Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present on the tree at block ${blockNumber} (from tx ${txHash})`,
770
+ `Note hash ${noteHash} (uniqued as ${uniqueNoteHash}) is not present on the tree at block ${syncedBlockNumber} (from tx ${txHash})`,
742
771
  );
743
772
  }
744
773
 
@@ -750,10 +779,10 @@ export class SimulatorOracle implements DBOracle {
750
779
  noteHash,
751
780
  siloedNullifier,
752
781
  new TxHash(txHash),
753
- blockNumber!,
754
- blockHash!.toString(),
782
+ receipt.blockNumber!,
783
+ receipt.blockHash!.toString(),
755
784
  uniqueNoteHashTreeIndex,
756
- recipient.toAddressPoint(),
785
+ await recipient.toAddressPoint(),
757
786
  NoteSelector.empty(), // todo: remove
758
787
  );
759
788
  }
@@ -780,7 +809,7 @@ export class SimulatorOracle implements DBOracle {
780
809
  const execRequest: FunctionCall = {
781
810
  name: artifact.name,
782
811
  to: contractAddress,
783
- selector: FunctionSelector.fromNameAndParameters(artifact),
812
+ selector: await FunctionSelector.fromNameAndParameters(artifact),
784
813
  type: FunctionType.UNCONSTRAINED,
785
814
  isStatic: artifact.isStatic,
786
815
  args: encodeArguments(artifact, [