@aztec/pxe 0.66.0 → 0.67.1-devnet

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 (100) hide show
  1. package/dest/bin/index.js +5 -6
  2. package/dest/config/index.d.ts +0 -9
  3. package/dest/config/index.d.ts.map +1 -1
  4. package/dest/config/index.js +1 -17
  5. package/dest/config/package_info.d.ts +5 -0
  6. package/dest/config/package_info.d.ts.map +1 -0
  7. package/dest/config/package_info.js +4 -0
  8. package/dest/contract_data_oracle/index.js +2 -2
  9. package/dest/database/incoming_note_dao.d.ts +1 -1
  10. package/dest/database/incoming_note_dao.d.ts.map +1 -1
  11. package/dest/database/incoming_note_dao.js +2 -2
  12. package/dest/database/kv_pxe_database.d.ts +10 -13
  13. package/dest/database/kv_pxe_database.d.ts.map +1 -1
  14. package/dest/database/kv_pxe_database.js +153 -230
  15. package/dest/database/outgoing_note_dao.js +2 -2
  16. package/dest/database/pxe_database.d.ts +7 -25
  17. package/dest/database/pxe_database.d.ts.map +1 -1
  18. package/dest/database/pxe_database_test_suite.d.ts.map +1 -1
  19. package/dest/database/pxe_database_test_suite.js +18 -59
  20. package/dest/index.d.ts +2 -0
  21. package/dest/index.d.ts.map +1 -1
  22. package/dest/index.js +3 -1
  23. package/dest/kernel_oracle/index.d.ts.map +1 -1
  24. package/dest/kernel_oracle/index.js +4 -4
  25. package/dest/kernel_prover/hints/build_private_kernel_reset_private_inputs.js +2 -2
  26. package/dest/kernel_prover/index.d.ts +1 -0
  27. package/dest/kernel_prover/index.d.ts.map +1 -1
  28. package/dest/kernel_prover/index.js +2 -1
  29. package/dest/kernel_prover/kernel_prover.d.ts +1 -0
  30. package/dest/kernel_prover/kernel_prover.d.ts.map +1 -1
  31. package/dest/kernel_prover/kernel_prover.js +42 -5
  32. package/dest/kernel_prover/test/test_circuit_prover.d.ts +1 -2
  33. package/dest/kernel_prover/test/test_circuit_prover.d.ts.map +1 -1
  34. package/dest/kernel_prover/test/test_circuit_prover.js +7 -13
  35. package/dest/note_decryption_utils/add_public_values_to_payload.js +2 -2
  36. package/dest/note_decryption_utils/brute_force_note_info.d.ts +3 -3
  37. package/dest/note_decryption_utils/brute_force_note_info.d.ts.map +1 -1
  38. package/dest/note_decryption_utils/brute_force_note_info.js +8 -8
  39. package/dest/note_decryption_utils/produce_note_daos.d.ts +3 -6
  40. package/dest/note_decryption_utils/produce_note_daos.d.ts.map +1 -1
  41. package/dest/note_decryption_utils/produce_note_daos.js +5 -19
  42. package/dest/note_decryption_utils/produce_note_daos_for_key.d.ts +1 -1
  43. package/dest/note_decryption_utils/produce_note_daos_for_key.d.ts.map +1 -1
  44. package/dest/pxe_service/error_enriching.d.ts +3 -3
  45. package/dest/pxe_service/error_enriching.d.ts.map +1 -1
  46. package/dest/pxe_service/error_enriching.js +10 -10
  47. package/dest/pxe_service/index.d.ts +0 -1
  48. package/dest/pxe_service/index.d.ts.map +1 -1
  49. package/dest/pxe_service/index.js +1 -2
  50. package/dest/pxe_service/pxe_service.d.ts +2 -16
  51. package/dest/pxe_service/pxe_service.d.ts.map +1 -1
  52. package/dest/pxe_service/pxe_service.js +74 -96
  53. package/dest/pxe_service/test/pxe_test_suite.d.ts.map +1 -1
  54. package/dest/pxe_service/test/pxe_test_suite.js +1 -3
  55. package/dest/simulator/index.d.ts +1 -1
  56. package/dest/simulator/index.d.ts.map +1 -1
  57. package/dest/simulator/index.js +2 -2
  58. package/dest/simulator_oracle/index.d.ts +14 -11
  59. package/dest/simulator_oracle/index.d.ts.map +1 -1
  60. package/dest/simulator_oracle/index.js +170 -140
  61. package/dest/simulator_oracle/tagging_utils.d.ts +15 -0
  62. package/dest/simulator_oracle/tagging_utils.d.ts.map +1 -0
  63. package/dest/simulator_oracle/tagging_utils.js +23 -0
  64. package/dest/synchronizer/synchronizer.d.ts +10 -40
  65. package/dest/synchronizer/synchronizer.d.ts.map +1 -1
  66. package/dest/synchronizer/synchronizer.js +35 -69
  67. package/dest/{pxe_service → utils}/create_pxe_service.d.ts +1 -1
  68. package/dest/utils/create_pxe_service.d.ts.map +1 -0
  69. package/dest/utils/create_pxe_service.js +52 -0
  70. package/package.json +31 -19
  71. package/src/bin/index.ts +4 -5
  72. package/src/config/index.ts +0 -21
  73. package/src/config/package_info.ts +3 -0
  74. package/src/contract_data_oracle/index.ts +1 -1
  75. package/src/database/incoming_note_dao.ts +2 -2
  76. package/src/database/kv_pxe_database.ts +214 -309
  77. package/src/database/outgoing_note_dao.ts +1 -1
  78. package/src/database/pxe_database.ts +7 -28
  79. package/src/database/pxe_database_test_suite.ts +20 -75
  80. package/src/index.ts +2 -0
  81. package/src/kernel_oracle/index.ts +3 -3
  82. package/src/kernel_prover/hints/build_private_kernel_reset_private_inputs.ts +1 -1
  83. package/src/kernel_prover/index.ts +2 -0
  84. package/src/kernel_prover/kernel_prover.ts +72 -3
  85. package/src/kernel_prover/test/test_circuit_prover.ts +11 -25
  86. package/src/note_decryption_utils/add_public_values_to_payload.ts +1 -1
  87. package/src/note_decryption_utils/brute_force_note_info.ts +9 -9
  88. package/src/note_decryption_utils/produce_note_daos.ts +5 -48
  89. package/src/note_decryption_utils/produce_note_daos_for_key.ts +1 -1
  90. package/src/pxe_service/error_enriching.ts +14 -12
  91. package/src/pxe_service/index.ts +0 -1
  92. package/src/pxe_service/pxe_service.ts +127 -174
  93. package/src/pxe_service/test/pxe_test_suite.ts +0 -3
  94. package/src/simulator/index.ts +1 -1
  95. package/src/simulator_oracle/index.ts +201 -188
  96. package/src/simulator_oracle/tagging_utils.ts +28 -0
  97. package/src/synchronizer/synchronizer.ts +37 -77
  98. package/src/{pxe_service → utils}/create_pxe_service.ts +16 -13
  99. package/dest/pxe_service/create_pxe_service.d.ts.map +0 -1
  100. package/dest/pxe_service/create_pxe_service.js +0 -49
