@aztec/txe 0.69.1-devnet → 0.70.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.
@@ -63,25 +63,27 @@ import { Timer } from '@aztec/foundation/timer';
63
63
  import { type KeyStore } from '@aztec/key-store';
64
64
  import { ContractDataOracle, SimulatorOracle, enrichPublicSimulationError } from '@aztec/pxe';
65
65
  import {
66
- ExecutionError,
67
66
  ExecutionNoteCache,
68
67
  type MessageLoadOracleInputs,
69
68
  type NoteData,
70
69
  Oracle,
71
- type PackedValuesCache,
72
- type PublicTxResult,
73
- PublicTxSimulator,
74
70
  type TypedOracle,
75
- acvm,
76
- createSimulationError,
71
+ WASMSimulator,
77
72
  extractCallStack,
78
73
  extractPrivateCircuitPublicInputs,
79
74
  pickNotes,
80
- resolveAssertionMessageFromError,
81
75
  toACVMWitness,
82
76
  witnessMapToFields,
83
- } from '@aztec/simulator';
84
- import { createTxForPublicCall } from '@aztec/simulator/public/fixtures';
77
+ } from '@aztec/simulator/client';
78
+ import { createTxForPublicCalls } from '@aztec/simulator/public/fixtures';
79
+ import {
80
+ ExecutionError,
81
+ type HashedValuesCache,
82
+ type PublicTxResult,
83
+ PublicTxSimulator,
84
+ createSimulationError,
85
+ resolveAssertionMessageFromError,
86
+ } from '@aztec/simulator/server';
85
87
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
86
88
  import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state';
87
89
 
