@aztec/pxe 0.63.1 → 0.64.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 (47) hide show
  1. package/dest/database/contracts/contract_artifact_db.d.ts +1 -0
  2. package/dest/database/contracts/contract_artifact_db.d.ts.map +1 -1
  3. package/dest/database/incoming_note_dao.d.ts +10 -1
  4. package/dest/database/incoming_note_dao.d.ts.map +1 -1
  5. package/dest/database/incoming_note_dao.js +18 -5
  6. package/dest/database/kv_pxe_database.d.ts +6 -3
  7. package/dest/database/kv_pxe_database.d.ts.map +1 -1
  8. package/dest/database/kv_pxe_database.js +123 -22
  9. package/dest/database/outgoing_note_dao.d.ts +10 -1
  10. package/dest/database/outgoing_note_dao.d.ts.map +1 -1
  11. package/dest/database/outgoing_note_dao.js +18 -5
  12. package/dest/database/pxe_database.d.ts +22 -5
  13. package/dest/database/pxe_database.d.ts.map +1 -1
  14. package/dest/database/pxe_database_test_suite.d.ts.map +1 -1
  15. package/dest/database/pxe_database_test_suite.js +65 -16
  16. package/dest/kernel_oracle/index.js +2 -2
  17. package/dest/note_decryption_utils/produce_note_daos.d.ts +1 -1
  18. package/dest/note_decryption_utils/produce_note_daos.d.ts.map +1 -1
  19. package/dest/note_decryption_utils/produce_note_daos.js +5 -5
  20. package/dest/note_decryption_utils/produce_note_daos_for_key.d.ts +1 -1
  21. package/dest/note_decryption_utils/produce_note_daos_for_key.d.ts.map +1 -1
  22. package/dest/note_decryption_utils/produce_note_daos_for_key.js +3 -3
  23. package/dest/pxe_service/create_pxe_service.d.ts.map +1 -1
  24. package/dest/pxe_service/create_pxe_service.js +6 -3
  25. package/dest/pxe_service/pxe_service.d.ts +5 -4
  26. package/dest/pxe_service/pxe_service.d.ts.map +1 -1
  27. package/dest/pxe_service/pxe_service.js +51 -25
  28. package/dest/simulator_oracle/index.d.ts +8 -0
  29. package/dest/simulator_oracle/index.d.ts.map +1 -1
  30. package/dest/simulator_oracle/index.js +67 -10
  31. package/dest/synchronizer/synchronizer.d.ts +13 -28
  32. package/dest/synchronizer/synchronizer.d.ts.map +1 -1
  33. package/dest/synchronizer/synchronizer.js +42 -64
  34. package/package.json +14 -14
  35. package/src/database/contracts/contract_artifact_db.ts +1 -0
  36. package/src/database/incoming_note_dao.ts +46 -1
  37. package/src/database/kv_pxe_database.ts +148 -23
  38. package/src/database/outgoing_note_dao.ts +43 -1
  39. package/src/database/pxe_database.ts +25 -5
  40. package/src/database/pxe_database_test_suite.ts +79 -17
  41. package/src/kernel_oracle/index.ts +1 -1
  42. package/src/note_decryption_utils/produce_note_daos.ts +8 -0
  43. package/src/note_decryption_utils/produce_note_daos_for_key.ts +5 -1
  44. package/src/pxe_service/create_pxe_service.ts +7 -4
  45. package/src/pxe_service/pxe_service.ts +109 -72
  46. package/src/simulator_oracle/index.ts +95 -17
  47. package/src/synchronizer/synchronizer.ts +60 -71