@@ -14,31 +14,31 @@ import {
14
14
  } from '@aztec/circuit-types';
15
15
  import {
16
16
  type AztecAddress,
17
+ type BlockHeader,
17
18
  type CompleteAddress,
18
19
  type ContractInstance,
19
20
  Fr,
20
21
  type FunctionSelector,
21
- type Header,
22
22
  IndexedTaggingSecret,
23
23
  type KeyValidationRequest,
24
24
  type L1_TO_L2_MSG_TREE_HEIGHT,
25
25
  PrivateLog,
26
26
  computeAddressSecret,
27
- computeTaggingSecret,
27
+ computeTaggingSecretPoint,
28
28
  } from '@aztec/circuits.js';
29
29
  import { type FunctionArtifact, getFunctionArtifact } from '@aztec/foundation/abi';
30
30
  import { poseidon2Hash } from '@aztec/foundation/crypto';
31
- import { tryJsonStringify } from '@aztec/foundation/json-rpc';
32
- import { createDebugLogger } from '@aztec/foundation/log';
31
+ import { createLogger } from '@aztec/foundation/log';
33
32
  import { type KeyStore } from '@aztec/key-store';
34
- import { type AcirSimulator, type DBOracle, MessageLoadOracleInputs } from '@aztec/simulator';
33
+ import { MessageLoadOracleInputs } from '@aztec/simulator/acvm';
34
+ import { type AcirSimulator, type DBOracle } from '@aztec/simulator/client';
35
35
 
36
36
  import { type ContractDataOracle } from '../contract_data_oracle/index.js';
37
37
  import { type IncomingNoteDao } from '../database/incoming_note_dao.js';
38
38
  import { type PxeDatabase } from '../database/index.js';
39
- import { type OutgoingNoteDao } from '../database/outgoing_note_dao.js';
40
39
  import { produceNoteDaos } from '../note_decryption_utils/produce_note_daos.js';
41
40
  import { getAcirSimulator } from '../simulator/index.js';
41
+ import { getIndexedTaggingSecretsForTheWindow, getInitialIndexesMap } from './tagging_utils.js';
42
42
 
43
43
  /**
44
44
  * A data oracle that provides information needed for simulating a transaction.
@@ -49,7 +49,7 @@ export class SimulatorOracle implements DBOracle {
49
49
  private db: PxeDatabase,
50
50
  private keyStore: KeyStore,
51
51
  private aztecNode: AztecNode,
52
- private log = createDebugLogger('aztec:pxe:simulator_oracle'),
52
+ private log = createLogger('pxe:simulator_oracle'),
53
53
  ) {}
54
54
 
55
55
  getKeyValidationRequest(pkMHash: Fr, contractAddress: AztecAddress): Promise<KeyValidationRequest> {
@@ -229,10 +229,10 @@ export class SimulatorOracle implements DBOracle {
229
229
  * Retrieve the databases view of the Block Header object.
230
230
  * This structure is fed into the circuits simulator and is used to prove against certain historical roots.
231
231
  *
232
- * @returns A Promise that resolves to a Header object.
232
+ * @returns A Promise that resolves to a BlockHeader object.
233
233
  */