@@ -108,7 +110,6 @@ export class TXE implements TypedOracle {
108
110
 
109
111
  private uniqueNoteHashesFromPublic: Fr[] = [];
110
112
  private siloedNullifiersFromPublic: Fr[] = [];
111
- private siloedNullifiersFromPrivate: Set<string> = new Set();
112
113
  private privateLogs: PrivateLog[] = [];
113
114
  private publicLogs: UnencryptedL2Log[] = [];
114
115
 
@@ -116,21 +117,31 @@ export class TXE implements TypedOracle {
116
117
 
117
118
  private node = new TXENode(this.blockNumber);
118
119
 
120
+ private simulationProvider = new WASMSimulator();
121
+
122
+ private noteCache: ExecutionNoteCache;
123
+
119
124
  debug: LogFn;
120
125
 
121
126
  constructor(
122
127
  private logger: Logger,
123
128
  private trees: MerkleTrees,
124
- private packedValuesCache: PackedValuesCache,
125
- private noteCache: ExecutionNoteCache,
129
+ private executionCache: HashedValuesCache,
126
130
  private keyStore: KeyStore,
127
131
  private txeDatabase: TXEDatabase,
128
132
  ) {
133
+ this.noteCache = new ExecutionNoteCache(this.getTxRequestHash());
129
134
  this.contractDataOracle = new ContractDataOracle(txeDatabase);
130
135
  this.contractAddress = AztecAddress.random();
131
136
  // Default msg_sender (for entrypoints) is now Fr.max_value rather than 0 addr (see #7190 & #7404)
132
137
  this.msgSender = AztecAddress.fromField(Fr.MAX_FIELD_VALUE);
133
- this.simulatorOracle = new SimulatorOracle(this.contractDataOracle, txeDatabase, keyStore, this.node);
138
+ this.simulatorOracle = new SimulatorOracle(
139
+ this.contractDataOracle,
140
+ txeDatabase,
141
+ keyStore,
142
+ this.node,
143
+ this.simulationProvider,
144
+ );
134
145
 
135
146
  this.debug = createDebugOnlyLogger('aztec:kv-pxe-database');
136
147
  }
@@ -264,18 +275,14 @@ export class TXE implements TypedOracle {
264
275
  );
265
276
  }
266
277
 
267
- async addNullifiersFromPrivate(contractAddress: AztecAddress, nullifiers: Fr[]) {
278
+ async checkNullifiersNotInTree(contractAddress: AztecAddress, nullifiers: Fr[]) {
268
279
  const siloedNullifiers = nullifiers.map(nullifier => siloNullifier(contractAddress, nullifier));
269
280
  const db = await this.trees.getLatest();
270
281
  const nullifierIndexesInTree = await db.findLeafIndices(
271
282
  MerkleTreeId.NULLIFIER_TREE,
272
283
  siloedNullifiers.map(n => n.toBuffer()),
273
284
  );
274
- const notInTree = nullifierIndexesInTree.every(index => index === undefined);
275
- const notInCache = siloedNullifiers.every(n => !this.siloedNullifiersFromPrivate.has(n.toString()));
276
- if (notInTree && notInCache) {
277
- siloedNullifiers.forEach(n => this.siloedNullifiersFromPrivate.add(n.toString()));
278
- } else {
285
+ if (nullifierIndexesInTree.some(index => index !== undefined)) {
279
286
  throw new Error(`Rejecting tx for emitting duplicate nullifiers`);
280
287
  }
281
288
  }
@@ -364,16 +371,16 @@ export class TXE implements TypedOracle {
364
371
  return Fr.random();
365
372
  }
366
373
 
367
- packArgumentsArray(args: Fr[]) {
368
- return Promise.resolve(this.packedValuesCache.pack(args));
374
+ storeArrayInExecutionCache(values: Fr[]) {
375
+ return Promise.resolve(this.executionCache.store(values));
369
376
  }
370
377
 
371
- packReturns(returns: Fr[]) {
372
- return Promise.resolve(this.packedValuesCache.pack(returns));
378
+ storeInExecutionCache(values: Fr[]) {
379
+ return Promise.resolve(this.executionCache.store(values));
373
380
  }
374
381
 
375
- unpackReturns(returnsHash: Fr) {
376
- return Promise.resolve(this.packedValuesCache.unpack(returnsHash));
382
+ loadFromExecutionCache(returnsHash: Fr) {
383
+ return Promise.resolve(this.executionCache.getPreimage(returnsHash));
377
384
  }
378
385
 
379
386
  getKeyValidationRequest(pkMHash: Fr): Promise<KeyValidationRequest> {
@@ -554,12 +561,18 @@ export class TXE implements TypedOracle {
554
561
  }
555
562
 
556
563
  async notifyNullifiedNote(innerNullifier: Fr, noteHash: Fr, counter: number) {
557
- await this.addNullifiersFromPrivate(this.contractAddress, [innerNullifier]);
564
+ await this.checkNullifiersNotInTree(this.contractAddress, [innerNullifier]);
558
565
  this.noteCache.nullifyNote(this.contractAddress, innerNullifier, noteHash);
559
566
  this.sideEffectCounter = counter + 1;
560
567
  return Promise.resolve();
561
568
  }
562
569
 
570
+ async notifyCreatedNullifier(innerNullifier: Fr): Promise<void> {
571
+ await this.checkNullifiersNotInTree(this.contractAddress, [innerNullifier]);
572
+ this.noteCache.nullifierCreated(this.contractAddress, innerNullifier);
573
+ return Promise.resolve();
574
+ }
575
+
563
576
  async checkNullifierExists(innerNullifier: Fr): Promise<boolean> {
564
577
  const nullifier = siloNullifier(this.contractAddress, innerNullifier!);
565
578
  const db = await this.trees.getLatest();
@@ -621,6 +634,7 @@ export class TXE implements TypedOracle {
621
634
 
622
635
  async commitState() {
623
636
  const blockNumber = await this.getBlockNumber();
637
+ const { usedTxRequestHashForNonces } = this.noteCache.finish();
624
638
  if (this.committedBlocks.has(blockNumber)) {
625
639
  throw new Error('Already committed state');
626
640
  } else {
@@ -629,30 +643,25 @@ export class TXE implements TypedOracle {
629
643
 
630
644
  const txEffect = TxEffect.empty();
631
645
 
646
+ const nonceGenerator = usedTxRequestHashForNonces ? this.getTxRequestHash() : this.noteCache.getAllNullifiers()[0];
647
+
632
648
  let i = 0;
633
649
  txEffect.noteHashes = [
634
650
  ...this.noteCache
635
651
  .getAllNotes()
636
652
  .map(pendingNote =>
637
653
  computeUniqueNoteHash(
638
- computeNoteHashNonce(new Fr(this.blockNumber + 6969), i++),
654
+ computeNoteHashNonce(nonceGenerator, i++),
639
655
  siloNoteHash(pendingNote.note.contractAddress, pendingNote.noteHashForConsumption),
640
656
  ),
641
657
  ),
642
658
  ...this.uniqueNoteHashesFromPublic,
643
659
  ];
644
- txEffect.nullifiers = [
645
- new Fr(blockNumber + 6969),
646
- ...Array.from(this.siloedNullifiersFromPrivate).map(n => Fr.fromString(n)),
647
- ];
648
-
649
- // Using block number itself, (without adding 6969) gets killed at 1 as it says the slot is already used,
650
- // it seems like we commit a 1 there to the trees before ? To see what I mean, uncomment these lines below
651
- // let index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.ONE.toBuffer());
652
- // console.log('INDEX OF ONE', index);
653
- // index = await (await this.trees.getLatest()).findLeafIndex(MerkleTreeId.NULLIFIER_TREE, Fr.random().toBuffer());
654
- // console.log('INDEX OF RANDOM', index);
655
660
 
661
+ txEffect.nullifiers = this.noteCache.getAllNullifiers();
662
+ if (usedTxRequestHashForNonces) {
663
+ txEffect.nullifiers.unshift(this.getTxRequestHash());
664
+ }
656
665
  this.node.setTxEffect(blockNumber, new TxHash(new Fr(blockNumber)), txEffect);
657
666
  this.node.setNullifiersIndexesWithBlock(blockNumber, txEffect.nullifiers);
658
667
  this.node.addNoteLogsByTags(this.blockNumber, this.privateLogs);
@@ -663,10 +672,14 @@ export class TXE implements TypedOracle {
663
672
 
664
673
  this.privateLogs = [];
665
674
  this.publicLogs = [];
666
- this.siloedNullifiersFromPrivate = new Set();
667
675
  this.uniqueNoteHashesFromPublic = [];
668
676
  this.siloedNullifiersFromPublic = [];
669
- this.noteCache = new ExecutionNoteCache(new Fr(1));
677
+ this.noteCache = new ExecutionNoteCache(this.getTxRequestHash());
678
+ }
679
+
680
+ getTxRequestHash() {
681
+ // Using block number itself is invalid since indexed trees come prefilled with the first slots.
682
+ return new Fr(this.blockNumber + 6969);
670
683
  }
671
684
 
672
685
  emitContractClassLog(_log: UnencryptedL2Log, _counter: number): Fr {
@@ -701,21 +714,23 @@ export class TXE implements TypedOracle {
701
714
  const initialWitness = await this.getInitialWitness(artifact, argsHash, sideEffectCounter, isStaticCall);
702
715
  const acvmCallback = new Oracle(this);
703
716
  const timer = new Timer();
704
- const acirExecutionResult = await acvm(acir, initialWitness, acvmCallback).catch((err: Error) => {
705
- err.message = resolveAssertionMessageFromError(err, artifact);
706
-
707
- const execError = new ExecutionError(
708
- err.message,
709
- {
710
- contractAddress: targetContractAddress,
711
- functionSelector,
712
- },
713
- extractCallStack(err, artifact.debug),
714
- { cause: err },
715
- );
716
- this.logger.debug(`Error executing private function ${targetContractAddress}:${functionSelector}`);
717
- throw createSimulationError(execError);
718
- });
717
+ const acirExecutionResult = await this.simulationProvider
718
+ .executeUserCircuit(acir, initialWitness, acvmCallback)
719
+ .catch((err: Error) => {
720
+ err.message = resolveAssertionMessageFromError(err, artifact);
721
+
722
+ const execError = new ExecutionError(
723
+ err.message,
724
+ {
725
+ contractAddress: targetContractAddress,
726
+ functionSelector,
727
+ },
728
+ extractCallStack(err, artifact.debug),
729
+ { cause: err },
730
+ );
731
+ this.logger.debug(`Error executing private function ${targetContractAddress}:${functionSelector}`);
732
+ throw createSimulationError(execError);
733
+ });
719
734
  const duration = timer.ms();
720
735
  const publicInputs = extractPrivateCircuitPublicInputs(artifact, acirExecutionResult.partialWitness);
721
736
 
@@ -738,24 +753,6 @@ export class TXE implements TypedOracle {
738
753
  publicInputs.privateLogs.filter(privateLog => !privateLog.isEmpty()).map(privateLog => privateLog.log),
739
754
  );
740
755
 
741
- const executionNullifiers = publicInputs.nullifiers
742
- .filter(nullifier => !nullifier.isEmpty())
743
- .map(nullifier => nullifier.value);
744
- // We inject nullifiers into siloedNullifiersFromPrivate from notifyNullifiedNote,
745
- // so top level calls to destroyNote work as expected. As such, we are certain
746
- // that we would insert duplicates if we just took the nullifiers from the public inputs and
747
- // blindly inserted them into siloedNullifiersFromPrivate. To avoid this, we extract the first
748
- // (and only the first!) duplicated nullifier from the public inputs, so we can just push
749
- // the ones that were not created by deleting a note
750
- const firstDuplicateIndexes = executionNullifiers
751
- .map((nullifier, index) => {
752
- const siloedNullifier = siloNullifier(targetContractAddress, nullifier);
753
- return this.siloedNullifiersFromPrivate.has(siloedNullifier.toString()) ? index : -1;
754
- })
755
- .filter(index => index !== -1);
756
- const nonNoteNullifiers = executionNullifiers.filter((_, index) => !firstDuplicateIndexes.includes(index));
757
- await this.addNullifiersFromPrivate(targetContractAddress, nonNoteNullifiers);
758
-
759
756
  this.setContractAddress(currentContractAddress);
760
757
  this.setMsgSender(currentMessageSender);
761
758
  this.setFunctionSelector(currentFunctionSelector);
@@ -766,7 +763,7 @@ export class TXE implements TypedOracle {
766
763
  async getInitialWitness(abi: FunctionAbi, argsHash: Fr, sideEffectCounter: number, isStaticCall: boolean) {
767
764
  const argumentsSize = countArgumentsSize(abi);
768
765
 
769
- const args = this.packedValuesCache.unpack(argsHash);
766
+ const args = this.executionCache.getPreimage(argsHash);
770
767
 
771
768
  if (args.length !== argumentsSize) {
772
769
  throw new Error('Invalid arguments size');
@@ -840,7 +837,12 @@ export class TXE implements TypedOracle {
840
837
  // When setting up a teardown call, we tell it that
841
838
  // private execution used Gas(1, 1) so it can compute a tx fee.
842
839
  const gasUsedByPrivate = isTeardown ? new Gas(1, 1) : Gas.empty();
843
- const tx = createTxForPublicCall(executionRequest, gasUsedByPrivate, isTeardown);
840
+ const tx = createTxForPublicCalls(
841
+ /*setupExecutionRequests=*/ [],
842
+ /*appExecutionRequests=*/ isTeardown ? [] : [executionRequest],
843
+ /*teardownExecutionRequests=*/ isTeardown ? executionRequest : undefined,
844
+ gasUsedByPrivate,
845
+ );
844
846
 
845
847
  const result = await simulator.simulate(tx);
846
848
 
@@ -872,8 +874,8 @@ export class TXE implements TypedOracle {
872
874
  isStaticCall,
873
875
  );
874
876
 
875
- const args = [this.functionSelector.toField(), ...this.packedValuesCache.unpack(argsHash)];
876
- const newArgsHash = this.packedValuesCache.pack(args);
877
+ const args = [this.functionSelector.toField(), ...this.executionCache.getPreimage(argsHash)];
878
+ const newArgsHash = this.executionCache.store(args);
877
879
 
878
880
  const executionResult = await this.executePublicFunction(args, callContext, isTeardown);
879
881
 
@@ -18,7 +18,8 @@ import { openTmpStore } from '@aztec/kv-store/lmdb';
18
18
  import { protocolContractNames } from '@aztec/protocol-contracts';
19
19
  import { getCanonicalProtocolContract } from '@aztec/protocol-contracts/bundle';
20
20
  import { enrichPublicSimulationError } from '@aztec/pxe';
21
- import { ExecutionNoteCache, PackedValuesCache, type TypedOracle } from '@aztec/simulator';
21
+ import { type TypedOracle } from '@aztec/simulator/client';
22
+ import { HashedValuesCache } from '@aztec/simulator/server';
22
23
  import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
23
24
  import { MerkleTrees } from '@aztec/world-state';
24
25
 
@@ -42,9 +43,7 @@ export class TXEService {
42
43
  static async init(logger: Logger) {
43
44
  const store = openTmpStore(true);
44
45
  const trees = await MerkleTrees.new(store, new NoopTelemetryClient(), logger);
45
- const packedValuesCache = new PackedValuesCache();
46
- const txHash = new Fr(1); // The txHash is used for computing the revertible nullifiers for non-revertible note hashes. It can be any value for testing.
47
- const noteCache = new ExecutionNoteCache(txHash);
46
+ const executionCache = new HashedValuesCache();
48
47
  const keyStore = new KeyStore(store);
49
48
  const txeDatabase = new TXEDatabase(store);
50
49
  // Register protocol contracts.
@@ -54,7 +53,7 @@ export class TXEService {
54
53
  await txeDatabase.addContractInstance(instance);
55
54
  }
56
55
  logger.debug(`TXE service initialized`);
57
- const txe = new TXE(logger, trees, packedValuesCache, noteCache, keyStore, txeDatabase);
56
+ const txe = new TXE(logger, trees, executionCache, keyStore, txeDatabase);
58
57
  const service = new TXEService(logger, txe);
59
58
  await service.advanceBlocksBy(toSingle(new Fr(1n)));
60
59
  return service;
@@ -272,25 +271,20 @@ export class TXEService {
272
271
  return toForeignCallResult([toSingle(new Fr(blockNumber))]);
273
272
  }
274
273
 
275
- async packArgumentsArray(args: ForeignCallArray) {
276
- const packed = await this.typedOracle.packArgumentsArray(fromArray(args));
277
- return toForeignCallResult([toSingle(packed)]);
278
- }
279
-
280
- async packArguments(_length: ForeignCallSingle, values: ForeignCallArray) {
281
- const packed = await this.typedOracle.packArgumentsArray(fromArray(values));
282
- return toForeignCallResult([toSingle(packed)]);
274
+ async storeArrayInExecutionCache(args: ForeignCallArray) {
275
+ const hash = await this.typedOracle.storeArrayInExecutionCache(fromArray(args));
276
+ return toForeignCallResult([toSingle(hash)]);
283
277
  }
284
278
 
285
279
  // Since the argument is a slice, noir automatically adds a length field to oracle call.
286
- async packReturns(_length: ForeignCallSingle, values: ForeignCallArray) {
287
- const packed = await this.typedOracle.packReturns(fromArray(values));
288
- return toForeignCallResult([toSingle(packed)]);
280
+ async storeInExecutionCache(_length: ForeignCallSingle, values: ForeignCallArray) {
281
+ const returnsHash = await this.typedOracle.storeInExecutionCache(fromArray(values));
282
+ return toForeignCallResult([toSingle(returnsHash)]);
289
283
  }
290
284
 
291
- async unpackReturns(returnsHash: ForeignCallSingle) {
292
- const unpacked = await this.typedOracle.unpackReturns(fromSingle(returnsHash));
293
- return toForeignCallResult([toArray(unpacked)]);
285
+ async loadFromExecutionCache(hash: ForeignCallSingle) {
286
+ const returns = await this.typedOracle.loadFromExecutionCache(fromSingle(hash));
287
+ return toForeignCallResult([toArray(returns)]);
294
288
  }
295
289
 
296
290
  // Since the argument is a slice, noir automatically adds a length field to oracle call.
@@ -425,6 +419,11 @@ export class TXEService {
425
419
  return toForeignCallResult([toSingle(new Fr(0))]);
426
420
  }
427
421
 
422
+ async notifyCreatedNullifier(innerNullifier: ForeignCallSingle) {
423
+ await this.typedOracle.notifyCreatedNullifier(fromSingle(innerNullifier));
424
+ return toForeignCallResult([toSingle(new Fr(0))]);
425
+ }
426
+
428
427
  async checkNullifierExists(innerNullifier: ForeignCallSingle) {
429
428
  const exists = await this.typedOracle.checkNullifierExists(fromSingle(innerNullifier));
430
429
  return toForeignCallResult([toSingle(new Fr(exists))]);
@@ -7,7 +7,7 @@ import {
7
7
  type PublicDataTreeLeafPreimage,
8
8
  } from '@aztec/circuits.js';
9
9
  import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash';
10
- import { WorldStateDB } from '@aztec/simulator';
10
+ import { WorldStateDB } from '@aztec/simulator/server';
11
11
 
12
12
  export class TXEWorldStateDB extends WorldStateDB {
13
13
  constructor(private merkleDb: MerkleTreeWriteOperations, dataSource: ContractDataSource) {