@@ -13,6 +13,8 @@ export async function produceNoteDaosForKey<T>(
13
13
  pkM: PublicKey,
14
14
  payload: L1NotePayload,
15
15
  txHash: TxHash,
16
+ l2BlockNumber: number,
17
+ l2BlockHash: string,
16
18
  noteHashes: Fr[],
17
19
  dataStartIndexForTx: number,
18
20
  excludedIndices: Set<number>,
@@ -21,6 +23,8 @@ export async function produceNoteDaosForKey<T>(
21
23
  note: Note,
22
24
  payload: L1NotePayload,
23
25
  noteInfo: NoteInfo,
26
+ l2BlockNumber: number,
27
+ l2BlockHash: string,
24
28
  dataStartIndexForTx: number,
25
29
  pkM: PublicKey,
26
30
  ) => T,
@@ -44,7 +48,7 @@ export async function produceNoteDaosForKey<T>(
44
48
  );
45
49
  excludedIndices?.add(noteInfo.noteHashIndex);
46
50
 
47
- noteDao = daoConstructor(note, payload, noteInfo, dataStartIndexForTx, pkM);
51
+ noteDao = daoConstructor(note, payload, noteInfo, l2BlockNumber, l2BlockHash, dataStartIndexForTx, pkM);
48
52
  } catch (e) {
49
53
  logger.error(`Could not process note because of "${e}". Discarding note...`);
50
54
  }
@@ -3,6 +3,7 @@ import { type AztecNode, type PrivateKernelProver } from '@aztec/circuit-types';
3
3
  import { randomBytes } from '@aztec/foundation/crypto';
4
4
  import { createDebugLogger } from '@aztec/foundation/log';
5
5
  import { KeyStore } from '@aztec/key-store';
6
+ import { L2TipsStore } from '@aztec/kv-store/stores';
6
7
  import { createStore } from '@aztec/kv-store/utils';
7
8
 
8
9
  import { type PXEServiceConfig } from '../config/index.js';
@@ -39,12 +40,14 @@ export async function createPXEService(
39
40
  const keyStore = new KeyStore(
40
41
  await createStore('pxe_key_store', configWithContracts, createDebugLogger('aztec:pxe:keystore:lmdb')),
41
42
  );
42
- const db = new KVPxeDatabase(
43
- await createStore('pxe_data', configWithContracts, createDebugLogger('aztec:pxe:data:lmdb')),
44
- );
43
+
44
+ const store = await createStore('pxe_data', configWithContracts, createDebugLogger('aztec:pxe:data:lmdb'));
45
+
46
+ const db = new KVPxeDatabase(store);
47
+ const tips = new L2TipsStore(store, 'pxe');
45
48
 
46
49
  const prover = proofCreator ?? (await createProver(config, logSuffix));
47
- const server = new PXEService(keyStore, aztecNode, db, prover, config, logSuffix);
50
+ const server = new PXEService(keyStore, aztecNode, db, tips, prover, config, logSuffix);
48
51
  await server.start();
49
52
  return server;
50
53
  }