234
- getHeader(): Promise<Header> {
235
- return Promise.resolve(this.db.getHeader());
234
+ getBlockHeader(): Promise<BlockHeader> {
235
+ return this.db.getBlockHeader();
236
236
  }
237
237
 
238
238
  /**
@@ -253,33 +253,33 @@ export class SimulatorOracle implements DBOracle {
253
253
  * finally the index specified tag. We will then query the node with this tag for each address in the address book.
254
254
  * @returns The full list of the users contact addresses.
255
255
  */
256
- public getContacts(): AztecAddress[] {
256
+ public getContacts(): Promise<AztecAddress[]> {
257
257
  return this.db.getContactAddresses();
258
258
  }
259
259
 
260
260
  /**
261
- * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known.
261
+ * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivsk_m of the sender must be known.
262
262
  * Includes the next index to be used used for tagging with this secret.
263
263
  * @param contractAddress - The contract address to silo the secret for
264
264
  * @param sender - The address sending the note
265
265
  * @param recipient - The address receiving the note
266
- * @returns A siloed tagging secret that can be used to tag notes.
266
+ * @returns An indexed tagging secret that can be used to tag notes.
267
267
  */
268
- public async getAppTaggingSecretAsSender(
268
+ public async getIndexedTaggingSecretAsSender(
269
269
  contractAddress: AztecAddress,
270
270
  sender: AztecAddress,
271
271
  recipient: AztecAddress,
272
272
  ): Promise<IndexedTaggingSecret> {
273
273
  await this.syncTaggedLogsAsSender(contractAddress, sender, recipient);
274
274
 
275
- const secret = await this.#calculateTaggingSecret(contractAddress, sender, recipient);
276
- const [index] = await this.db.getTaggingSecretsIndexesAsSender([secret]);
275
+ const appTaggingSecret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient);
276
+ const [index] = await this.db.getTaggingSecretsIndexesAsSender([appTaggingSecret]);
277
277
 
278
- return new IndexedTaggingSecret(secret, index);
278
+ return new IndexedTaggingSecret(appTaggingSecret, index);
279
279
  }
280
280
 
281
281
  /**
282
- * Increments the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known.
282
+ * Increments the tagging secret for a given sender and recipient pair. For this to work, the ivsk_m of the sender must be known.
283
283
  * @param contractAddress - The contract address to silo the secret for
284
284
  * @param sender - The address sending the note
285
285
  * @param recipient - The address receiving the note
@@ -289,35 +289,39 @@ export class SimulatorOracle implements DBOracle {
289
289
  sender: AztecAddress,
290
290
  recipient: AztecAddress,
291
291
  ): Promise<void> {
292
- const secret = await this.#calculateTaggingSecret(contractAddress, sender, recipient);
292
+ const secret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient);
293
293
  const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
294
- this.log.verbose(
295
- `Incrementing secret ${secret} as sender ${sender} for recipient: ${recipient} at contract: ${contractName}(${contractAddress})`,
296
- );
294
+ this.log.debug(`Incrementing app tagging secret at ${contractName}(${contractAddress})`, {
295
+ secret,
296
+ sender,
297
+ recipient,
298
+ contractName,
299
+ contractAddress,
300
+ });
297
301
 
298
302
  const [index] = await this.db.getTaggingSecretsIndexesAsSender([secret]);
299
303
  await this.db.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(secret, index + 1)]);
300
304
  }
301
305
 
302
- async #calculateTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
306
+ async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
303
307
  const senderCompleteAddress = await this.getCompleteAddress(sender);
304
308
  const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
305
- const sharedSecret = computeTaggingSecret(senderCompleteAddress, senderIvsk, recipient);
306
- // Silo the secret to the app so it can't be used to track other app's notes
307
- const siloedSecret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
308
- return siloedSecret;
309
+ const secretPoint = computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
310
+ // Silo the secret so it can't be used to track other app's notes
311
+ const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]);
312
+ return appSecret;
309
313
  }
310
314
 
311
315
  /**
312
- * Returns the siloed tagging secrets for a given recipient and all the senders in the address book
316
+ * Returns the indexed tagging secrets for a given recipient and all the senders in the address book
313
317
  * This method should be exposed as an oracle call to allow aztec.nr to perform the orchestration
314
318
  * of the syncTaggedLogs and processTaggedLogs methods. However, it is not possible to do so at the moment,
315
319
  * so we're keeping it private for now.
316
320
  * @param contractAddress - The contract address to silo the secret for
317
321
  * @param recipient - The address receiving the notes
318
- * @returns A list of siloed tagging secrets
322
+ * @returns A list of indexed tagging secrets
319
323
  */
320
- async #getAppTaggingSecretsForContacts(
324
+ async #getIndexedTaggingSecretsForContacts(
321
325
  contractAddress: AztecAddress,
322
326
  recipient: AztecAddress,
323
327
  ): Promise<IndexedTaggingSecret[]> {
@@ -325,11 +329,11 @@ export class SimulatorOracle implements DBOracle {
325
329
  const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient);
326
330
 
327
331
  // We implicitly add all PXE accounts as contacts, this helps us decrypt tags on notes that we send to ourselves (recipient = us, sender = us)
328
- const contacts = [...this.db.getContactAddresses(), ...(await this.keyStore.getAccounts())].filter(
332
+ const contacts = [...(await this.db.getContactAddresses()), ...(await this.keyStore.getAccounts())].filter(
329
333
  (address, index, self) => index === self.findIndex(otherAddress => otherAddress.equals(address)),
330
334
  );
331
335
  const appTaggingSecrets = contacts.map(contact => {
332
- const sharedSecret = computeTaggingSecret(recipientCompleteAddress, recipientIvsk, contact);
336
+ const sharedSecret = computeTaggingSecretPoint(recipientCompleteAddress, recipientIvsk, contact);
333
337
  return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
334
338
  });
335
339
  const indexes = await this.db.getTaggingSecretsIndexesAsRecipient(appTaggingSecrets);
@@ -348,7 +352,7 @@ export class SimulatorOracle implements DBOracle {
348
352
  sender: AztecAddress,
349
353
  recipient: AztecAddress,
350
354
  ): Promise<void> {
351
- const appTaggingSecret = await this.#calculateTaggingSecret(contractAddress, sender, recipient);
355
+ const appTaggingSecret = await this.#calculateAppTaggingSecret(contractAddress, sender, recipient);
352
356
  let [currentIndex] = await this.db.getTaggingSecretsIndexesAsSender([appTaggingSecret]);
353
357
 
354
358
  const INDEX_OFFSET = 10;
@@ -395,14 +399,19 @@ export class SimulatorOracle implements DBOracle {
395
399
  await this.db.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(appTaggingSecret, newIndex)]);
396
400
 
397
401
  const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
398
- this.log.debug(
399
- `Syncing logs for sender ${sender}, secret ${appTaggingSecret}:${currentIndex} at contract: ${contractName}(${contractAddress})`,
400
- );
402
+ this.log.debug(`Syncing logs for sender ${sender} at contract ${contractName}(${contractAddress})`, {
403
+ sender,
404
+ secret: appTaggingSecret,
405
+ index: currentIndex,
406
+ contractName,
407
+ contractAddress,
408
+ });
401
409
  }
402
410
 
403
411
  /**
404
- * Synchronizes the logs tagged with scoped addresses and all the senders in the addressbook.
405
- * Returns the unsynched logs and updates the indexes of the secrets used to tag them until there are no more logs to sync.
412
+ * Synchronizes the logs tagged with scoped addresses and all the senders in the address book.
413
+ * Returns the unsynched logs and updates the indexes of the secrets used to tag them until there are no more logs
414
+ * to sync.
406
415
  * @param contractAddress - The address of the contract that the logs are tagged for
407
416
  * @param recipient - The address of the recipient
408
417
  * @returns A list of encrypted logs tagged with the recipient's address
@@ -412,117 +421,140 @@ export class SimulatorOracle implements DBOracle {
412
421
  maxBlockNumber: number,
413
422
  scopes?: AztecAddress[],
414
423
  ): Promise<Map<string, TxScopedL2Log[]>> {
424
+ // Half the size of the window we slide over the tagging secret indexes.
425
+ const WINDOW_HALF_SIZE = 10;
426
+
427
+ // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles.
428
+ // However it is impossible at the moment due to the language not supporting nested slices.
429
+ // This nesting is necessary because for a given set of tags we don't
430
+ // know how many logs we will get back. Furthermore, these logs are of undetermined
431
+ // length, since we don't really know the note they correspond to until we decrypt them.
432
+
415
433
  const recipients = scopes ? scopes : await this.keyStore.getAccounts();
416
- const result = new Map<string, TxScopedL2Log[]>();
434
+ // A map of logs going from recipient address to logs. Note that the logs might have been processed before
435
+ // due to us having a sliding window that "looks back" for logs as well. (We look back as there is no guarantee
436
+ // that a logs will be received ordered by a given tax index and that the tags won't be reused).
437
+ const logsMap = new Map<string, TxScopedL2Log[]>();
417
438
  const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
418
439
  for (const recipient of recipients) {
419
- const logs: TxScopedL2Log[] = [];
420
- // Ideally this algorithm would be implemented in noir, exposing its building blocks as oracles.
421
- // However it is impossible at the moment due to the language not supporting nested slices.
422
- // This nesting is necessary because for a given set of tags we don't
423
- // know how many logs we will get back. Furthermore, these logs are of undetermined
424
- // length, since we don't really know the note they correspond to until we decrypt them.
425
-
426
- // 1. Get all the secrets for the recipient and sender pairs (#9365)
427
- const appTaggingSecrets = await this.#getAppTaggingSecretsForContacts(contractAddress, recipient);
428
-
429
- // 1.1 Set up a sliding window with an offset. Chances are the sender might have messed up
430
- // and inadvertedly incremented their index without use getting any logs (for example, in case
431
- // of a revert). If we stopped looking for logs the first time
432
- // we receive 0 for a tag, we might never receive anything from that sender again.
433
- // Also there's a possibility that we have advanced our index, but the sender has reused it, so
434
- // we might have missed some logs. For these reasons, we have to look both back and ahead of the
435
- // stored index
436
- const INDEX_OFFSET = 10;
437
- type SearchState = {
438
- currentTagggingSecrets: IndexedTaggingSecret[];
439
- maxIndexesToCheck: { [k: string]: number };
440
- initialSecretIndexes: { [k: string]: number };
441
- secretsToIncrement: { [k: string]: number };
442
- };
443
- const searchState = appTaggingSecrets.reduce<SearchState>(
444
- (acc, appTaggingSecret) => ({
445
- // Start looking for logs before the stored index
446
- currentTagggingSecrets: acc.currentTagggingSecrets.concat([
447
- new IndexedTaggingSecret(appTaggingSecret.secret, Math.max(0, appTaggingSecret.index - INDEX_OFFSET)),
448
- ]),
449
- // Keep looking for logs beyond the stored index
450
- maxIndexesToCheck: {
451
- ...acc.maxIndexesToCheck,
452
- ...{ [appTaggingSecret.secret.toString()]: appTaggingSecret.index + INDEX_OFFSET },
453
- },
454
- // Keeps track of the secrets we have to increment in the database
455
- secretsToIncrement: {},
456
- // Store the initial set of indexes for the secrets
457
- initialSecretIndexes: {
458
- ...acc.initialSecretIndexes,
459
- ...{ [appTaggingSecret.secret.toString()]: appTaggingSecret.index },
460
- },
461
- }),
462
- { currentTagggingSecrets: [], maxIndexesToCheck: {}, secretsToIncrement: {}, initialSecretIndexes: {} },
463
- );
440
+ const logsForRecipient: TxScopedL2Log[] = [];
441
+
442
+ // Get all the secrets for the recipient and sender pairs (#9365)
443
+ const secrets = await this.#getIndexedTaggingSecretsForContacts(contractAddress, recipient);
444
+
445
+ // We fetch logs for a window of indexes in a range:
446
+ // <latest_log_index - WINDOW_HALF_SIZE, latest_log_index + WINDOW_HALF_SIZE>.
447
+ //
448
+ // We use this window approach because it could happen that a sender might have messed up and inadvertently
449
+ // incremented their index without us getting any logs (for example, in case of a revert). If we stopped looking
450
+ // for logs the first time we don't receive any logs for a tag, we might never receive anything from that sender again.
451
+ // Also there's a possibility that we have advanced our index, but the sender has reused it, so we might have missed
452
+ // some logs. For these reasons, we have to look both back and ahead of the stored index.
453
+ let secretsAndWindows = secrets.map(secret => {
454
+ return {
455
+ appTaggingSecret: secret.appTaggingSecret,
456
+ leftMostIndex: Math.max(0, secret.index - WINDOW_HALF_SIZE),
457
+ rightMostIndex: secret.index + WINDOW_HALF_SIZE,
458
+ };
459
+ });
460
+
461
+ // As we iterate we store the largest index we have seen for a given secret to later on store it in the db.
462
+ const newLargestIndexMapToStore: { [k: string]: number } = {};
464
463
 
465
- let { currentTagggingSecrets } = searchState;
466
- const { maxIndexesToCheck, secretsToIncrement, initialSecretIndexes } = searchState;
464
+ // The initial/unmodified indexes of the secrets stored in a key-value map where key is the app tagging secret.
465
+ const initialIndexesMap = getInitialIndexesMap(secrets);
467
466
 
468
- while (currentTagggingSecrets.length > 0) {
469
- // 2. Compute tags using the secrets, recipient and index. Obtain logs for each tag (#9380)
470
- const currentTags = currentTagggingSecrets.map(taggingSecret =>
471
- taggingSecret.computeSiloedTag(recipient, contractAddress),
467
+ while (secretsAndWindows.length > 0) {
468
+ const secretsForTheWholeWindow = getIndexedTaggingSecretsForTheWindow(secretsAndWindows);
469
+ const tagsForTheWholeWindow = secretsForTheWholeWindow.map(secret =>
470
+ secret.computeSiloedTag(recipient, contractAddress),
472
471
  );
473
- const logsByTags = await this.aztecNode.getLogsByTags(currentTags);
474
- const newTaggingSecrets: IndexedTaggingSecret[] = [];
472
+
473
+ // We store the new largest indexes we find in the iteration in the following map to later on construct
474
+ // a new set of secrets and windows to fetch logs for.
475
+ const newLargestIndexMapForIteration: { [k: string]: number } = {};
476
+
477
+ // Fetch the logs for the tags and iterate over them
478
+ const logsByTags = await this.aztecNode.getLogsByTags(tagsForTheWholeWindow);
479
+
475
480
  logsByTags.forEach((logsByTag, logIndex) => {
476
- const { secret: currentSecret, index: currentIndex } = currentTagggingSecrets[logIndex];
477
- const currentSecretAsStr = currentSecret.toString();
478
- this.log.debug(
479
- `Syncing logs for recipient ${recipient}, secret ${currentSecretAsStr}:${currentIndex} at contract: ${contractName}(${contractAddress})`,
480
- );
481
- // 3.1. Append logs to the list and increment the index for the tags that have logs (#9380)
482
481
  if (logsByTag.length > 0) {
483
- this.log.verbose(
484
- `Found ${
485
- logsByTag.length
486
- } logs for secret ${currentSecretAsStr} as recipient ${recipient}. Incrementing index to ${
487
- currentIndex + 1
488
- } at contract: ${contractName}(${contractAddress})`,
489
- );
490
- logs.push(...logsByTag);
491
-
492
- if (currentIndex >= initialSecretIndexes[currentSecretAsStr]) {
493
- // 3.2. Increment the index for the tags that have logs, provided they're higher than the one
494
- // we have stored in the db (#9380)
495
- secretsToIncrement[currentSecretAsStr] = currentIndex + 1;
496
- // 3.3. Slide the window forwards if we have found logs beyond the initial index
497
- maxIndexesToCheck[currentSecretAsStr] = currentIndex + INDEX_OFFSET;
482
+ // The logs for the given tag exist so we store them for later processing
483
+ logsForRecipient.push(...logsByTag);
484
+
485
+ // We retrieve the indexed tagging secret corresponding to the log as I need that to evaluate whether
486
+ // a new largest index have been found.
487
+ const secretCorrespondingToLog = secretsForTheWholeWindow[logIndex];
488
+ const initialIndex = initialIndexesMap[secretCorrespondingToLog.appTaggingSecret.toString()];
489
+
490
+ this.log.debug(`Found ${logsByTag.length} logs as recipient ${recipient}`, {
491
+ recipient,
492
+ secret: secretCorrespondingToLog.appTaggingSecret,
493
+ contractName,
494
+ contractAddress,
495
+ });
496
+
497
+ if (
498
+ secretCorrespondingToLog.index >= initialIndex &&
499
+ (newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] === undefined ||
500
+ secretCorrespondingToLog.index >=
501
+ newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()])
502
+ ) {
503
+ // We have found a new largest index so we store it for later processing (storing it in the db + fetching
504
+ // the difference of the window sets of current and the next iteration)
505
+ newLargestIndexMapForIteration[secretCorrespondingToLog.appTaggingSecret.toString()] =
506
+ secretCorrespondingToLog.index + 1;
507
+
508
+ this.log.debug(
509
+ `Incrementing index to ${
510
+ secretCorrespondingToLog.index + 1
511
+ } at contract ${contractName}(${contractAddress})`,
512
+ );
498
513
  }
499
514
  }
500
- // 3.4 Keep increasing the index (inside the window) temporarily for the tags that have no logs
501
- // There's a chance the sender missed some and we want to catch up
502
- if (currentIndex < maxIndexesToCheck[currentSecretAsStr]) {
503
- const newTaggingSecret = new IndexedTaggingSecret(currentSecret, currentIndex + 1);
504
- newTaggingSecrets.push(newTaggingSecret);
505
- }
506
515
  });
507
- await this.db.setTaggingSecretsIndexesAsRecipient(
508
- Object.keys(secretsToIncrement).map(
509
- secret => new IndexedTaggingSecret(Fr.fromString(secret), secretsToIncrement[secret]),
510
- ),
511
- );
512
- currentTagggingSecrets = newTaggingSecrets;
516
+
517
+ // Now based on the new largest indexes we found, we will construct a new secrets and windows set to fetch logs
518
+ // for. Note that it's very unlikely that a new log from the current window would appear between the iterations
519
+ // so we fetch the logs only for the difference of the window sets.
520
+ const newSecretsAndWindows = [];
521
+ for (const [appTaggingSecret, newIndex] of Object.entries(newLargestIndexMapForIteration)) {
522
+ const secret = secrets.find(secret => secret.appTaggingSecret.toString() === appTaggingSecret);
523
+ if (secret) {
524
+ newSecretsAndWindows.push({
525
+ appTaggingSecret: secret.appTaggingSecret,
526
+ // We set the left most index to the new index to avoid fetching the same logs again
527
+ leftMostIndex: newIndex,
528
+ rightMostIndex: newIndex + WINDOW_HALF_SIZE,
529
+ });
530
+
531
+ // We store the new largest index in the map to later store it in the db.
532
+ newLargestIndexMapToStore[appTaggingSecret] = newIndex;
533
+ } else {
534
+ throw new Error(
535
+ `Secret not found for appTaggingSecret ${appTaggingSecret}. This is a bug as it should never happen!`,
536
+ );
537
+ }
538
+ }
539
+
540
+ // Now we set the new secrets and windows and proceed to the next iteration.
541
+ secretsAndWindows = newSecretsAndWindows;
513
542
  }
514
543
 
515
- result.set(
544
+ // We filter the logs by block number and store them in the map.
545
+ logsMap.set(
516
546
  recipient.toString(),
517
- // Remove logs with a block number higher than the max block number
518
- // Duplicates are likely to happen due to the sliding window, so we also filter them out
519
- logs.filter(
520
- (log, index, self) =>
521
- log.blockNumber <= maxBlockNumber && index === self.findIndex(otherLog => otherLog.equals(log)),
547
+ logsForRecipient.filter(log => log.blockNumber <= maxBlockNumber),
548
+ );
549
+
550
+ // At this point we have processed all the logs for the recipient so we store the new largest indexes in the db.
551
+ await this.db.setTaggingSecretsIndexesAsRecipient(
552
+ Object.entries(newLargestIndexMapToStore).map(
553
+ ([appTaggingSecret, index]) => new IndexedTaggingSecret(Fr.fromHexString(appTaggingSecret), index),
522
554
  ),
523
555
  );
524
556
  }
525
- return result;
557
+ return logsMap;
526
558
  }
527
559
 
528
560
  /**
@@ -538,14 +570,11 @@ export class SimulatorOracle implements DBOracle {
538
570
  recipientCompleteAddress.publicKeys.masterIncomingViewingPublicKey,
539
571
  );
540
572
  const addressSecret = computeAddressSecret(recipientCompleteAddress.getPreaddress(), ivskM);
541
- const ovskM = await this.keyStore.getMasterSecretKey(
542
- recipientCompleteAddress.publicKeys.masterOutgoingViewingPublicKey,
543
- );
573
+
544
574
  // Since we could have notes with the same index for different txs, we need
545
575
  // to keep track of them scoping by txHash
546
576
  const excludedIndices: Map<string, Set<number>> = new Map();
547
577
  const incomingNotes: IncomingNoteDao[] = [];
548
- const outgoingNotes: OutgoingNoteDao[] = [];
549
578
 
550
579
  const txEffectsCache = new Map<string, InBlock<TxEffect> | undefined>();
551
580
 
@@ -553,21 +582,9 @@ export class SimulatorOracle implements DBOracle {
553
582
  const incomingNotePayload = scopedLog.isFromPublic
554
583
  ? L1NotePayload.decryptAsIncomingFromPublic(scopedLog.logData, addressSecret)
555
584
  : L1NotePayload.decryptAsIncoming(PrivateLog.fromBuffer(scopedLog.logData), addressSecret);
556
- const outgoingNotePayload = scopedLog.isFromPublic
557
- ? L1NotePayload.decryptAsOutgoingFromPublic(scopedLog.logData, ovskM)
558
- : L1NotePayload.decryptAsOutgoing(PrivateLog.fromBuffer(scopedLog.logData), ovskM);
559
-
560
- if (incomingNotePayload || outgoingNotePayload) {
561
- if (incomingNotePayload && outgoingNotePayload && !incomingNotePayload.equals(outgoingNotePayload)) {
562
- this.log.warn(
563
- `Incoming and outgoing note payloads do not match. Incoming: ${tryJsonStringify(
564
- incomingNotePayload,
565
- )}, Outgoing: ${tryJsonStringify(outgoingNotePayload)}`,
566
- );
567
- continue;
568
- }
569
585
 
570
- const payload = incomingNotePayload || outgoingNotePayload;
586
+ if (incomingNotePayload) {
587
+ const payload = incomingNotePayload;
571
588
 
572
589
  const txEffect =
573
590
  txEffectsCache.get(scopedLog.txHash.toString()) ?? (await this.aztecNode.getTxEffect(scopedLog.txHash));
@@ -582,14 +599,13 @@ export class SimulatorOracle implements DBOracle {
582
599
  if (!excludedIndices.has(scopedLog.txHash.toString())) {
583
600
  excludedIndices.set(scopedLog.txHash.toString(), new Set());
584
601
  }
585
- const { incomingNote, outgoingNote } = await produceNoteDaos(
602
+ const { incomingNote } = await produceNoteDaos(
586
603
  // I don't like this at all, but we need a simulator to run `computeNoteHashAndOptionallyANullifier`. This generates
587
604
  // a chicken-and-egg problem due to this oracle requiring a simulator, which in turn requires this oracle. Furthermore, since jest doesn't allow
588
605
  // mocking ESM exports, we have to pollute the method even more by providing a simulator parameter so tests can inject a fake one.
589
606
  simulator ?? getAcirSimulator(this.db, this.aztecNode, this.keyStore, this.contractDataOracle),
590
607
  this.db,
591
608
  incomingNotePayload ? recipient.toAddressPoint() : undefined,
592
- outgoingNotePayload ? recipientCompleteAddress.publicKeys.masterOutgoingViewingPublicKey : undefined,
593
609
  payload!,
594
610
  txEffect.data.txHash,
595
611
  txEffect.l2BlockNumber,
@@ -603,12 +619,9 @@ export class SimulatorOracle implements DBOracle {
603
619
  if (incomingNote) {
604
620
  incomingNotes.push(incomingNote);
605
621
  }
606
- if (outgoingNote) {
607
- outgoingNotes.push(outgoingNote);
608
- }
609
622
  }
610
623
  }
611
- return { incomingNotes, outgoingNotes };
624
+ return { incomingNotes };
612
625
  }
613
626
 
614
627
  /**
@@ -621,41 +634,41 @@ export class SimulatorOracle implements DBOracle {
621
634
  recipient: AztecAddress,
622
635
  simulator?: AcirSimulator,
623
636
  ): Promise<void> {
624
- const { incomingNotes, outgoingNotes } = await this.#decryptTaggedLogs(logs, recipient, simulator);
625
- if (incomingNotes.length || outgoingNotes.length) {
626
- await this.db.addNotes(incomingNotes, outgoingNotes, recipient);
637
+ const { incomingNotes } = await this.#decryptTaggedLogs(logs, recipient, simulator);
638
+ if (incomingNotes.length) {
639
+ await this.db.addNotes(incomingNotes, recipient);
627
640
  incomingNotes.forEach(noteDao => {
628
- this.log.verbose(
629
- `Added incoming note for contract ${noteDao.contractAddress} at slot ${
630
- noteDao.storageSlot
631
- } with nullifier ${noteDao.siloedNullifier.toString()}`,
632
- );
641
+ this.log.verbose(`Added incoming note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`, {
642
+ contract: noteDao.contractAddress,
643
+ slot: noteDao.storageSlot,
644
+ nullifier: noteDao.siloedNullifier.toString(),
645
+ });
633
646
  });
634
- outgoingNotes.forEach(noteDao => {
635
- this.log.verbose(`Added outgoing note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`);
647
+ }
648
+ }
649
+
650
+ public async removeNullifiedNotes(contractAddress: AztecAddress) {
651
+ for (const recipient of await this.keyStore.getAccounts()) {
652
+ const currentNotesForRecipient = await this.db.getIncomingNotes({ contractAddress, owner: recipient });
653
+ const nullifiersToCheck = currentNotesForRecipient.map(note => note.siloedNullifier);
654
+ const nullifierIndexes = await this.aztecNode.findNullifiersIndexesWithBlock('latest', nullifiersToCheck);
655
+
656
+ const foundNullifiers = nullifiersToCheck
657
+ .map((nullifier, i) => {
658
+ if (nullifierIndexes[i] !== undefined) {
659
+ return { ...nullifierIndexes[i], ...{ data: nullifier } } as InBlock<Fr>;
660
+ }
661
+ })
662
+ .filter(nullifier => nullifier !== undefined) as InBlock<Fr>[];
663
+
664
+ const nullifiedNotes = await this.db.removeNullifiedNotes(foundNullifiers, recipient.toAddressPoint());
665
+ nullifiedNotes.forEach(noteDao => {
666
+ this.log.verbose(`Removed note for contract ${noteDao.contractAddress} at slot ${noteDao.storageSlot}`, {
667
+ contract: noteDao.contractAddress,
668
+ slot: noteDao.storageSlot,
669
+ nullifier: noteDao.siloedNullifier.toString(),
670
+ });
636
671
  });
637
672
  }
638
- const nullifiedNotes: IncomingNoteDao[] = [];
639
- const currentNotesForRecipient = await this.db.getIncomingNotes({ owner: recipient });
640
- const nullifiersToCheck = currentNotesForRecipient.map(note => note.siloedNullifier);
641
- const currentBlockNumber = await this.getBlockNumber();
642
- const nullifierIndexes = await this.aztecNode.findNullifiersIndexesWithBlock(currentBlockNumber, nullifiersToCheck);
643
-
644
- const foundNullifiers = nullifiersToCheck
645
- .map((nullifier, i) => {
646
- if (nullifierIndexes[i] !== undefined) {
647
- return { ...nullifierIndexes[i], ...{ data: nullifier } } as InBlock<Fr>;
648
- }
649
- })
650
- .filter(nullifier => nullifier !== undefined) as InBlock<Fr>[];
651
-
652
- await this.db.removeNullifiedNotes(foundNullifiers, recipient.toAddressPoint());
653
- nullifiedNotes.forEach(noteDao => {
654
- this.log.verbose(
655
- `Removed note for contract ${noteDao.contractAddress} at slot ${
656
- noteDao.storageSlot
657
- } with nullifier ${noteDao.siloedNullifier.toString()}`,
658
- );
659
- });
660
673
  }
661
674
  }
@@ -0,0 +1,28 @@
1
+ import { type Fr, IndexedTaggingSecret } from '@aztec/circuits.js';
2
+
3
+ export function getIndexedTaggingSecretsForTheWindow(
4
+ secretsAndWindows: { appTaggingSecret: Fr; leftMostIndex: number; rightMostIndex: number }[],
5
+ ): IndexedTaggingSecret[] {
6
+ const secrets: IndexedTaggingSecret[] = [];
7
+ for (const secretAndWindow of secretsAndWindows) {
8
+ for (let i = secretAndWindow.leftMostIndex; i <= secretAndWindow.rightMostIndex; i++) {
9
+ secrets.push(new IndexedTaggingSecret(secretAndWindow.appTaggingSecret, i));
10
+ }
11
+ }
12
+ return secrets;
13
+ }
14
+
15
+ /**
16
+ * Creates a map from app tagging secret to initial index.
17
+ * @param indexedTaggingSecrets - The indexed tagging secrets to get the initial indexes from.
18
+ * @returns The map from app tagging secret to initial index.
19
+ */
20
+ export function getInitialIndexesMap(indexedTaggingSecrets: IndexedTaggingSecret[]): { [k: string]: number } {
21
+ const initialIndexes: { [k: string]: number } = {};
22
+
23
+ for (const indexedTaggingSecret of indexedTaggingSecrets) {
24
+ initialIndexes[indexedTaggingSecret.appTaggingSecret.toString()] = indexedTaggingSecret.index;
25
+ }
26
+
27
+ return initialIndexes;
28
+ }