@aztec/txe 0.66.0 → 0.67.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.
@@ -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,6 +31,7 @@ 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,
@@ -37,7 +41,13 @@ import {
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,20 @@ 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 privateLogs: PrivateLog[] = [];
112
+ private publicLogs: UnencryptedL2Log[] = [];
113
+
114
+ private committedBlocks = new Set<number>();
115
+
116
+ private node = new TXENode(this.blockNumber);
117
+
97
118
  constructor(
98
119
  private logger: Logger,
99
120
  private trees: MerkleTrees,
@@ -106,6 +127,7 @@ export class TXE implements TypedOracle {
106
127
  this.contractAddress = AztecAddress.random();
107
128
  // Default msg_sender (for entrypoints) is now Fr.max_value rather than 0 addr (see #7190 & #7404)
108
129
  this.msgSender = AztecAddress.fromField(Fr.MAX_FIELD_VALUE);
130
+ this.simulatorOracle = new SimulatorOracle(this.contractDataOracle, txeDatabase, keyStore, this.node);
109
131
  }
110
132
 
111
133
  // Utils
@@ -156,6 +178,7 @@ export class TXE implements TypedOracle {
156
178
 
157
179
  setBlockNumber(blockNumber: number) {
158
180
  this.blockNumber = blockNumber;
181
+ this.node.setBlockNumber(blockNumber);
159
182
  }
160
183
 
161
184
  getTrees() {
@@ -210,7 +233,7 @@ export class TXE implements TypedOracle {
210
233
  }
211
234
 
212
235
  async addAuthWitness(address: AztecAddress, messageHash: Fr) {
213
- const account = this.txeDatabase.getAccount(address);
236
+ const account = await this.txeDatabase.getAccount(address);
214
237
  const privateKey = await this.keyStore.getMasterSecretKey(account.publicKeys.masterIncomingViewingPublicKey);
215
238
  const schnorr = new Schnorr();
216
239
  const signature = schnorr.constructSignature(messageHash.toBuffer(), privateKey).toBuffer();
@@ -236,18 +259,66 @@ export class TXE implements TypedOracle {
236
259
  );
237
260
  }
238
261
 
262
+ async addSiloedNullifiersFromPublic(siloedNullifiers: Fr[]) {
263
+ this.siloedNullifiersFromPublic.push(...siloedNullifiers);
264
+
265
+ await this.addSiloedNullifiers(siloedNullifiers);
266
+ }
267
+
239
268
  async addNullifiers(contractAddress: AztecAddress, nullifiers: Fr[]) {
240
269
  const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier));
241
270
  await this.addSiloedNullifiers(siloedNullifiers);
242
271
  }
243
272
 
244
- async addSiloedNoteHashes(siloedNoteHashes: Fr[]) {
273
+ async addUniqueNoteHashes(siloedNoteHashes: Fr[]) {
245
274
  const db = await this.trees.getLatest();
246
275
  await db.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, siloedNoteHashes);
247
276
  }
277
+
278
+ async addUniqueNoteHashesFromPublic(siloedNoteHashes: Fr[]) {
279
+ this.uniqueNoteHashesFromPublic.push(...siloedNoteHashes);
280
+ await this.addUniqueNoteHashes(siloedNoteHashes);
281
+ }
282
+
248
283
  async addNoteHashes(contractAddress: AztecAddress, noteHashes: Fr[]) {
249
284
  const siloedNoteHashes = noteHashes.map(noteHash => siloNoteHash(contractAddress, noteHash));
250
- await this.addSiloedNoteHashes(siloedNoteHashes);
285
+
286
+ await this.addUniqueNoteHashes(siloedNoteHashes);
287
+ }
288
+
289
+ addPrivateLogs(contractAddress: AztecAddress, privateLogs: PrivateLog[]) {
290
+ privateLogs.forEach(privateLog => {
291
+ privateLog.fields[0] = poseidon2Hash([contractAddress, privateLog.fields[0]]);
292
+ });
293
+
294
+ this.privateLogs.push(...privateLogs);
295
+ }
296
+
297
+ addPublicLogs(logs: UnencryptedL2Log[]) {
298
+ logs.forEach(log => {
299
+ if (log.data.length < 32 * 33) {
300
+ // TODO remove when #9835 and #9836 are fixed
301
+ this.logger.warn(`Skipping unencrypted log with insufficient data length: ${log.data.length}`);
302
+ return;
303
+ }
304
+ try {
305
+ // TODO remove when #9835 and #9836 are fixed. The partial note logs are emitted as bytes, but encoded as Fields.
306
+ // This means that for every 32 bytes of payload, we only have 1 byte of data.
307
+ // 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.
308
+ const correctedBuffer = Buffer.alloc(32);
309
+ const initialOffset = 32;
310
+ for (let i = 0; i < 32; i++) {
311
+ const byte = Fr.fromBuffer(log.data.subarray(i * 32 + initialOffset, i * 32 + 32 + initialOffset)).toNumber();
312
+ correctedBuffer.writeUInt8(byte, i);
313
+ }
314
+ const tag = new Fr(correctedBuffer);
315
+
316
+ this.logger.verbose(`Found tagged unencrypted log with tag ${tag.toString()} in block ${this.blockNumber}`);
317
+ this.publicLogs.push(log);
318
+ } catch (err) {
319
+ this.logger.warn(`Failed to add tagged log to store: ${err}`);
320
+ }
321
+ });
251
322
  }
252
323
 
253
324
  // TypedOracle
@@ -298,11 +369,13 @@ export class TXE implements TypedOracle {
298
369
 
299
370
  async getMembershipWitness(blockNumber: number, treeId: MerkleTreeId, leafValue: Fr): Promise<Fr[] | undefined> {
300
371
  const db = await this.#getTreesAt(blockNumber);
372
+
301
373
  const index = await db.findLeafIndex(treeId, leafValue.toBuffer());
302
- if (!index) {
374
+ if (index === undefined) {
303
375
  throw new Error(`Leaf value: ${leafValue} not found in ${MerkleTreeId[treeId]} at block ${blockNumber}`);
304
376
  }
305
377
  const siblingPath = await db.getSiblingPath(treeId, index);
378
+
306
379
  return [new Fr(index), ...siblingPath.toFields()];
307
380
  }
308
381
 
@@ -355,15 +428,30 @@ export class TXE implements TypedOracle {
355
428
  }
356
429
  }
357
430
 
358
- getLowNullifierMembershipWitness(
359
- _blockNumber: number,
360
- _nullifier: Fr,
431
+ async getLowNullifierMembershipWitness(
432
+ blockNumber: number,
433
+ nullifier: Fr,
361
434
  ): Promise<NullifierMembershipWitness | undefined> {
362
- throw new Error('Method not implemented.');
435
+ const committedDb = await this.#getTreesAt(blockNumber);
436
+ const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
437
+ if (!findResult) {
438
+ return undefined;
439
+ }
440
+ const { index, alreadyPresent } = findResult;
441
+ if (alreadyPresent) {
442
+ this.logger.warn(`Nullifier ${nullifier.toBigInt()} already exists in the tree`);
443
+ }
444
+ const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
445
+
446
+ const siblingPath = await committedDb.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
447
+ MerkleTreeId.NULLIFIER_TREE,
448
+ BigInt(index),
449
+ );
450
+ return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
363
451
  }
364
452
 
365
- async getHeader(blockNumber: number): Promise<Header | undefined> {
366
- const header = Header.empty();
453
+ async getBlockHeader(blockNumber: number): Promise<BlockHeader | undefined> {
454
+ const header = BlockHeader.empty();
367
455
  const db = await this.#getTreesAt(blockNumber);
368
456
  header.state = await db.getStateReference();
369
457
  header.globalVariables.blockNumber = new Fr(blockNumber);
@@ -382,7 +470,7 @@ export class TXE implements TypedOracle {
382
470
  throw new Error('Method not implemented.');
383
471
  }
384
472
 
385
- getNotes(
473
+ async getNotes(
386
474
  storageSlot: Fr,
387
475
  numSelects: number,
388
476
  selectByIndexes: number[],
@@ -398,10 +486,12 @@ export class TXE implements TypedOracle {
398
486
  offset: number,
399
487
  _status: NoteStatus,
400
488
  ) {
401
- // Nullified pending notes are already removed from the list.
489
+ const syncedNotes = await this.simulatorOracle.getNotes(this.contractAddress, storageSlot, _status);
402
490
  const pendingNotes = this.noteCache.getNotes(this.contractAddress, storageSlot);
403
491
 
404
- const notes = pickNotes<NoteData>(pendingNotes, {
492
+ const notesToFilter = [...pendingNotes, ...syncedNotes];
493
+
494
+ const notes = pickNotes<NoteData>(notesToFilter, {
405
495
  selects: selectByIndexes.slice(0, numSelects).map((index, i) => ({
406
496
  selector: { index, offset: selectByOffsets[i], length: selectByLengths[i] },
407
497
  value: selectValues[i],
@@ -421,10 +511,10 @@ export class TXE implements TypedOracle {
421
511
  .join(', ')}`,
422
512
  );
423
513
 
424
- return Promise.resolve(notes);
514
+ return notes;
425
515
  }
426
516
 
427
- notifyCreatedNote(storageSlot: Fr, noteTypeId: NoteSelector, noteItems: Fr[], noteHash: Fr, counter: number) {
517
+ notifyCreatedNote(storageSlot: Fr, _noteTypeId: NoteSelector, noteItems: Fr[], noteHash: Fr, counter: number) {
428
518
  const note = new Note(noteItems);
429
519
  this.noteCache.addNewNote(
430
520
  {
@@ -506,6 +596,52 @@ export class TXE implements TypedOracle {
506
596
  return publicDataWrites.map(write => write.value);
507
597
  }
508
598
 
599
+ async commitState() {
600
+ const blockNumber = await this.getBlockNumber();
601
+ if (this.committedBlocks.has(blockNumber)) {
602
+ throw new Error('Already committed state');
603
+ } else {
604
+ this.committedBlocks.add(blockNumber);
605
+ }
606
+
607
+ const txEffect = TxEffect.empty();
608
+
609
+ let i = 0;
610
+ txEffect.noteHashes = [
611
+ ...this.noteCache
612
+ .getAllNotes()
613
+ .map(pendingNote =>
614
+ computeUniqueNoteHash(
615
+ computeNoteHashNonce(new Fr(this.blockNumber + 6969), i++),
616
+ siloNoteHash(pendingNote.note.contractAddress, pendingNote.noteHashForConsumption),
617
+ ),
618
+ ),
619
+ ...this.uniqueNoteHashesFromPublic,
620
+ ];
621
+ txEffect.nullifiers = [new Fr(blockNumber + 6969), ...this.noteCache.getAllNullifiers()];
622
+
623
+ // Using block number itself, (without adding 6969) gets killed at 1 as it says the slot is already used,
624
+ // it seems like we commit a 1 there to the trees before ? To see what I mean, uncomment these lines below
625
+ // let index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.ONE.toBuffer());
626
+ // console.log('INDEX OF ONE', index);
627
+ // index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.random().toBuffer());
628
+ // console.log('INDEX OF RANDOM', index);
629
+
630
+ this.node.setTxEffect(blockNumber, new TxHash(new Fr(blockNumber).toBuffer()), txEffect);
631
+ this.node.setNullifiersIndexesWithBlock(blockNumber, txEffect.nullifiers);
632
+ this.node.addNoteLogsByTags(this.blockNumber, this.privateLogs);
633
+ this.node.addPublicLogsByTags(this.blockNumber, this.publicLogs);
634
+
635
+ await this.addUniqueNoteHashes(txEffect.noteHashes);
636
+ await this.addSiloedNullifiers(txEffect.nullifiers);
637
+
638
+ this.privateLogs = [];
639
+ this.publicLogs = [];
640
+ this.uniqueNoteHashesFromPublic = [];
641
+ this.siloedNullifiersFromPublic = [];
642
+ this.noteCache = new ExecutionNoteCache(new Fr(1));
643
+ }
644
+
509
645
  emitContractClassLog(_log: UnencryptedL2Log, _counter: number): Fr {
510
646
  throw new Error('Method not implemented.');
511
647
  }
@@ -570,14 +706,9 @@ export class TXE implements TypedOracle {
570
706
  const endSideEffectCounter = publicInputs.endSideEffectCounter;
571
707
  this.sideEffectCounter = endSideEffectCounter.toNumber() + 1;
572
708
 
573
- await this.addNullifiers(
709
+ this.addPrivateLogs(
574
710
  targetContractAddress,
575
- publicInputs.nullifiers.filter(nullifier => !nullifier.isEmpty()).map(nullifier => nullifier.value),
576
- );
577
-
578
- await this.addNoteHashes(
579
- targetContractAddress,
580
- publicInputs.noteHashes.filter(noteHash => !noteHash.isEmpty()).map(noteHash => noteHash.value),
711
+ publicInputs.privateLogs.filter(privateLog => !privateLog.isEmpty()).map(privateLog => privateLog.log),
581
712
  );
582
713
 
583
714
  this.setContractAddress(currentContractAddress);
@@ -634,6 +765,7 @@ export class TXE implements TypedOracle {
634
765
  const executionRequest = new PublicExecutionRequest(callContext, args);
635
766
 
636
767
  const db = await this.trees.getLatest();
768
+ const worldStateDb = new TXEWorldStateDB(db, new TXEPublicContractDataSource(this));
637
769
 
638
770
  const globalVariables = GlobalVariables.empty();
639
771
  globalVariables.chainId = this.chainId;
@@ -641,12 +773,23 @@ export class TXE implements TypedOracle {
641
773
  globalVariables.blockNumber = new Fr(this.blockNumber);
642
774
  globalVariables.gasFees = new GasFees(1, 1);
643
775
 
776
+ // If the contract instance exists in the TXE's world state, make sure its nullifier is present in the tree
777
+ // so its nullifier check passes.
778
+ if ((await worldStateDb.getContractInstance(callContext.contractAddress)) !== undefined) {
779
+ const contractAddressNullifier = siloNullifier(
780
+ AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
781
+ callContext.contractAddress.toField(),
782
+ );
783
+ if ((await worldStateDb.getNullifierIndex(contractAddressNullifier)) === undefined) {
784
+ await db.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
785
+ }
786
+ }
787
+
644
788
  const simulator = new PublicTxSimulator(
645
789
  db,
646
790
  new TXEWorldStateDB(db, new TXEPublicContractDataSource(this)),
647
791
  new NoopTelemetryClient(),
648
792
  globalVariables,
649
- /*realAvmProvingRequests=*/ false,
650
793
  );
651
794
 
652
795
  // When setting up a teardown call, we tell it that
@@ -655,6 +798,9 @@ export class TXE implements TypedOracle {
655
798
  const tx = createTxForPublicCall(executionRequest, gasUsedByPrivate, isTeardown);
656
799
 
657
800
  const result = await simulator.simulate(tx);
801
+
802
+ this.addPublicLogs(tx.unencryptedLogs.unrollLogs());
803
+
658
804
  return Promise.resolve(result);
659
805
  }
660
806
 
@@ -707,7 +853,7 @@ export class TXE implements TypedOracle {
707
853
  const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
708
854
  const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
709
855
  await this.addPublicDataWrites(publicDataWrites);
710
- await this.addSiloedNoteHashes(noteHashes);
856
+ await this.addUniqueNoteHashesFromPublic(noteHashes);
711
857
  await this.addSiloedNullifiers(nullifiers);
712
858
 
713
859
  this.setContractAddress(currentContractAddress);
@@ -741,7 +887,7 @@ export class TXE implements TypedOracle {
741
887
  }
742
888
 
743
889
  debugLog(message: string, fields: Fr[]): void {
744
- this.logger.verbose(`debug_log ${applyStringFormatting(message, fields)}`);
890
+ this.logger.verbose(`${applyStringFormatting(message, fields)}`, { module: `${this.logger.module}:debug_log` });
745
891
  }
746
892
 
747
893
  async incrementAppTaggingSecretIndexAsSender(sender: AztecAddress, recipient: AztecAddress): Promise<void> {
@@ -765,8 +911,17 @@ export class TXE implements TypedOracle {
765
911
  return siloedSecret;
766
912
  }
767
913
 
768
- syncNotes() {
769
- // TODO: Implement
914
+ async syncNotes() {
915
+ const taggedLogsByRecipient = await this.simulatorOracle.syncTaggedLogs(
916
+ this.contractAddress,
917
+ await this.getBlockNumber(),
918
+ undefined,
919
+ );
920
+
921
+ for (const [recipient, taggedLogs] of taggedLogsByRecipient.entries()) {
922
+ await this.simulatorOracle.processTaggedLogs(taggedLogs, AztecAddress.fromString(recipient));
923
+ }
924
+
770
925
  return Promise.resolve();
771
926
  }
772
927
 
@@ -797,7 +952,7 @@ export class TXE implements TypedOracle {
797
952
  const noteHashes = sideEffects.noteHashes.filter(s => !s.isEmpty());
798
953
  const nullifiers = sideEffects.nullifiers.filter(s => !s.isEmpty());
799
954
  await this.addPublicDataWrites(publicDataWrites);
800
- await this.addSiloedNoteHashes(noteHashes);
955
+ await this.addUniqueNoteHashes(noteHashes);
801
956
  await this.addSiloedNullifiers(nullifiers);
802
957
  }
803
958
 
@@ -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,7 +14,7 @@ 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';
17
+ import { openTmpStore } from '@aztec/kv-store/lmdb';
18
18
  import { getCanonicalProtocolContract, protocolContractNames } from '@aztec/protocol-contracts';
19
19
  import { enrichPublicSimulationError } from '@aztec/pxe';
20
20
  import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator';
@@ -70,9 +70,12 @@ export class TXEService {
70
70
  const nBlocks = fromSingle(blocks).toNumber();
71
71
  this.logger.debug(`time traveling ${nBlocks} blocks`);
72
72
  const trees = (this.typedOracle as TXE).getTrees();
73
+
74
+ await (this.typedOracle as TXE).commitState();
75
+
73
76
  for (let i = 0; i < nBlocks; i++) {
74
77
  const blockNumber = await this.typedOracle.getBlockNumber();
75
- const header = Header.empty();
78
+ const header = BlockHeader.empty();
76
79
  const l2Block = L2Block.empty();
77
80
  header.state = await trees.getStateReference(true);
78
81
  header.globalVariables.blockNumber = new Fr(blockNumber);
@@ -550,8 +553,8 @@ export class TXEService {
550
553
  return toForeignCallResult([]);
551
554
  }
552
555
 
553
- async getHeader(blockNumber: ForeignCallSingle) {
554
- const header = await this.typedOracle.getHeader(fromSingle(blockNumber).toNumber());
556
+ async getBlockHeader(blockNumber: ForeignCallSingle) {
557
+ const header = await this.typedOracle.getBlockHeader(fromSingle(blockNumber).toNumber());
555
558
  if (!header) {
556
559
  throw new Error(`Block header not found for block ${blockNumber}.`);
557
560
  }
@@ -571,6 +574,16 @@ export class TXEService {
571
574
  return toForeignCallResult([toArray(witness)]);
572
575
  }
573
576
 
577
+ async getLowNullifierMembershipWitness(blockNumber: ForeignCallSingle, nullifier: ForeignCallSingle) {
578
+ const parsedBlockNumber = fromSingle(blockNumber).toNumber();
579
+
580
+ const witness = await this.typedOracle.getLowNullifierMembershipWitness(parsedBlockNumber, fromSingle(nullifier));
581
+ if (!witness) {
582
+ throw new Error(`Low nullifier witness not found for nullifier ${nullifier} at block ${parsedBlockNumber}.`);
583
+ }
584
+ return toForeignCallResult([toArray(witness.toFields())]);
585
+ }
586
+
574
587
  async getAppTaggingSecretAsSender(sender: ForeignCallSingle, recipient: ForeignCallSingle) {
575
588
  const secret = await this.typedOracle.getAppTaggingSecretAsSender(
576
589
  AztecAddress.fromField(fromSingle(sender)),
@@ -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
  }