@aztec/txe 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.
@@ -7,17 +7,20 @@ import {
7
7
  PublicDataWitness,
8
8
  PublicExecutionRequest,
9
9
  SimulationError,
10
+ TxEffect,
11
+ TxHash,
10
12
  type UnencryptedL2Log,
11
13
  } from '@aztec/circuit-types';
12
14
  import { type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats';
13
15
  import {
16
+ BlockHeader,
14
17
  CallContext,
15
18
  type ContractInstance,
16
19
  type ContractInstanceWithAddress,
20
+ DEPLOYER_CONTRACT_ADDRESS,
17
21
  Gas,
18
22
  GasFees,
19
23
  GlobalVariables,
20
- Header,
21
24
  IndexedTaggingSecret,
22
25
  type KeyValidationRequest,
23
26
  type L1_TO_L2_MSG_TREE_HEIGHT,
@@ -28,16 +31,23 @@ import {
28
31
  type PUBLIC_DATA_TREE_HEIGHT,
29
32
  PUBLIC_DISPATCH_SELECTOR,
30
33
  PrivateContextInputs,
34
+ type PrivateLog,
31
35
  PublicDataTreeLeaf,
32
36
  type PublicDataTreeLeafPreimage,
33
37
  type PublicDataWrite,
34
38
  computeContractClassId,
35
- computeTaggingSecret,
39
+ computeTaggingSecretPoint,
36
40
  deriveKeys,
37
41
  getContractClassFromArtifact,
38
42
  } from '@aztec/circuits.js';
39
43
  import { Schnorr } from '@aztec/circuits.js/barretenberg';
40
- import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash';
44
+ import {
45
+ computeNoteHashNonce,
46
+ computePublicDataTreeLeafSlot,
47
+ computeUniqueNoteHash,
48
+ siloNoteHash,
49
+ siloNullifier,
50
+ } from '@aztec/circuits.js/hash';
41
51
  import {
42
52
  type ContractArtifact,
43
53
  type FunctionAbi,
@@ -51,10 +61,10 @@ import { Fr } from '@aztec/foundation/fields';
51
61
  import { type Logger, applyStringFormatting } from '@aztec/foundation/log';
52
62
  import { Timer } from '@aztec/foundation/timer';
53
63
  import { type KeyStore } from '@aztec/key-store';
54
- import { ContractDataOracle, enrichPublicSimulationError } from '@aztec/pxe';
64
+ import { ContractDataOracle, SimulatorOracle, enrichPublicSimulationError } from '@aztec/pxe';
55
65
  import {
56
66
  ExecutionError,
57
- type ExecutionNoteCache,
67
+ ExecutionNoteCache,
58
68
  type MessageLoadOracleInputs,
59
69
  type NoteData,
60
70
  Oracle,
@@ -75,6 +85,7 @@ import { createTxForPublicCall } from '@aztec/simulator/public/fixtures';
75
85
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
76
86
  import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state';
77
87
 
88
+ import { TXENode } from '../node/txe_node.js';
78
89
  import { type TXEDatabase } from '../util/txe_database.js';
79
90
  import { TXEPublicContractDataSource } from '../util/txe_public_contract_data_source.js';
80
91
  import { TXEWorldStateDB } from '../util/txe_world_state_db.js';
@@ -90,10 +101,21 @@ export class TXE implements TypedOracle {
90
101
  private nestedCallReturndata: Fr[] = [];
91
102
 
92
103
  private contractDataOracle: ContractDataOracle;
104
+ private simulatorOracle: SimulatorOracle;
93
105
 
94
106
  private version: Fr = Fr.ONE;
95
107
  private chainId: Fr = Fr.ONE;
96
108
 
109
+ private uniqueNoteHashesFromPublic: Fr[] = [];
110
+ private siloedNullifiersFromPublic: Fr[] = [];
111
+ private siloedNullifiersFromPrivate: Set<string> = new Set();
112
+ private privateLogs: PrivateLog[] = [];
113
+ private publicLogs: UnencryptedL2Log[] = [];
114
+
115
+ private committedBlocks = new Set<number>();
116
+
117
+ private node = new TXENode(this.blockNumber);
118
+
97
119
  constructor(
98
120
  private logger: Logger,
99
121
  private trees: MerkleTrees,
@@ -106,6 +128,7 @@ export class TXE implements TypedOracle {
106
128
  this.contractAddress = AztecAddress.random();
107
129
  // Default msg_sender (for entrypoints) is now Fr.max_value rather than 0 addr (see #7190 & #7404)
108
130
  this.msgSender = AztecAddress.fromField(Fr.MAX_FIELD_VALUE);
131
+ this.simulatorOracle = new SimulatorOracle(this.contractDataOracle, txeDatabase, keyStore, this.node);
109
132
  }
110
133
 
111
134
  // Utils
@@ -156,6 +179,7 @@ export class TXE implements TypedOracle {
156
179
 
157
180
  setBlockNumber(blockNumber: number) {
158
181
  this.blockNumber = blockNumber;
182
+ this.node.setBlockNumber(blockNumber);
159
183
  }
160
184
 
161
185
  getTrees() {
@@ -210,7 +234,7 @@ export class TXE implements TypedOracle {
210
234
  }
211
235
 
212
236
  async addAuthWitness(address: AztecAddress, messageHash: Fr) {
213
- const account = this.txeDatabase.getAccount(address);
237
+ const account = await this.txeDatabase.getAccount(address);
214
238
  const privateKey = await this.keyStore.getMasterSecretKey(account.publicKeys.masterIncomingViewingPublicKey);
215
239
  const schnorr = new Schnorr();
216
240
  const signature = schnorr.constructSignature(messageHash.toBuffer(), privateKey).toBuffer();
@@ -236,18 +260,82 @@ export class TXE implements TypedOracle {
236
260
  );
237
261
  }
238
262
 
263
+ async addNullifiersFromPrivate(contractAddress: AztecAddress, nullifiers: Fr[]) {
264
+ const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier));
265
+ const db = await this.trees.getLatest();
266
+ const nullifierIndexesInTree = await db.findLeafIndices(
267
+ MerkleTreeId.NULLIFIER_TREE,
268
+ siloedNullifiers.map(n => n.toBuffer()),
269
+ );
270
+ const notInTree = nullifierIndexesInTree.every(index => index === undefined);
271
+ const notInCache = siloedNullifiers.every(n => !this.siloedNullifiersFromPrivate.has(n.toString()));
272
+ if (notInTree && notInCache) {
273
+ siloedNullifiers.forEach(n => this.siloedNullifiersFromPrivate.add(n.toString()));
274
+ } else {
275
+ throw new Error(`Rejecting tx for emitting duplicate nullifiers`);
276
+ }
277
+ }
278
+
279
+ async addSiloedNullifiersFromPublic(siloedNullifiers: Fr[]) {
280
+ this.siloedNullifiersFromPublic.push(...siloedNullifiers);
281
+
282
+ await this.addSiloedNullifiers(siloedNullifiers);
283
+ }
284
+
239
285
  async addNullifiers(contractAddress: AztecAddress, nullifiers: Fr[]) {
240
286
  const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier));
241
287
  await this.addSiloedNullifiers(siloedNullifiers);
242
288
  }
243
289
 
244
- async addSiloedNoteHashes(siloedNoteHashes: Fr[]) {
290
+ async addUniqueNoteHashes(siloedNoteHashes: Fr[]) {
245
291
  const db = await this.trees.getLatest();
246
292
  await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, siloedNoteHashes);
247
293
  }
294
+
295
+ async addUniqueNoteHashesFromPublic(siloedNoteHashes: Fr[]) {
296
+ this.uniqueNoteHashesFromPublic.push(...siloedNoteHashes);
297
+ await this.addUniqueNoteHashes(siloedNoteHashes);
298
+ }
299
+
248
300
  async addNoteHashes(contractAddress: AztecAddress, noteHashes: Fr[]) {
249
301
  const siloedNoteHashes = noteHashes.map(noteHash => siloNoteHash(contractAddress, noteHash));
250
- await this.addSiloedNoteHashes(siloedNoteHashes);
302
+
303
+ await this.addUniqueNoteHashes(siloedNoteHashes);
304
+ }
305
+
306
+ addPrivateLogs(contractAddress: AztecAddress, privateLogs: PrivateLog[]) {
307
+ privateLogs.forEach(privateLog => {
308
+ privateLog.fields[0] = poseidon2Hash([contractAddress, privateLog.fields[0]]);
309
+ });
310
+
311
+ this.privateLogs.push(...privateLogs);
312
+ }
313
+
314
+ addPublicLogs(logs: UnencryptedL2Log[]) {
315
+ logs.forEach(log => {
316
+ if (log.data.length < 32 * 33) {
317
+ // TODO remove when #9835 and #9836 are fixed
318
+ this.logger.warn(`Skipping unencrypted log with insufficient data length: ${log.data.length}`);
319
+ return;
320
+ }
321
+ try {
322
+ // TODO remove when #9835 and #9836 are fixed. The partial note logs are emitted as bytes, but encoded as Fields.
323
+ // This means that for every 32 bytes of payload, we only have 1 byte of data.
324
+ // Also, the tag is not stored in the first 32 bytes of the log, (that's the length of public fields now) but in the next 32.
325
+ const correctedBuffer = Buffer.alloc(32);
326
+ const initialOffset = 32;
327
+ for (let i = 0; i < 32; i++) {
328
+ const byte = Fr.fromBuffer(log.data.subarray(i * 32 + initialOffset, i * 32 + 32 + initialOffset)).toNumber();
329
+ correctedBuffer.writeUInt8(byte, i);
330
+ }
331
+ const tag = new Fr(correctedBuffer);
332
+
333
+ this.logger.verbose(`Found tagged unencrypted log with tag ${tag.toString()} in block ${this.blockNumber}`);
334
+ this.publicLogs.push(log);
335
+ } catch (err) {
336
+ this.logger.warn(`Failed to add tagged log to store: ${err}`);
337
+ }
338
+ });
251
339
  }
252
340
 
253
341
  // TypedOracle
@@ -298,11 +386,12 @@ export class TXE implements TypedOracle {
298
386
 
299
387
  async getMembershipWitness(blockNumber: number, treeId: MerkleTreeId, leafValue: Fr): Promise<Fr[] | undefined> {
300
388
  const db = await this.#getTreesAt(blockNumber);
301
- const index = await db.findLeafIndex(treeId, leafValue.toBuffer());
302
- if (!index) {
389
+ const index = (await db.findLeafIndices(treeId, [leafValue.toBuffer()]))[0];
390
+ if (index === undefined) {
303
391
  throw new Error(`Leaf value: ${leafValue} not found in ${MerkleTreeId[treeId]} at block ${blockNumber}`);
304
392
  }
305
393
  const siblingPath = await db.getSiblingPath(treeId, index);
394
+
306
395
  return [new Fr(index), ...siblingPath.toFields()];
307
396
  }
308
397
 
@@ -317,7 +406,7 @@ export class TXE implements TypedOracle {
317
406
  nullifier: Fr,
318
407
  ): Promise<NullifierMembershipWitness | undefined> {
319
408
  const db = await this.#getTreesAt(blockNumber);
320
- const index = await db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
409
+ const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
321
410
  if (!index) {
322
411
  return undefined;
323
412
  }
@@ -355,15 +444,30 @@ export class TXE implements TypedOracle {
355
444
  }
356
445
  }
357
446
 
358
- getLowNullifierMembershipWitness(
359
- _blockNumber: number,
360
- _nullifier: Fr,
447
+ async getLowNullifierMembershipWitness(
448
+ blockNumber: number,
449
+ nullifier: Fr,
361
450
  ): Promise<NullifierMembershipWitness | undefined> {
362
- throw new Error('Method not implemented.');
451
+ const committedDb = await this.#getTreesAt(blockNumber);
452
+ const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
453
+ if (!findResult) {
454
+ return undefined;
455
+ }
456
+ const { index, alreadyPresent } = findResult;
457
+ if (alreadyPresent) {
458
+ this.logger.warn(`Nullifier ${nullifier.toBigInt()} already exists in the tree`);
459
+ }
460
+ const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
461
+
462
+ const siblingPath = await committedDb.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
463
+ MerkleTreeId.NULLIFIER_TREE,
464
+ BigInt(index),
465
+ );
466
+ return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
363
467
  }
364
468
 
365
- async getHeader(blockNumber: number): Promise<Header | undefined> {
366
- const header = Header.empty();
469
+ async getBlockHeader(blockNumber: number): Promise<BlockHeader | undefined> {
470
+ const header = BlockHeader.empty();
367
471
  const db = await this.#getTreesAt(blockNumber);
368
472
  header.state = await db.getStateReference();
369
473
  header.globalVariables.blockNumber = new Fr(blockNumber);
@@ -382,7 +486,7 @@ export class TXE implements TypedOracle {
382
486
  throw new Error('Method not implemented.');
383
487
  }
384
488
 
385
- getNotes(
489
+ async getNotes(
386
490
  storageSlot: Fr,
387
491
  numSelects: number,
388
492
  selectByIndexes: number[],
@@ -396,12 +500,16 @@ export class TXE implements TypedOracle {
396
500
  sortOrder: number[],
397
501
  limit: number,
398
502
  offset: number,
399
- _status: NoteStatus,
503
+ status: NoteStatus,
400
504
  ) {
401
505
  // Nullified pending notes are already removed from the list.
402
506
  const pendingNotes = this.noteCache.getNotes(this.contractAddress, storageSlot);
403
507
 
404
- const notes = pickNotes<NoteData>(pendingNotes, {
508
+ const pendingNullifiers = this.noteCache.getNullifiers(this.contractAddress);
509
+ const dbNotes = await this.simulatorOracle.getNotes(this.contractAddress, storageSlot, status);
510
+ const dbNotesFiltered = dbNotes.filter(n => !pendingNullifiers.has((n.siloedNullifier as Fr).value));
511
+
512
+ const notes = pickNotes<NoteData>([...dbNotesFiltered, ...pendingNotes], {
405
513
  selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({
406
514
  selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] },
407
515
  value: selectValues[i],
@@ -421,10 +529,10 @@ export class TXE implements TypedOracle {
421
529
  .join(', ')}`,
422
530
  );
423
531
 
424
- return Promise.resolve(notes);
532
+ return notes;
425
533
  }
426
534
 
427
- notifyCreatedNote(storageSlot: Fr, noteTypeId: NoteSelector, noteItems: Fr[], noteHash: Fr, counter: number) {
535
+ notifyCreatedNote(storageSlot: Fr, _noteTypeId: NoteSelector, noteItems: Fr[], noteHash: Fr, counter: number) {
428
536
  const note = new Note(noteItems);
429
537
  this.noteCache.addNewNote(
430
538
  {
@@ -441,7 +549,8 @@ export class TXE implements TypedOracle {
441
549
  return Promise.resolve();
442
550
  }
443
551
 
444
- notifyNullifiedNote(innerNullifier: Fr, noteHash: Fr, counter: number) {
552
+ async notifyNullifiedNote(innerNullifier: Fr, noteHash: Fr, counter: number) {
553
+ await this.addNullifiersFromPrivate(this.contractAddress, [innerNullifier]);
445
554
  this.noteCache.nullifyNote(this.contractAddress, innerNullifier, noteHash);
446
555
  this.sideEffectCounter = counter + 1;
447
556
  return Promise.resolve();
@@ -450,7 +559,7 @@ export class TXE implements TypedOracle {
450
559
  async checkNullifierExists(innerNullifier: Fr): Promise<boolean> {
451
560
  const nullifier = siloNullifier(this.contractAddress, innerNullifier!);
452
561
  const db = await this.trees.getLatest();
453
- const index = await db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
562
+ const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
454
563
  return index !== undefined;
455
564
  }
456
565
 
@@ -506,6 +615,56 @@ export class TXE implements TypedOracle {
506
615
  return publicDataWrites.map(write => write.value);
507
616
  }
508
617
 
618
+ async commitState() {
619
+ const blockNumber = await this.getBlockNumber();
620
+ if (this.committedBlocks.has(blockNumber)) {
621
+ throw new Error('Already committed state');
622
+ } else {
623
+ this.committedBlocks.add(blockNumber);
624
+ }
625
+
626
+ const txEffect = TxEffect.empty();
627
+
628
+ let i = 0;
629
+ txEffect.noteHashes = [
630
+ ...this.noteCache
631
+ .getAllNotes()
632
+ .map(pendingNote =>
633
+ computeUniqueNoteHash(
634
+ computeNoteHashNonce(new Fr(this.blockNumber + 6969), i++),
635
+ siloNoteHash(pendingNote.note.contractAddress, pendingNote.noteHashForConsumption),
636
+ ),
637
+ ),
638
+ ...this.uniqueNoteHashesFromPublic,
639
+ ];
640
+ txEffect.nullifiers = [
641
+ new Fr(blockNumber + 6969),
642
+ ...Array.from(this.siloedNullifiersFromPrivate).map(n => Fr.fromString(n)),
643
+ ];
644
+
645
+ // Using block number itself, (without adding 6969) gets killed at 1 as it says the slot is already used,
646
+ // it seems like we commit a 1 there to the trees before ? To see what I mean, uncomment these lines below
647
+ // let index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.ONE.toBuffer());
648
+ // console.log('INDEX OF ONE', index);
649
+ // index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.random().toBuffer());
650
+ // console.log('INDEX OF RANDOM', index);
651
+
652
+ this.node.setTxEffect(blockNumber, new TxHash(new Fr(blockNumber).toBuffer()), txEffect);
653
+ this.node.setNullifiersIndexesWithBlock(blockNumber, txEffect.nullifiers);
654
+ this.node.addNoteLogsByTags(this.blockNumber, this.privateLogs);
655
+ this.node.addPublicLogsByTags(this.blockNumber, this.publicLogs);
656
+
657
+ await this.addUniqueNoteHashes(txEffect.noteHashes);
658
+ await this.addSiloedNullifiers(txEffect.nullifiers);
659
+
660
+ this.privateLogs = [];
661
+ this.publicLogs = [];
662
+ this.siloedNullifiersFromPrivate = new Set();
663
+ this.uniqueNoteHashesFromPublic = [];
664
+ this.siloedNullifiersFromPublic = [];
665
+ this.noteCache = new ExecutionNoteCache(new Fr(1));
666
+ }
667
+
509
668
  emitContractClassLog(_log: UnencryptedL2Log, _counter: number): Fr {
510
669
  throw new Error('Method not implemented.');
511
670
  }
@@ -570,15 +729,28 @@ export class TXE implements TypedOracle {
570
729
  const endSideEffectCounter = publicInputs.endSideEffectCounter;
571
730
  this.sideEffectCounter = endSideEffectCounter.toNumber() + 1;
572
731
 
573
- await this.addNullifiers(
732
+ this.addPrivateLogs(
574
733
  targetContractAddress,
575
- publicInputs.nullifiers.filter(nullifier => !nullifier.isEmpty()).map(nullifier => nullifier.value),
734
+ publicInputs.privateLogs.filter(privateLog => !privateLog.isEmpty()).map(privateLog => privateLog.log),
576
735
  );
577
736
 
578
- await this.addNoteHashes(
579
- targetContractAddress,
580
- publicInputs.noteHashes.filter(noteHash => !noteHash.isEmpty()).map(noteHash => noteHash.value),
581
- );
737
+ const executionNullifiers = publicInputs.nullifiers
738
+ .filter(nullifier => !nullifier.isEmpty())
739
+ .map(nullifier => nullifier.value);
740
+ // We inject nullifiers into siloedNullifiersFromPrivate from notifyNullifiedNote,
741
+ // so top level calls to destroyNote work as expected. As such, we are certain
742
+ // that we would insert duplicates if we just took the nullifiers from the public inputs and
743
+ // blindly inserted them into siloedNullifiersFromPrivate. To avoid this, we extract the first
744
+ // (and only the first!) duplicated nullifier from the public inputs, so we can just push
745
+ // the ones that were not created by deleting a note
746
+ const firstDuplicateIndexes = executionNullifiers
747
+ .map((nullifier, index) => {
748
+ const siloedNullifier = siloNullifier(targetContractAddress, nullifier);
749
+ return this.siloedNullifiersFromPrivate.has(siloedNullifier.toString()) ? index : -1;
750
+ })
751
+ .filter(index => index !== -1);
752
+ const nonNoteNullifiers = executionNullifiers.filter((_, index) => !firstDuplicateIndexes.includes(index));
753
+ await this.addNullifiersFromPrivate(targetContractAddress, nonNoteNullifiers);
582
754
 
583
755
  this.setContractAddress(currentContractAddress);
584
756
  this.setMsgSender(currentMessageSender);
@@ -634,6 +806,7 @@ export class TXE implements TypedOracle {
634
806
  const executionRequest = new PublicExecutionRequest(callContext, args);
635
807
 
636
808
  const db = await this.trees.getLatest();
809
+ const worldStateDb = new TXEWorldStateDB(db, new TXEPublicContractDataSource(this));
637
810
 
638
811
  const globalVariables = GlobalVariables.empty();
639
812
  globalVariables.chainId = this.chainId;
@@ -641,12 +814,23 @@ export class TXE implements TypedOracle {
641
814
  globalVariables.blockNumber = new Fr(this.blockNumber);
642
815
  globalVariables.gasFees = new GasFees(1, 1);
643
816
 
817
+ // If the contract instance exists in the TXE's world state, make sure its nullifier is present in the tree
818
+ // so its nullifier check passes.
819
+ if ((await worldStateDb.getContractInstance(callContext.contractAddress)) !== undefined) {
820
+ const contractAddressNullifier = siloNullifier(
821
+ AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
822
+ callContext.contractAddress.toField(),
823
+ );
824
+ if ((await worldStateDb.getNullifierIndex(contractAddressNullifier)) === undefined) {
825
+ await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
826
+ }
827
+ }
828
+
644
829
  const simulator = new PublicTxSimulator(
645
830
  db,
646
831
  new TXEWorldStateDB(db, new TXEPublicContractDataSource(this)),
647
832
  new NoopTelemetryClient(),
648
833
  globalVariables,
649
- /*realAvmProvingRequests=*/ false,
650
834
  );
651
835
 
652
836
  // When setting up a teardown call, we tell it that
@@ -655,6 +839,9 @@ export class TXE implements TypedOracle {
655
839
  const tx = createTxForPublicCall(executionRequest, gasUsedByPrivate, isTeardown);
656
840
 
657
841
  const result = await simulator.simulate(tx);
842
+
843
+ this.addPublicLogs(tx.unencryptedLogs.unrollLogs());
844
+
658
845
  return Promise.resolve(result);
659
846
  }
660
847
 
@@ -707,7 +894,7 @@ export class TXE implements TypedOracle {
707
894
  const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
708
895
  const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
709
896
  await this.addPublicDataWrites(publicDataWrites);
710
- await this.addSiloedNoteHashes(noteHashes);
897
+ await this.addUniqueNoteHashesFromPublic(noteHashes);
711
898
  await this.addSiloedNullifiers(nullifiers);
712
899
 
713
900
  this.setContractAddress(currentContractAddress);
@@ -741,32 +928,43 @@ export class TXE implements TypedOracle {
741
928
  }
742
929
 
743
930
  debugLog(message: string, fields: Fr[]): void {
744
- this.logger.verbose(`debug_log ${applyStringFormatting(message, fields)}`);
931
+ this.logger.verbose(`${applyStringFormatting(message, fields)}`, { module: `${this.logger.module}:debug_log` });
745
932
  }
746
933
 
747
934
  async incrementAppTaggingSecretIndexAsSender(sender: AztecAddress, recipient: AztecAddress): Promise<void> {
748
- const appSecret = await this.#calculateTaggingSecret(this.contractAddress, sender, recipient);
935
+ const appSecret = await this.#calculateAppTaggingSecret(this.contractAddress, sender, recipient);
749
936
  const [index] = await this.txeDatabase.getTaggingSecretsIndexesAsSender([appSecret]);
750
937
  await this.txeDatabase.setTaggingSecretsIndexesAsSender([new IndexedTaggingSecret(appSecret, index + 1)]);
751
938
  }
752
939
 
753
- async getAppTaggingSecretAsSender(sender: AztecAddress, recipient: AztecAddress): Promise<IndexedTaggingSecret> {
754
- const secret = await this.#calculateTaggingSecret(this.contractAddress, sender, recipient);
940
+ async getIndexedTaggingSecretAsSender(sender: AztecAddress, recipient: AztecAddress): Promise<IndexedTaggingSecret> {
941
+ const secret = await this.#calculateAppTaggingSecret(this.contractAddress, sender, recipient);
755
942
  const [index] = await this.txeDatabase.getTaggingSecretsIndexesAsSender([secret]);
756
943
  return new IndexedTaggingSecret(secret, index);
757
944
  }
758
945
 
759
- async #calculateTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
946
+ async #calculateAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress) {
760
947
  const senderCompleteAddress = await this.getCompleteAddress(sender);
761
948
  const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender);
762
- const sharedSecret = computeTaggingSecret(senderCompleteAddress, senderIvsk, recipient);
949
+ const secretPoint = computeTaggingSecretPoint(senderCompleteAddress, senderIvsk, recipient);
763
950
  // Silo the secret to the app so it can't be used to track other app's notes
764
- const siloedSecret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]);
765
- return siloedSecret;
951
+ const appSecret = poseidon2Hash([secretPoint.x, secretPoint.y, contractAddress]);
952
+ return appSecret;
766
953
  }
767
954
 
768
- syncNotes() {
769
- // TODO: Implement
955
+ async syncNotes() {
956
+ const taggedLogsByRecipient = await this.simulatorOracle.syncTaggedLogs(
957
+ this.contractAddress,
958
+ await this.getBlockNumber(),
959
+ undefined,
960
+ );
961
+
962
+ for (const [recipient, taggedLogs] of taggedLogsByRecipient.entries()) {
963
+ await this.simulatorOracle.processTaggedLogs(taggedLogs, AztecAddress.fromString(recipient));
964
+ }
965
+
966
+ await this.simulatorOracle.removeNullifiedNotes(this.contractAddress);
967
+
770
968
  return Promise.resolve();
771
969
  }
772
970
 
@@ -797,7 +995,7 @@ export class TXE implements TypedOracle {
797
995
  const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
798
996
  const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
799
997
  await this.addPublicDataWrites(publicDataWrites);
800
- await this.addSiloedNoteHashes(noteHashes);
998
+ await this.addUniqueNoteHashes(noteHashes);
801
999
  await this.addSiloedNullifiers(nullifiers);
802
1000
  }
803
1001
 
@@ -818,7 +1016,7 @@ export class TXE implements TypedOracle {
818
1016
  async avmOpcodeNullifierExists(innerNullifier: Fr, targetAddress: AztecAddress): Promise<boolean> {
819
1017
  const nullifier = siloNullifier(targetAddress, innerNullifier!);
820
1018
  const db = await this.trees.getLatest();
821
- const index = await db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
1019
+ const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
822
1020
  return index !== undefined;
823
1021
  }
824
1022
 
@@ -1,9 +1,9 @@
1
1
  import { SchnorrAccountContractArtifact } from '@aztec/accounts/schnorr';
2
2
  import { L2Block, MerkleTreeId, SimulationError } from '@aztec/circuit-types';
3
3
  import {
4
+ BlockHeader,
4
5
  Fr,
5
6
  FunctionSelector,
6
- Header,
7
7
  PublicDataTreeLeaf,
8
8
  PublicKeys,
9
9
  computePartialAddress,
@@ -14,8 +14,9 @@ import { type ContractArtifact, NoteSelector } from '@aztec/foundation/abi';
14
14
  import { AztecAddress } from '@aztec/foundation/aztec-address';
15
15
  import { type Logger } from '@aztec/foundation/log';
16
16
  import { KeyStore } from '@aztec/key-store';
17
- import { openTmpStore } from '@aztec/kv-store/utils';
18
- import { getCanonicalProtocolContract, protocolContractNames } from '@aztec/protocol-contracts';
17
+ import { openTmpStore } from '@aztec/kv-store/lmdb';
18
+ import { protocolContractNames } from '@aztec/protocol-contracts';
19
+ import { getCanonicalProtocolContract } from '@aztec/protocol-contracts/bundle';
19
20
  import { enrichPublicSimulationError } from '@aztec/pxe';
20
21
  import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator';
21
22
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
@@ -70,9 +71,12 @@ export class TXEService {
70
71
  const nBlocks = fromSingle(blocks).toNumber();
71
72
  this.logger.debug(`time traveling ${nBlocks} blocks`);
72
73
  const trees = (this.typedOracle as TXE).getTrees();
74
+
75
+ await (this.typedOracle as TXE).commitState();
76
+
73
77
  for (let i = 0; i < nBlocks; i++) {
74
78
  const blockNumber = await this.typedOracle.getBlockNumber();
75
- const header = Header.empty();
79
+ const header = BlockHeader.empty();
76
80
  const l2Block = L2Block.empty();
77
81
  header.state = await trees.getStateReference(true);
78
82
  header.globalVariables.blockNumber = new Fr(blockNumber);
@@ -550,8 +554,8 @@ export class TXEService {
550
554
  return toForeignCallResult([]);
551
555
  }
552
556
 
553
- async getHeader(blockNumber: ForeignCallSingle) {
554
- const header = await this.typedOracle.getHeader(fromSingle(blockNumber).toNumber());
557
+ async getBlockHeader(blockNumber: ForeignCallSingle) {
558
+ const header = await this.typedOracle.getBlockHeader(fromSingle(blockNumber).toNumber());
555
559
  if (!header) {
556
560
  throw new Error(`Block header not found for block ${blockNumber}.`);
557
561
  }
@@ -571,8 +575,18 @@ export class TXEService {
571
575
  return toForeignCallResult([toArray(witness)]);
572
576
  }
573
577
 
574
- async getAppTaggingSecretAsSender(sender: ForeignCallSingle, recipient: ForeignCallSingle) {
575
- const secret = await this.typedOracle.getAppTaggingSecretAsSender(
578
+ async getLowNullifierMembershipWitness(blockNumber: ForeignCallSingle, nullifier: ForeignCallSingle) {
579
+ const parsedBlockNumber = fromSingle(blockNumber).toNumber();
580
+
581
+ const witness = await this.typedOracle.getLowNullifierMembershipWitness(parsedBlockNumber, fromSingle(nullifier));
582
+ if (!witness) {
583
+ throw new Error(`Low nullifier witness not found for nullifier ${nullifier} at block ${parsedBlockNumber}.`);
584
+ }
585
+ return toForeignCallResult([toArray(witness.toFields())]);
586
+ }
587
+
588
+ async getIndexedTaggingSecretAsSender(sender: ForeignCallSingle, recipient: ForeignCallSingle) {
589
+ const secret = await this.typedOracle.getIndexedTaggingSecretAsSender(
576
590
  AztecAddress.fromField(fromSingle(sender)),
577
591
  AztecAddress.fromField(fromSingle(recipient)),
578
592
  );
@@ -1,17 +1,17 @@
1
1
  import { type AztecAddress, CompleteAddress } from '@aztec/circuits.js';
2
- import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';
2
+ import { type AztecAsyncKVStore, type AztecAsyncMap } from '@aztec/kv-store';
3
3
  import { KVPxeDatabase } from '@aztec/pxe';
4
4
 
5
5
  export class TXEDatabase extends KVPxeDatabase {
6
- #accounts: AztecMap<string, Buffer>;
6
+ #accounts: AztecAsyncMap<string, Buffer>;
7
7
 
8
- constructor(db: AztecKVStore) {
8
+ constructor(db: AztecAsyncKVStore) {
9
9
  super(db);
10
10
  this.#accounts = db.openMap('accounts');
11
11
  }
12
12
 
13
- getAccount(key: AztecAddress) {
14
- const completeAddress = this.#accounts.get(key.toString());
13
+ async getAccount(key: AztecAddress) {
14
+ const completeAddress = await this.#accounts.getAsync(key.toString());
15
15
  if (!completeAddress) {
16
16
  throw new Error(`Account not found: ${key.toString()}`);
17
17
  }
@@ -20,5 +20,6 @@ export class TXEDatabase extends KVPxeDatabase {
20
20
 
21
21
  async setAccount(key: AztecAddress, value: CompleteAddress) {
22
22
  await this.#accounts.set(key.toString(), value.toBuffer());
23
+ await this.addCompleteAddress(value);
23
24
  }
24
25
  }