@@ -6,6 +6,7 @@ import {
6
6
  type ExtendedNote,
7
7
  type FunctionCall,
8
8
  type GetUnencryptedLogsResponse,
9
+ type InBlock,
9
10
  type IncomingNotesFilter,
10
11
  L1EventPayload,
11
12
  type L2Block,
@@ -22,7 +23,6 @@ import {
22
23
  type SiblingPath,
23
24
  SimulationError,
24
25
  type Tx,
25
- type TxEffect,
26
26
  type TxExecutionRequest,
27
27
  type TxHash,
28
28
  TxProvingResult,
@@ -43,7 +43,6 @@ import {
43
43
  computeAddressSecret,
44
44
  computeContractAddressFromInstance,
45
45
  computeContractClassId,
46
- computePoint,
47
46
  getContractClassFromArtifact,
48
47
  } from '@aztec/circuits.js';
49
48
  import { computeNoteHashNonce, siloNullifier } from '@aztec/circuits.js/hash';
@@ -58,6 +57,7 @@ import { Fr, type Point } from '@aztec/foundation/fields';
58
57
  import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
59
58
  import { SerialQueue } from '@aztec/foundation/queue';
60
59
  import { type KeyStore } from '@aztec/key-store';
60
+ import { type L2TipsStore } from '@aztec/kv-store/stores';
61
61
  import {
62
62
  ProtocolContractAddress,
63
63
  getCanonicalProtocolContract,
@@ -93,12 +93,13 @@ export class PXEService implements PXE {
93
93
  private keyStore: KeyStore,
94
94
  private node: AztecNode,
95
95
  private db: PxeDatabase,
96
+ tipsStore: L2TipsStore,
96
97
  private proofCreator: PrivateKernelProver,
97
- private config: PXEServiceConfig,
98
+ config: PXEServiceConfig,
98
99
  logSuffix?: string,
99
100
  ) {
100
101
  this.log = createDebugLogger(logSuffix ? `aztec:pxe_service_${logSuffix}` : `aztec:pxe_service`);
101
- this.synchronizer = new Synchronizer(node, db, this.jobQueue, logSuffix);
102
+ this.synchronizer = new Synchronizer(node, db, tipsStore, config, logSuffix);
102
103
  this.contractDataOracle = new ContractDataOracle(db);
103
104
  this.simulator = getAcirSimulator(db, node, keyStore, this.contractDataOracle);
104
105
  this.packageVersion = getPackageInfo().version;
@@ -112,8 +113,7 @@ export class PXEService implements PXE {
112
113
  * @returns A promise that resolves when the server has started successfully.
113
114
  */
114
115
  public async start() {
115
- const { l2BlockPollingIntervalMS } = this.config;
116
- await this.synchronizer.start(1, l2BlockPollingIntervalMS);
116
+ await this.synchronizer.start();
117
117
  await this.#registerProtocolContracts();
118
118
  const info = await this.getNodeInfo();
119
119
  this.log.info(`Started PXE connected to chain ${info.l1ChainId} version ${info.protocolVersion}`);
@@ -242,21 +242,24 @@ export class PXEService implements PXE {
242
242
 
243
243
  if (artifact) {
244
244
  // If the user provides an artifact, validate it against the expected class id and register it
245
- const contractClassId = computeContractClassId(getContractClassFromArtifact(artifact));
245
+ const contractClass = getContractClassFromArtifact(artifact);
246
+ const contractClassId = computeContractClassId(contractClass);
246
247
  if (!contractClassId.equals(instance.contractClassId)) {
247
248
  throw new Error(
248
249
  `Artifact does not match expected class id (computed ${contractClassId} but instance refers to ${instance.contractClassId})`,
249
250
  );
250
251
  }
251
- if (
252
- // Computed address from the instance does not match address inside instance
253
- !computeContractAddressFromInstance(instance).equals(instance.address)
254
- ) {
252
+ if (!computeContractAddressFromInstance(instance).equals(instance.address)) {
255
253
  throw new Error('Added a contract in which the address does not match the contract instance.');
256
254
  }
257
255
 
258
256
  await this.db.addContractArtifact(contractClassId, artifact);
257
+
258
+ // TODO: PXE may not want to broadcast the artifact to the network
259
259
  await this.node.addContractArtifact(instance.address, artifact);
260
+
261
+ // TODO(#10007): Node should get public contract class from the registration event, not from PXE registration
262
+ await this.node.addContractClass({ ...contractClass, privateFunctions: [], unconstrainedFunctions: [] });
260
263
  } else {
261
264
  // Otherwise, make sure there is an artifact already registered for that class id
262
265
  artifact = await this.db.getContractArtifact(instance.contractClassId);
@@ -289,7 +292,7 @@ export class PXEService implements PXE {
289
292
  let owner = filter.owner;
290
293
  if (owner === undefined) {
291
294
  const completeAddresses = (await this.db.getCompleteAddresses()).find(completeAddress =>
292
- computePoint(completeAddress.address).equals(dao.addressPoint),
295
+ completeAddress.address.toAddressPoint().equals(dao.addressPoint),
293
296
  );
294
297
  if (completeAddresses === undefined) {
295
298
  throw new Error(`Cannot find complete address for addressPoint ${dao.addressPoint.toString()}`);
@@ -350,7 +353,7 @@ export class PXEService implements PXE {
350
353
  throw new Error(`Unknown account: ${note.owner.toString()}`);
351
354
  }
352
355
 
353
- const nonces = await this.#getNoteNonces(note);
356
+ const { data: nonces, l2BlockNumber, l2BlockHash } = await this.#getNoteNonces(note);
354
357
  if (nonces.length === 0) {
355
358
  throw new Error(`Cannot find the note in tx: ${note.txHash}.`);
356
359
  }
@@ -385,11 +388,13 @@ export class PXEService implements PXE {
385
388
  note.storageSlot,
386
389
  note.noteTypeId,
387
390
  note.txHash,
391
+ l2BlockNumber,
392
+ l2BlockHash,
388
393
  nonce,
389
394
  noteHash,
390
395
  siloedNullifier,
391
396
  index,
392
- computePoint(owner.address),
397
+ owner.address.toAddressPoint(),
393
398
  ),
394
399
  scope,
395
400
  );
@@ -397,7 +402,7 @@ export class PXEService implements PXE {
397
402
  }
398
403
 
399
404
  public async addNullifiedNote(note: ExtendedNote) {
400
- const nonces = await this.#getNoteNonces(note);
405
+ const { data: nonces, l2BlockHash, l2BlockNumber } = await this.#getNoteNonces(note);
401
406
  if (nonces.length === 0) {
402
407
  throw new Error(`Cannot find the note in tx: ${note.txHash}.`);
403
408
  }
@@ -428,11 +433,13 @@ export class PXEService implements PXE {
428
433
  note.storageSlot,
429
434
  note.noteTypeId,
430
435
  note.txHash,
436
+ l2BlockNumber,
437
+ l2BlockHash,
431
438
  nonce,
432
439
  noteHash,
433
440
  Fr.ZERO, // We are not able to derive
434
441
  index,
435
- computePoint(note.owner),
442
+ note.owner.toAddressPoint(),
436
443
  ),
437
444
  );
438
445
  }
@@ -444,15 +451,15 @@ export class PXEService implements PXE {
444
451
  * @returns The nonces of the note.
445
452
  * @remarks More than a single nonce may be returned since there might be more than one nonce for a given note.
446
453
  */
447
- async #getNoteNonces(note: ExtendedNote): Promise<Fr[]> {
454
+ async #getNoteNonces(note: ExtendedNote): Promise<InBlock<Fr[]>> {
448
455
  const tx = await this.node.getTxEffect(note.txHash);
449
456
  if (!tx) {
450
457
  throw new Error(`Unknown tx: ${note.txHash}`);
451
458
  }
452
459
 
453
460
  const nonces: Fr[] = [];
454
- const firstNullifier = tx.nullifiers[0];
455
- const hashes = tx.noteHashes;
461
+ const firstNullifier = tx.data.nullifiers[0];
462
+ const hashes = tx.data.noteHashes;
456
463
  for (let i = 0; i < hashes.length; ++i) {
457
464
  const hash = hashes[i];
458
465
  if (hash.equals(Fr.ZERO)) {
@@ -473,7 +480,7 @@ export class PXEService implements PXE {
473
480
  }
474
481
  }
475
482
 
476
- return nonces;
483
+ return { l2BlockHash: tx.l2BlockHash, l2BlockNumber: tx.l2BlockNumber, data: nonces };
477
484
  }
478
485
 
479
486
  public async getBlock(blockNumber: number): Promise<L2Block | undefined> {
@@ -496,10 +503,19 @@ export class PXEService implements PXE {
496
503
  txRequest: TxExecutionRequest,
497
504
  privateExecutionResult: PrivateExecutionResult,
498
505
  ): Promise<TxProvingResult> {
499
- return this.jobQueue.put(async () => {
500
- const { publicInputs, clientIvcProof } = await this.#prove(txRequest, this.proofCreator, privateExecutionResult);
501
- return new TxProvingResult(privateExecutionResult, publicInputs, clientIvcProof!);
502
- });
506
+ return this.jobQueue
507
+ .put(async () => {
508
+ const { publicInputs, clientIvcProof } = await this.#prove(
509
+ txRequest,
510
+ this.proofCreator,
511
+ privateExecutionResult,
512
+ );
513
+ return new TxProvingResult(privateExecutionResult, publicInputs, clientIvcProof!);
514
+ })
515
+ .catch(err => {
516
+ this.log.error(err);
517
+ throw err;
518
+ });
503
519
  }
504
520
 
505
521
  // TODO(#7456) Prevent msgSender being defined here for the first call
@@ -511,47 +527,52 @@ export class PXEService implements PXE {
511
527
  profile: boolean = false,
512
528
  scopes?: AztecAddress[],
513
529
  ): Promise<TxSimulationResult> {
514
- return await this.jobQueue.put(async () => {
515
- const privateExecutionResult = await this.#executePrivate(txRequest, msgSender, scopes);
516
-
517
- let publicInputs: PrivateKernelTailCircuitPublicInputs;
518
- let profileResult;
519
- if (profile) {
520
- ({ publicInputs, profileResult } = await this.#profileKernelProver(
521
- txRequest,
522
- this.proofCreator,
523
- privateExecutionResult,
524
- ));
525
- } else {
526
- publicInputs = await this.#simulateKernels(txRequest, privateExecutionResult);
527
- }
530
+ return await this.jobQueue
531
+ .put(async () => {
532
+ const privateExecutionResult = await this.#executePrivate(txRequest, msgSender, scopes);
533
+
534
+ let publicInputs: PrivateKernelTailCircuitPublicInputs;
535
+ let profileResult;
536
+ if (profile) {
537
+ ({ publicInputs, profileResult } = await this.#profileKernelProver(
538
+ txRequest,
539
+ this.proofCreator,
540
+ privateExecutionResult,
541
+ ));
542
+ } else {
543
+ publicInputs = await this.#simulateKernels(txRequest, privateExecutionResult);
544
+ }
528
545
 
529
- const privateSimulationResult = new PrivateSimulationResult(privateExecutionResult, publicInputs);
530
- const simulatedTx = privateSimulationResult.toSimulatedTx();
531
- let publicOutput: PublicSimulationOutput | undefined;
532
- if (simulatePublic) {
533
- publicOutput = await this.#simulatePublicCalls(simulatedTx);
534
- }
546
+ const privateSimulationResult = new PrivateSimulationResult(privateExecutionResult, publicInputs);
547
+ const simulatedTx = privateSimulationResult.toSimulatedTx();
548
+ let publicOutput: PublicSimulationOutput | undefined;
549
+ if (simulatePublic) {
550
+ publicOutput = await this.#simulatePublicCalls(simulatedTx);
551
+ }
535
552
 
536
- if (!skipTxValidation) {
537
- if (!(await this.node.isValidTx(simulatedTx, true))) {
538
- throw new Error('The simulated transaction is unable to be added to state and is invalid.');
553
+ if (!skipTxValidation) {
554
+ if (!(await this.node.isValidTx(simulatedTx, true))) {
555
+ throw new Error('The simulated transaction is unable to be added to state and is invalid.');
556
+ }
539
557
  }
540
- }
541
558
 
542
- // We log only if the msgSender is undefined, as simulating with a different msgSender
543
- // is unlikely to be a real transaction, and likely to be only used to read data.
544
- // Meaning that it will not necessarily have produced a nullifier (and thus have no TxHash)
545
- // If we log, the `getTxHash` function will throw.
546
- if (!msgSender) {
547
- this.log.info(`Executed local simulation for ${simulatedTx.getTxHash()}`);
548
- }
549
- return TxSimulationResult.fromPrivateSimulationResultAndPublicOutput(
550
- privateSimulationResult,
551
- publicOutput,
552
- profileResult,
553
- );
554
- });
559
+ // We log only if the msgSender is undefined, as simulating with a different msgSender
560
+ // is unlikely to be a real transaction, and likely to be only used to read data.
561
+ // Meaning that it will not necessarily have produced a nullifier (and thus have no TxHash)
562
+ // If we log, the `getTxHash` function will throw.
563
+ if (!msgSender) {
564
+ this.log.info(`Executed local simulation for ${simulatedTx.getTxHash()}`);
565
+ }
566
+ return TxSimulationResult.fromPrivateSimulationResultAndPublicOutput(
567
+ privateSimulationResult,
568
+ publicOutput,
569
+ profileResult,
570
+ );
571
+ })
572
+ .catch(err => {
573
+ this.log.error(err);
574
+ throw err;
575
+ });
555
576
  }
556
577
 
557
578
  public async sendTx(tx: Tx): Promise<TxHash> {
@@ -560,7 +581,10 @@ export class PXEService implements PXE {
560
581
  throw new Error(`A settled tx with equal hash ${txHash.toString()} exists.`);
561
582
  }
562
583
  this.log.info(`Sending transaction ${txHash}`);
563
- await this.node.sendTx(tx);
584
+ await this.node.sendTx(tx).catch(err => {
585
+ this.log.error(err);
586
+ throw err;
587
+ });
564
588
  this.log.info(`Sent transaction ${txHash}`);
565
589
  return txHash;
566
590
  }
@@ -573,21 +597,26 @@ export class PXEService implements PXE {
573
597
  scopes?: AztecAddress[],
574
598
  ): Promise<AbiDecoded> {
575
599
  // all simulations must be serialized w.r.t. the synchronizer
576
- return await this.jobQueue.put(async () => {
577
- // TODO - Should check if `from` has the permission to call the view function.
578
- const functionCall = await this.#getFunctionCall(functionName, args, to);
579
- const executionResult = await this.#simulateUnconstrained(functionCall, scopes);
580
-
581
- // TODO - Return typed result based on the function artifact.
582
- return executionResult;
583
- });
600
+ return await this.jobQueue
601
+ .put(async () => {
602
+ // TODO - Should check if `from` has the permission to call the view function.
603
+ const functionCall = await this.#getFunctionCall(functionName, args, to);
604
+ const executionResult = await this.#simulateUnconstrained(functionCall, scopes);
605
+
606
+ // TODO - Return typed result based on the function artifact.
607
+ return executionResult;
608
+ })
609
+ .catch(err => {
610
+ this.log.error(err);
611
+ throw err;
612
+ });
584
613
  }
585
614
 
586
615
  public getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
587
616
  return this.node.getTxReceipt(txHash);
588
617
  }
589
618
 
590
- public getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
619
+ public getTxEffect(txHash: TxHash) {
591
620
  return this.node.getTxEffect(txHash);
592
621
  }
593
622
 
@@ -773,7 +802,11 @@ export class PXEService implements PXE {
773
802
  return result;
774
803
  } catch (err) {
775
804
  if (err instanceof SimulationError) {
776
- await enrichPublicSimulationError(err, this.contractDataOracle, this.db, this.log);
805
+ try {
806
+ await enrichPublicSimulationError(err, this.contractDataOracle, this.db, this.log);
807
+ } catch (enrichErr) {
808
+ this.log.error(`Failed to enrich public simulation error: ${enrichErr}`);
809
+ }
777
810
  }
778
811
  throw err;
779
812
  }
@@ -944,4 +977,8 @@ export class PXEService implements PXE {
944
977
 
945
978
  return decodedEvents;
946
979
  }
980
+
981
+ async resetNoteSyncData() {
982
+ return await this.db.resetNoteSyncData();
983
+ }
947
984
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type AztecNode,
3
+ type InBlock,
3
4
  L1NotePayload,
4
5
  type L2Block,
5
6
  type L2BlockNumber,
@@ -22,7 +23,6 @@ import {
22
23
  type KeyValidationRequest,
23
24
  type L1_TO_L2_MSG_TREE_HEIGHT,
24
25
  computeAddressSecret,
25
- computePoint,
26
26
  computeTaggingSecret,
27
27
  } from '@aztec/circuits.js';
28
28
  import { type FunctionArtifact, getFunctionArtifact } from '@aztec/foundation/abi';
@@ -268,8 +268,11 @@ export class SimulatorOracle implements DBOracle {
268
268
  sender: AztecAddress,
269
269
  recipient: AztecAddress,
270
270
  ): Promise<IndexedTaggingSecret> {
271
+ await this.syncTaggedLogsAsSender(contractAddress, sender, recipient);
272
+
271
273
  const secret = await this.#calculateTaggingSecret(contractAddress, sender, recipient);
272
274
  const [index] = await this.db.getTaggingSecretsIndexesAsSender([secret]);
275
+
273
276
  return new IndexedTaggingSecret(secret, index);
274
277
  }
275
278
 
@@ -289,7 +292,9 @@ export class SimulatorOracle implements DBOracle {
289
292
  this.log.verbose(
290
293
  `Incrementing secret ${secret} as sender ${sender} for recipient: ${recipient} at contract: ${contractName}(${contractAddress})`,
291
294
  );
292
- await this.db.incrementTaggingSecretsIndexesAsSender([secret]);
295
+
296
+ const [index] = await this.db.getTaggingSecretsIndexesAsSender([secret]);
297
+ await this.db.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(secret, index + 1)]);
293
298
  }
294
299
 
295
300
  async #calculateTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
@@ -329,6 +334,70 @@ export class SimulatorOracle implements DBOracle {
329
334
  return appTaggingSecrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i]));
330
335
  }
331
336
 
337
+ /**
338
+ * Updates the local index of the shared tagging secret of a sender / recipient pair
339
+ * if a log with a larger index is found from the node.
340
+ * @param contractAddress - The address of the contract that the logs are tagged for
341
+ * @param sender - The address of the sender, we must know the sender's ivsk_m.
342
+ * @param recipient - The address of the recipient.
343
+ */
344
+ public async syncTaggedLogsAsSender(
345
+ contractAddress: AztecAddress,
346
+ sender: AztecAddress,
347
+ recipient: AztecAddress,
348
+ ): Promise<void> {
349
+ const appTaggingSecret = await this.#calculateTaggingSecret(contractAddress, sender, recipient);
350
+ let [currentIndex] = await this.db.getTaggingSecretsIndexesAsSender([appTaggingSecret]);
351
+
352
+ const INDEX_OFFSET = 10;
353
+
354
+ let previousEmptyBack = 0;
355
+ let currentEmptyBack = 0;
356
+ let currentEmptyFront: number;
357
+
358
+ // The below code is trying to find the index of the start of the first window in which for all elements of window, we do not see logs.
359
+ // We take our window size, and fetch the node for these logs. We store both the amount of empty consecutive slots from the front and the back.
360
+ // We use our current empty consecutive slots from the front, as well as the previous consecutive empty slots from the back to see if we ever hit a time where there
361
+ // is a window in which we see the combination of them to be greater than the window's size. If true, we rewind current index to the start of said window and use it.
362
+ // Assuming two windows of 5:
363
+ // [0, 1, 0, 1, 0], [0, 0, 0, 0, 0]
364
+ // We can see that when processing the second window, the previous amount of empty slots from the back of the window (1), added with the empty elements from the front of the window (5)
365
+ // is greater than 5 (6) and therefore we have found a window to use.
366
+ // We simply need to take the number of elements (10) - the size of the window (5) - the number of consecutive empty elements from the back of the last window (1) = 4;
367
+ // This is the first index of our desired window.
368
+ // Note that if we ever see a situation like so:
369
+ // [0, 1, 0, 1, 0], [0, 0, 0, 0, 1]
370
+ // This also returns the correct index (4), but this is indicative of a problem / desync. i.e. we should never have a window that has a log that exists after the window.
371
+
372
+ do {
373
+ const currentTags = [...new Array(INDEX_OFFSET)].map((_, i) => {
374
+ const indexedAppTaggingSecret = new IndexedTaggingSecret(appTaggingSecret, currentIndex + i);
375
+ return indexedAppTaggingSecret.computeTag(recipient);
376
+ });
377
+ previousEmptyBack = currentEmptyBack;
378
+
379
+ const possibleLogs = await this.aztecNode.getLogsByTags(currentTags);
380
+
381
+ const indexOfFirstLog = possibleLogs.findIndex(possibleLog => possibleLog.length !== 0);
382
+ currentEmptyFront = indexOfFirstLog === -1 ? INDEX_OFFSET : indexOfFirstLog;
383
+
384
+ const indexOfLastLog = possibleLogs.findLastIndex(possibleLog => possibleLog.length !== 0);
385
+ currentEmptyBack = indexOfLastLog === -1 ? INDEX_OFFSET : INDEX_OFFSET - 1 - indexOfLastLog;
386
+
387
+ currentIndex += INDEX_OFFSET;
388
+ } while (currentEmptyFront + previousEmptyBack < INDEX_OFFSET);
389
+
390
+ // We unwind the entire current window and the amount of consecutive empty slots from the previous window
391
+ const newIndex = currentIndex - (INDEX_OFFSET + previousEmptyBack);
392
+
393
+ await this.db.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(appTaggingSecret, newIndex)]);
394
+
395
+ const contractName = await this.contractDataOracle.getDebugContractName(contractAddress);
396
+ this.log.debug(
397
+ `Syncing logs for sender ${sender}, secret ${appTaggingSecret}:${currentIndex} at contract: ${contractName}(${contractAddress})`,
398
+ );
399
+ }
400
+
332
401
  /**
333
402
  * Synchronizes the logs tagged with scoped addresses and all the senders in the addressbook.
334
403
  * Returns the unsynched logs and updates the indexes of the secrets used to tag them until there are no more logs to sync.
@@ -441,7 +510,12 @@ export class SimulatorOracle implements DBOracle {
441
510
 
442
511
  result.set(
443
512
  recipient.toString(),
444
- logs.filter(log => log.blockNumber <= maxBlockNumber),
513
+ // Remove logs with a block number higher than the max block number
514
+ // Duplicates are likely to happen due to the sliding window, so we also filter them out
515
+ logs.filter(
516
+ (log, index, self) =>
517
+ log.blockNumber <= maxBlockNumber && index === self.findIndex(otherLog => otherLog.equals(log)),
518
+ ),
445
519
  );
446
520
  }
447
521
  return result;
@@ -469,7 +543,7 @@ export class SimulatorOracle implements DBOracle {
469
543
  const incomingNotes: IncomingNoteDao[] = [];
470
544
  const outgoingNotes: OutgoingNoteDao[] = [];
471
545
 
472
- const txEffectsCache = new Map<string, TxEffect | undefined>();
546
+ const txEffectsCache = new Map<string, InBlock<TxEffect> | undefined>();
473
547
 
474
548
  for (const scopedLog of scopedLogs) {
475
549
  const incomingNotePayload = L1NotePayload.decryptAsIncoming(
@@ -510,11 +584,13 @@ export class SimulatorOracle implements DBOracle {
510
584
  // mocking ESM exports, we have to pollute the method even more by providing a simulator parameter so tests can inject a fake one.
511
585
  simulator ?? getAcirSimulator(this.db, this.aztecNode, this.keyStore, this.contractDataOracle),
512
586
  this.db,
513
- incomingNotePayload ? computePoint(recipient) : undefined,
587
+ incomingNotePayload ? recipient.toAddressPoint() : undefined,
514
588
  outgoingNotePayload ? recipientCompleteAddress.publicKeys.masterOutgoingViewingPublicKey : undefined,
515
589
  payload!,
516
- txEffect.txHash,
517
- txEffect.noteHashes,
590
+ txEffect.data.txHash,
591
+ txEffect.l2BlockNumber,
592
+ txEffect.l2BlockHash,
593
+ txEffect.data.noteHashes,
518
594
  scopedLog.dataStartIndexForTx,
519
595
  excludedIndices.get(scopedLog.txHash.toString())!,
520
596
  this.log,
@@ -557,17 +633,19 @@ export class SimulatorOracle implements DBOracle {
557
633
  }
558
634
  const nullifiedNotes: IncomingNoteDao[] = [];
559
635
  const currentNotesForRecipient = await this.db.getIncomingNotes({ owner: recipient });
560
- const nullifierIndexes = await this.aztecNode.findLeavesIndexes(
561
- 'latest',
562
- MerkleTreeId.NULLIFIER_TREE,
563
- currentNotesForRecipient.map(note => note.siloedNullifier),
564
- );
565
-
566
- const foundNullifiers = currentNotesForRecipient
567
- .filter((_, i) => nullifierIndexes[i] !== undefined)
568
- .map(note => note.siloedNullifier);
636
+ const nullifiersToCheck = currentNotesForRecipient.map(note => note.siloedNullifier);
637
+ const currentBlockNumber = await this.getBlockNumber();
638
+ const nullifierIndexes = await this.aztecNode.findNullifiersIndexesWithBlock(currentBlockNumber, nullifiersToCheck);
639
+
640
+ const foundNullifiers = nullifiersToCheck
641
+ .map((nullifier, i) => {
642
+ if (nullifierIndexes[i] !== undefined) {
643
+ return { ...nullifierIndexes[i], ...{ data: nullifier } } as InBlock<Fr>;
644
+ }
645
+ })
646
+ .filter(nullifier => nullifier !== undefined) as InBlock<Fr>[];
569
647
 
570
- await this.db.removeNullifiedNotes(foundNullifiers, computePoint(recipient));
648
+ await this.db.removeNullifiedNotes(foundNullifiers, recipient.toAddressPoint());
571
649
  nullifiedNotes.forEach(noteDao => {
572
650
  this.log.verbose(
573
651
  `Removed note for contract ${noteDao.contractAddress} at slot ${