@aztec/simulator 0.64.0 → 0.65.1

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 (107) hide show
  1. package/dest/avm/avm_memory_types.d.ts +3 -1
  2. package/dest/avm/avm_memory_types.d.ts.map +1 -1
  3. package/dest/avm/avm_memory_types.js +22 -24
  4. package/dest/avm/avm_simulator.d.ts +5 -1
  5. package/dest/avm/avm_simulator.d.ts.map +1 -1
  6. package/dest/avm/avm_simulator.js +16 -9
  7. package/dest/avm/avm_tree.d.ts +56 -7
  8. package/dest/avm/avm_tree.d.ts.map +1 -1
  9. package/dest/avm/avm_tree.js +155 -82
  10. package/dest/avm/errors.d.ts +19 -0
  11. package/dest/avm/errors.d.ts.map +1 -1
  12. package/dest/avm/errors.js +29 -1
  13. package/dest/avm/journal/journal.d.ts +8 -7
  14. package/dest/avm/journal/journal.d.ts.map +1 -1
  15. package/dest/avm/journal/journal.js +47 -29
  16. package/dest/avm/journal/nullifiers.d.ts +11 -58
  17. package/dest/avm/journal/nullifiers.d.ts.map +1 -1
  18. package/dest/avm/journal/nullifiers.js +27 -107
  19. package/dest/avm/opcodes/contract.d.ts +2 -2
  20. package/dest/avm/opcodes/contract.d.ts.map +1 -1
  21. package/dest/avm/opcodes/contract.js +4 -4
  22. package/dest/avm/opcodes/control_flow.d.ts +2 -2
  23. package/dest/avm/opcodes/control_flow.d.ts.map +1 -1
  24. package/dest/avm/opcodes/control_flow.js +4 -4
  25. package/dest/avm/opcodes/environment_getters.d.ts +2 -2
  26. package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
  27. package/dest/avm/opcodes/environment_getters.js +4 -4
  28. package/dest/avm/opcodes/instruction.d.ts +1 -1
  29. package/dest/avm/opcodes/instruction.d.ts.map +1 -1
  30. package/dest/avm/opcodes/instruction.js +1 -1
  31. package/dest/avm/opcodes/memory.d.ts +4 -4
  32. package/dest/avm/opcodes/memory.d.ts.map +1 -1
  33. package/dest/avm/opcodes/memory.js +17 -13
  34. package/dest/avm/opcodes/misc.d.ts +2 -2
  35. package/dest/avm/opcodes/misc.d.ts.map +1 -1
  36. package/dest/avm/opcodes/misc.js +4 -4
  37. package/dest/avm/serialization/buffer_cursor.d.ts +2 -0
  38. package/dest/avm/serialization/buffer_cursor.d.ts.map +1 -1
  39. package/dest/avm/serialization/buffer_cursor.js +8 -3
  40. package/dest/avm/serialization/bytecode_serialization.d.ts +1 -0
  41. package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
  42. package/dest/avm/serialization/bytecode_serialization.js +27 -13
  43. package/dest/avm/serialization/instruction_serialization.d.ts +1 -0
  44. package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
  45. package/dest/avm/serialization/instruction_serialization.js +9 -6
  46. package/dest/avm/test_utils.d.ts.map +1 -1
  47. package/dest/avm/test_utils.js +3 -2
  48. package/dest/common/errors.d.ts.map +1 -1
  49. package/dest/common/errors.js +3 -2
  50. package/dest/public/dual_side_effect_trace.d.ts +2 -2
  51. package/dest/public/dual_side_effect_trace.d.ts.map +1 -1
  52. package/dest/public/dual_side_effect_trace.js +7 -7
  53. package/dest/public/enqueued_call_side_effect_trace.d.ts +8 -23
  54. package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
  55. package/dest/public/enqueued_call_side_effect_trace.js +31 -92
  56. package/dest/public/executor_metrics.d.ts +4 -2
  57. package/dest/public/executor_metrics.d.ts.map +1 -1
  58. package/dest/public/executor_metrics.js +20 -3
  59. package/dest/public/fixtures/index.d.ts.map +1 -1
  60. package/dest/public/fixtures/index.js +66 -35
  61. package/dest/public/public_db_sources.d.ts +3 -1
  62. package/dest/public/public_db_sources.d.ts.map +1 -1
  63. package/dest/public/public_db_sources.js +26 -11
  64. package/dest/public/public_processor.d.ts.map +1 -1
  65. package/dest/public/public_processor.js +12 -5
  66. package/dest/public/public_tx_context.d.ts +5 -6
  67. package/dest/public/public_tx_context.d.ts.map +1 -1
  68. package/dest/public/public_tx_context.js +19 -17
  69. package/dest/public/public_tx_simulator.d.ts +13 -2
  70. package/dest/public/public_tx_simulator.d.ts.map +1 -1
  71. package/dest/public/public_tx_simulator.js +263 -217
  72. package/dest/public/side_effect_trace.d.ts +2 -2
  73. package/dest/public/side_effect_trace.d.ts.map +1 -1
  74. package/dest/public/side_effect_trace.js +8 -6
  75. package/dest/public/side_effect_trace_interface.d.ts +2 -2
  76. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  77. package/dest/public/transitional_adapters.d.ts.map +1 -1
  78. package/dest/public/transitional_adapters.js +2 -6
  79. package/package.json +9 -9
  80. package/src/avm/avm_memory_types.ts +26 -24
  81. package/src/avm/avm_simulator.ts +20 -13
  82. package/src/avm/avm_tree.ts +196 -93
  83. package/src/avm/errors.ts +31 -0
  84. package/src/avm/journal/journal.ts +61 -47
  85. package/src/avm/journal/nullifiers.ts +29 -121
  86. package/src/avm/opcodes/contract.ts +2 -2
  87. package/src/avm/opcodes/control_flow.ts +2 -2
  88. package/src/avm/opcodes/environment_getters.ts +2 -2
  89. package/src/avm/opcodes/instruction.ts +1 -1
  90. package/src/avm/opcodes/memory.ts +15 -10
  91. package/src/avm/opcodes/misc.ts +2 -2
  92. package/src/avm/serialization/buffer_cursor.ts +9 -3
  93. package/src/avm/serialization/bytecode_serialization.ts +29 -13
  94. package/src/avm/serialization/instruction_serialization.ts +12 -6
  95. package/src/avm/test_utils.ts +9 -1
  96. package/src/common/errors.ts +2 -1
  97. package/src/public/dual_side_effect_trace.ts +6 -30
  98. package/src/public/enqueued_call_side_effect_trace.ts +35 -154
  99. package/src/public/executor_metrics.ts +23 -1
  100. package/src/public/fixtures/index.ts +97 -43
  101. package/src/public/public_db_sources.ts +29 -15
  102. package/src/public/public_processor.ts +11 -4
  103. package/src/public/public_tx_context.ts +21 -23
  104. package/src/public/public_tx_simulator.ts +45 -8
  105. package/src/public/side_effect_trace.ts +7 -9
  106. package/src/public/side_effect_trace_interface.ts +2 -4
  107. package/src/public/transitional_adapters.ts +0 -11
@@ -4,6 +4,7 @@ import { poseidon2Hash } from '@aztec/foundation/crypto';
4
4
  import { Fr } from '@aztec/foundation/fields';
5
5
  import { type IndexedTreeLeafPreimage, type TreeLeafPreimage } from '@aztec/foundation/trees';
6
6
 
7
+ import { strict as assert } from 'assert';
7
8
  import cloneDeep from 'lodash.clonedeep';
8
9
 
9
10
  /****************************************************/
@@ -147,11 +148,18 @@ export class AvmEphemeralForest {
147
148
  // This only works for the public data tree
148
149
  const treeId = MerkleTreeId.PUBLIC_DATA_TREE;
149
150
  const tree = this.treeMap.get(treeId)!;
150
- const { preimage, index, update }: PreimageWitness<PublicDataTreeLeafPreimage> = await this.getLeafOrLowLeafInfo(
151
- treeId,
152
- slot,
153
- );
151
+ const [leafOrLowLeafInfo, pathAbsentInEphemeralTree] = await this._getLeafOrLowLeafInfo<
152
+ typeof treeId,
153
+ PublicDataTreeLeafPreimage
154
+ >(treeId, slot);
155
+ const { preimage, index, update } = leafOrLowLeafInfo;
154
156
  const siblingPath = await this.getSiblingPath(treeId, index);
157
+
158
+ if (pathAbsentInEphemeralTree) {
159
+ // Since we have never seen this before - we should insert it into our tree as it is about to be updated.
160
+ this.treeMap.get(treeId)!.insertSiblingPath(index, siblingPath);
161
+ }
162
+
155
163
  if (update) {
156
164
  const updatedPreimage = cloneDeep(preimage);
157
165
  const existingPublicDataSiblingPath = siblingPath;
@@ -222,7 +230,7 @@ export class AvmEphemeralForest {
222
230
  if (foundIndex === -1) {
223
231
  // New element, we splice it into the correct location
224
232
  const spliceIndex =
225
- this.searchForKey(
233
+ indexOrNextLowestInArray(
226
234
  keys[i],
227
235
  existingKeyVector.map(x => x[0]),
228
236
  ) + 1;
@@ -242,15 +250,20 @@ export class AvmEphemeralForest {
242
250
  async appendNullifier(nullifier: Fr): Promise<IndexedInsertionResult<NullifierLeafPreimage>> {
243
251
  const treeId = MerkleTreeId.NULLIFIER_TREE;
244
252
  const tree = this.treeMap.get(treeId)!;
245
- const { preimage, index, update }: PreimageWitness<NullifierLeafPreimage> = await this.getLeafOrLowLeafInfo(
246
- treeId,
247
- nullifier,
248
- );
253
+ const [leafOrLowLeafInfo, pathAbsentInEphemeralTree] = await this._getLeafOrLowLeafInfo<
254
+ typeof treeId,
255
+ NullifierLeafPreimage
256
+ >(treeId, nullifier);
257
+ const { preimage, index, update } = leafOrLowLeafInfo;
249
258
  const siblingPath = await this.getSiblingPath(treeId, index);
250
259
 
251
- if (update) {
252
- throw new Error('Not allowed to update a nullifier');
260
+ if (pathAbsentInEphemeralTree) {
261
+ // Since we have never seen this before - we should insert it into our tree as it is about to be updated.
262
+ this.treeMap.get(treeId)!.insertSiblingPath(index, siblingPath);
253
263
  }
264
+
265
+ assert(!update, 'Nullifier already exists in the tree. Cannot update a nullifier!');
266
+
254
267
  // We are writing a new entry
255
268
  const insertionIndex = tree.leafCount;
256
269
  const updatedLowNullifier = cloneDeep(preimage);
@@ -311,17 +324,17 @@ export class AvmEphemeralForest {
311
324
  }
312
325
 
313
326
  /**
314
- * This is wrapper around treeId to get values in the indexedUpdates map
327
+ * This is wrapper around treeId to get values in the indexedUpdates map.
328
+ * Should only be called if we know the value exists.
315
329
  */
316
- private getIndexedUpdates<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(treeId: ID, index: bigint): T {
330
+ private getIndexedUpdate<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(treeId: ID, index: bigint): T {
317
331
  const updates = this.indexedUpdates.get(treeId);
318
- if (updates === undefined) {
319
- throw new Error('No updates found');
320
- }
332
+ assert(updates !== undefined, `No updates exist in the ephemeral ${MerkleTreeId[treeId]} tree.`);
321
333
  const preimage = updates.get(index);
322
- if (preimage === undefined) {
323
- throw new Error('No updates found');
324
- }
334
+ assert(
335
+ updates !== undefined,
336
+ `No update exists in the ephemeral ${MerkleTreeId[treeId]} tree for leafIndex ${index}.`,
337
+ );
325
338
  return preimage as T;
326
339
  }
327
340
 
@@ -336,93 +349,165 @@ export class AvmEphemeralForest {
336
349
  return updates.has(index);
337
350
  }
338
351
 
339
- private searchForKey(key: Fr, arr: Fr[]): number {
340
- // We are looking for the index of the largest element in the array that is less than the key
341
- let start = 0;
342
- let end = arr.length;
343
- // Note that the easiest way is to increment the search key by 1 and then do a binary search
344
- const searchKey = key.add(Fr.ONE);
345
- while (start < end) {
346
- const mid = Math.floor((start + end) / 2);
347
- if (arr[mid].cmp(searchKey) < 0) {
348
- // The key + 1 is greater than the arr element, so we can continue searching the top half
349
- start = mid + 1;
350
- } else {
351
- // The key + 1 is LT or EQ the arr element, so we can continue searching the bottom half
352
- end = mid;
353
- }
354
- }
355
- // We either found key + 1 or start is now at the index of the largest element that we would have inserted key + 1
356
- // Therefore start - 1 is the index of the element just below - note it can be -1 if the first element in the array is
357
- // greater than the key
358
- return start - 1;
359
- }
360
-
361
352
  /**
362
- * This gets the low leaf preimage and the index of the low leaf in the indexed tree given a value (slot or nullifier value)
363
- * If the value is not found in the tree, it does an external lookup to the merkleDB
353
+ * Get the leaf or low leaf preimage and its index in the indexed tree given a key (slot or nullifier value).
354
+ * If the key is not found in the tree, it does an external lookup to the underlying merkle DB.
364
355
  * @param treeId - The tree we are looking up in
365
- * @param key - The key for which we are look up the low leaf for.
356
+ * @param key - The key for which we are look up the leaf or low leaf for.
366
357
  * @param T - The type of the preimage (PublicData or Nullifier)
367
- * @returns The low leaf preimage and the index of the low leaf in the indexed tree
358
+ * @returns The leaf or low leaf info (preimage & leaf index).
368
359
  */
369
360
  async getLeafOrLowLeafInfo<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
370
361
  treeId: ID,
371
362
  key: Fr,
372
363
  ): Promise<PreimageWitness<T>> {
364
+ const [leafOrLowLeafInfo, _] = await this._getLeafOrLowLeafInfo<ID, T>(treeId, key);
365
+ return leafOrLowLeafInfo;
366
+ }
367
+
368
+ /**
369
+ * Internal helper to get the leaf or low leaf preimage and its index in the indexed tree given a key (slot or nullifier value).
370
+ * If the key is not found in the tree, it does an external lookup to the underlying merkle DB.
371
+ * Indicates whethe the sibling path is absent in the ephemeral tree.
372
+ * @param treeId - The tree we are looking up in
373
+ * @param key - The key for which we are look up the leaf or low leaf for.
374
+ * @param T - The type of the preimage (PublicData or Nullifier)
375
+ * @returns [
376
+ * preimageWitness - The leaf or low leaf info (preimage & leaf index),
377
+ * pathAbsentInEphemeralTree - whether its sibling path is absent in the ephemeral tree (useful during insertions)
378
+ * ]
379
+ */
380
+ async _getLeafOrLowLeafInfo<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
381
+ treeId: ID,
382
+ key: Fr,
383
+ ): Promise<[PreimageWitness<T>, /*pathAbsentInEphemeralTree=*/ boolean]> {
384
+ const bigIntKey = key.toBigInt();
385
+ // In this function, "min" refers to the leaf with the
386
+ // largest key <= the specified key in the indexedUpdates.
387
+ // In other words, the leaf with the "next lowest" key in indexedUpdates.
388
+
389
+ // First, search the indexed updates (no DB fallback) to find
390
+ // the leafIndex of the leaf with the largest key <= the specified key.
391
+ const minIndexedLeafIndex = this._getLeafIndexOrNextLowestInIndexedUpdates(treeId, key);
392
+ if (minIndexedLeafIndex === -1n) {
393
+ // No leaf is present in the indexed updates that is <= the key,
394
+ // so retrieve the leaf or low leaf from the underlying DB.
395
+ const leafOrLowLeafPreimage: PreimageWitness<T> = await this._getLeafOrLowLeafWitnessInExternalDb(
396
+ treeId,
397
+ bigIntKey,
398
+ );
399
+ return [leafOrLowLeafPreimage, /*pathAbsentInEphemeralTree=*/ true];
400
+ } else {
401
+ // A leaf was found in the indexed updates that is <= the key
402
+ const minPreimage: T = this.getIndexedUpdate(treeId, minIndexedLeafIndex);
403
+ if (minPreimage.getKey() === bigIntKey) {
404
+ // the index found is an exact match, no need to search further
405
+ const leafInfo = { preimage: minPreimage, index: minIndexedLeafIndex, update: true };
406
+ return [leafInfo, /*pathAbsentInEphemeralTree=*/ false];
407
+ } else {
408
+ // We are starting with the leaf with largest key <= the specified key
409
+ // Starting at that "min leaf", search for specified key in both the indexed updates
410
+ // and the underlying DB. If not found, return its low leaf.
411
+ const [leafOrLowLeafInfo, pathAbsentInEphemeralTree] = await this._searchForLeafOrLowLeaf<ID, T>(
412
+ treeId,
413
+ bigIntKey,
414
+ minPreimage,
415
+ minIndexedLeafIndex,
416
+ );
417
+ // We did not find it - this is unexpected... the leaf OR low leaf should always be present
418
+ assert(leafOrLowLeafInfo !== undefined, 'Could not find leaf or low leaf. This should not happen!');
419
+ return [leafOrLowLeafInfo, pathAbsentInEphemeralTree];
420
+ }
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Helper to search for the leaf with the specified key in the indexedUpdates
426
+ * and return its leafIndex.
427
+ * If not present, return the leafIndex of the largest leaf <= the specified key
428
+ * (the leafIndex of the next lowest key).
429
+ *
430
+ * If no entry exists in indexedUpdates <= the specified key, return -1.
431
+ * @returns - The leafIndex of the leaf with the largest key <= the specified key.
432
+ */
433
+ private _getLeafIndexOrNextLowestInIndexedUpdates<ID extends IndexedTreeId>(treeId: ID, key: Fr): bigint {
373
434
  const keyOrderedVector = this.indexedSortedKeys.get(treeId)!;
374
435
 
375
- const vectorIndex = this.searchForKey(
436
+ const indexInVector = indexOrNextLowestInArray(
376
437
  key,
377
438
  keyOrderedVector.map(x => x[0]),
378
439
  );
379
- // We have a match in our local updates
380
- let minPreimage = undefined;
381
-
382
- if (vectorIndex !== -1) {
383
- const [_, leafIndex] = keyOrderedVector[vectorIndex];
384
- minPreimage = {
385
- preimage: this.getIndexedUpdates(treeId, leafIndex) as T,
386
- index: leafIndex,
387
- };
388
- }
389
- // This can probably be done better, we want to say if the minInfo is undefined (because this is our first operation) we do the external lookup
390
- const start = minPreimage?.preimage;
391
- const bigIntKey = key.toBigInt();
392
440
 
393
- // If we don't have a first element or if that first element is already greater than the target key, we need to do an external lookup
394
- // The low public data witness is in the previous tree
395
- if (start === undefined || start.getKey() > key.toBigInt()) {
396
- // This function returns the leaf index to the actual element if it exists or the leaf index to the low leaf otherwise
397
- const { index, alreadyPresent } = (await this.treeDb.getPreviousValueIndex(treeId, bigIntKey))!;
398
- const preimage = await this.treeDb.getLeafPreimage(treeId, index);
399
-
400
- // Since we have never seen this before - we should insert it into our tree, as we know we will modify this leaf node
401
- const siblingPath = await this.getSiblingPath(treeId, index);
402
- // const siblingPath = (await this.treeDb.getSiblingPath(treeId, index)).toFields();
441
+ if (indexInVector !== -1) {
442
+ const [_, leafIndex] = keyOrderedVector[indexInVector];
443
+ return leafIndex;
444
+ } else {
445
+ // no leaf <= the specified key was found
446
+ return -1n;
447
+ }
448
+ }
403
449
 
404
- // Is it enough to just insert the sibling path without inserting the leaf? - now probably since we will update this low nullifier index in append
405
- this.treeMap.get(treeId)!.insertSiblingPath(index, siblingPath);
450
+ /**
451
+ * Query the external DB to get leaf if present, low leaf if absent
452
+ */
453
+ private async _getLeafOrLowLeafWitnessInExternalDb<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
454
+ treeId: ID,
455
+ key: bigint,
456
+ ): Promise<PreimageWitness<T>> {
457
+ // "key" is siloed slot (leafSlot) or siloed nullifier
458
+ const previousValueIndex = await this.treeDb.getPreviousValueIndex(treeId, key);
459
+ assert(
460
+ previousValueIndex !== undefined,
461
+ `${MerkleTreeId[treeId]} low leaf index should always be found (even if target leaf does not exist)`,
462
+ );
463
+ const { index: leafIndex, alreadyPresent } = previousValueIndex;
406
464
 
407
- const lowPublicDataPreimage = preimage as T;
465
+ const leafPreimage = await this.treeDb.getLeafPreimage(treeId, leafIndex);
466
+ assert(
467
+ leafPreimage !== undefined,
468
+ `${MerkleTreeId[treeId]} low leaf preimage should never be undefined (even if target leaf does not exist)`,
469
+ );
408
470
 
409
- return { preimage: lowPublicDataPreimage, index: index, update: alreadyPresent };
410
- }
471
+ return { preimage: leafPreimage as T, index: leafIndex, update: alreadyPresent };
472
+ }
411
473
 
412
- // We look for the low element by bouncing between our local indexedUpdates map or the external DB
413
- // The conditions we are looking for are:
414
- // (1) Exact Match: curr.nextKey == key (this is only valid for public data tree)
415
- // (2) Sandwich Match: curr.nextKey > key and curr.key < key
416
- // (3) Max Condition: curr.next_index == 0 and curr.key < key
417
- // Note the min condition does not need to be handled since indexed trees are prefilled with at least the 0 element
474
+ /**
475
+ * Search for the leaf for the specified key.
476
+ * Some leaf with key <= the specified key is expected to be present in the ephemeral tree's "indexed updates".
477
+ * While searching, this function bounces between our local indexedUpdates and the external DB.
478
+ *
479
+ * @param key - The key for which we are look up the leaf or low leaf for.
480
+ * @param minPreimage - The leaf with the largest key <= the specified key. Expected to be present in local indexedUpdates.
481
+ * @param minIndex - The index of the leaf with the largest key <= the specified key.
482
+ * @param T - The type of the preimage (PublicData or Nullifier)
483
+ * @returns [
484
+ * preimageWitness | undefined - The leaf or low leaf info (preimage & leaf index),
485
+ * pathAbsentInEphemeralTree - whether its sibling path is absent in the ephemeral tree (useful during insertions)
486
+ * ]
487
+ *
488
+ * @details We look for the low element by bouncing between our local indexedUpdates map or the external DB
489
+ * The conditions we are looking for are:
490
+ * (1) Exact Match: curr.nextKey == key (this is only valid for public data tree)
491
+ * (2) Sandwich Match: curr.nextKey > key and curr.key < key
492
+ * (3) Max Condition: curr.next_index == 0 and curr.key < key
493
+ * Note the min condition does not need to be handled since indexed trees are prefilled with at least the 0 element
494
+ */
495
+ private async _searchForLeafOrLowLeaf<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(
496
+ treeId: ID,
497
+ key: bigint,
498
+ minPreimage: T,
499
+ minIndex: bigint,
500
+ ): Promise<[PreimageWitness<T> | undefined, /*pathAbsentInEphemeralTree=*/ boolean]> {
418
501
  let found = false;
419
- let curr = minPreimage!.preimage as T;
502
+ let curr = minPreimage as T;
420
503
  let result: PreimageWitness<T> | undefined = undefined;
421
504
  // Temp to avoid infinite loops - the limit is the number of leaves we may have to read
422
505
  const LIMIT = 2n ** BigInt(getTreeHeight(treeId)) - 1n;
423
506
  let counter = 0n;
424
- let lowPublicDataIndex = minPreimage!.index;
507
+ let lowPublicDataIndex = minIndex;
508
+ let pathAbsentInEphemeralTree = false;
425
509
  while (!found && counter < LIMIT) {
510
+ const bigIntKey = key;
426
511
  if (curr.getKey() === bigIntKey) {
427
512
  // We found an exact match - therefore this is an update
428
513
  found = true;
@@ -436,30 +521,23 @@ export class AvmEphemeralForest {
436
521
  else {
437
522
  lowPublicDataIndex = curr.getNextIndex();
438
523
  if (this.hasLocalUpdates(treeId, lowPublicDataIndex)) {
439
- curr = this.getIndexedUpdates(treeId, lowPublicDataIndex)!;
524
+ curr = this.getIndexedUpdate(treeId, lowPublicDataIndex)!;
525
+ pathAbsentInEphemeralTree = false;
440
526
  } else {
441
527
  const preimage: IndexedTreeLeafPreimage = (await this.treeDb.getLeafPreimage(treeId, lowPublicDataIndex))!;
442
528
  curr = preimage as T;
529
+ pathAbsentInEphemeralTree = true;
443
530
  }
444
531
  }
445
532
  counter++;
446
533
  }
447
- // We did not find it - this is unexpected
448
- if (result === undefined) {
449
- throw new Error('No previous value found or ran out of iterations');
450
- }
451
- return result;
534
+ return [result, pathAbsentInEphemeralTree];
452
535
  }
453
536
 
454
537
  /**
455
538
  * This hashes the preimage to a field element
456
539
  */
457
540
  hashPreimage<T extends TreeLeafPreimage>(preimage: T): Fr {
458
- // Watch for this edge-case, we are hashing the key=0 leaf to 0.
459
- // This is for backward compatibility with the world state implementation
460
- if (preimage.getKey() === 0n) {
461
- return Fr.zero();
462
- }
463
541
  const input = preimage.toHashInputs().map(x => Fr.fromBuffer(x));
464
542
  return poseidon2Hash(input);
465
543
  }
@@ -807,3 +885,28 @@ export class EphemeralAvmTree {
807
885
  }
808
886
  }
809
887
  }
888
+
889
+ /**
890
+ * Return the index of the key in the array, or index-1 if they key is not found.
891
+ */
892
+ function indexOrNextLowestInArray(key: Fr, arr: Fr[]): number {
893
+ // We are looking for the index of the largest element in the array that is less than the key
894
+ let start = 0;
895
+ let end = arr.length;
896
+ // Note that the easiest way is to increment the search key by 1 and then do a binary search
897
+ const keyPlus1 = key.add(Fr.ONE);
898
+ while (start < end) {
899
+ const mid = Math.floor((start + end) / 2);
900
+ if (arr[mid].cmp(keyPlus1) < 0) {
901
+ // The key + 1 is greater than the midpoint, so we can continue searching the top half
902
+ start = mid + 1;
903
+ } else {
904
+ // The key + 1 is LT or EQ the arr element, so we can continue searching the bottom half
905
+ end = mid;
906
+ }
907
+ }
908
+ // We either found key + 1 or start is now at the index of the largest element that we would have inserted key + 1
909
+ // Therefore start - 1 is the index of the element just below - note it can be -1 if the first element in the array is
910
+ // greater than the key
911
+ return start - 1;
912
+ }
package/src/avm/errors.ts CHANGED
@@ -39,6 +39,37 @@ export class InvalidProgramCounterError extends AvmExecutionError {
39
39
  }
40
40
  }
41
41
 
42
+ /**
43
+ * Error is thrown when the program counter points to a byte
44
+ * of an invalid opcode.
45
+ */
46
+ export class InvalidOpcodeError extends AvmExecutionError {
47
+ constructor(str: string) {
48
+ super(str);
49
+ this.name = 'InvalidOpcodeError';
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Error is thrown during parsing.
55
+ */
56
+ export class AvmParsingError extends AvmExecutionError {
57
+ constructor(str: string) {
58
+ super(str);
59
+ this.name = 'AvmParsingError';
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Error is thrown when the tag has an invalid value.
65
+ */
66
+ export class InvalidTagValueError extends AvmExecutionError {
67
+ constructor(tagValue: number) {
68
+ super(`Tag value ${tagValue} is invalid.`);
69
+ this.name = 'InvalidTagValueError';
70
+ }
71
+ }
72
+
42
73
  /**
43
74
  * Error thrown during an instruction's execution (during its execute()).
44
75
  */
@@ -6,13 +6,13 @@ import {
6
6
  type PublicCallRequest,
7
7
  type PublicDataTreeLeafPreimage,
8
8
  SerializableContractInstance,
9
- computePublicBytecodeCommitment,
10
9
  } from '@aztec/circuits.js';
11
10
  import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash';
12
11
  import { Fr } from '@aztec/foundation/fields';
12
+ import { jsonStringify } from '@aztec/foundation/json-rpc';
13
13
  import { createDebugLogger } from '@aztec/foundation/log';
14
14
 
15
- import assert from 'assert';
15
+ import { strict as assert } from 'assert';
16
16
 
17
17
  import { getPublicFunctionDebugName } from '../../common/debug_fn_name.js';
18
18
  import { type WorldStateDB } from '../../public/public_db_sources.js';
@@ -50,7 +50,7 @@ export class AvmPersistableStateManager {
50
50
  private readonly nullifiers: NullifierManager = new NullifierManager(worldStateDB),
51
51
  private readonly doMerkleOperations: boolean = false,
52
52
  /** Ephmeral forest for merkle tree operations */
53
- public readonly merkleTrees: AvmEphemeralForest,
53
+ public merkleTrees: AvmEphemeralForest,
54
54
  ) {}
55
55
 
56
56
  /**
@@ -77,14 +77,18 @@ export class AvmPersistableStateManager {
77
77
  /**
78
78
  * Create a new state manager
79
79
  */
80
- public static async create(worldStateDB: WorldStateDB, trace: PublicSideEffectTraceInterface) {
80
+ public static async create(
81
+ worldStateDB: WorldStateDB,
82
+ trace: PublicSideEffectTraceInterface,
83
+ doMerkleOperations: boolean = false,
84
+ ) {
81
85
  const ephemeralForest = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface());
82
86
  return new AvmPersistableStateManager(
83
87
  worldStateDB,
84
88
  trace,
85
89
  /*publicStorage=*/ new PublicStorage(worldStateDB),
86
90
  /*nullifiers=*/ new NullifierManager(worldStateDB),
87
- /*doMerkleOperations=*/ true,
91
+ /*doMerkleOperations=*/ doMerkleOperations,
88
92
  ephemeralForest,
89
93
  );
90
94
  }
@@ -117,14 +121,6 @@ export class AvmPersistableStateManager {
117
121
  this._merge(forkedState, /*reverted=*/ true);
118
122
  }
119
123
 
120
- /**
121
- * Commit cached storage writes to the DB.
122
- * Keeps public storage up to date from tx to tx within a block.
123
- */
124
- public async commitStorageWritesToDB() {
125
- await this.publicStorage.commitToDB();
126
- }
127
-
128
124
  private _merge(forkedState: AvmPersistableStateManager, reverted: boolean) {
129
125
  // sanity check to avoid merging the same forked trace twice
130
126
  assert(
@@ -135,6 +131,14 @@ export class AvmPersistableStateManager {
135
131
  this.publicStorage.acceptAndMerge(forkedState.publicStorage);
136
132
  this.nullifiers.acceptAndMerge(forkedState.nullifiers);
137
133
  this.trace.merge(forkedState.trace, reverted);
134
+ if (!reverted) {
135
+ this.merkleTrees = forkedState.merkleTrees;
136
+ if (this.doMerkleOperations) {
137
+ this.log.debug(
138
+ `Rolled back nullifier tree to root ${this.merkleTrees.treeMap.get(MerkleTreeId.NULLIFIER_TREE)!.getRoot()}`,
139
+ );
140
+ }
141
+ }
138
142
  }
139
143
 
140
144
  /**
@@ -293,9 +297,10 @@ export class AvmPersistableStateManager {
293
297
  * @returns exists - whether the nullifier exists in the nullifier set
294
298
  */
295
299
  public async checkNullifierExists(contractAddress: AztecAddress, nullifier: Fr): Promise<boolean> {
296
- const [exists, isPending, _] = await this.nullifiers.checkExists(contractAddress, nullifier);
297
-
300
+ this.log.debug(`Checking existence of nullifier (address=${contractAddress}, nullifier=${nullifier})`);
298
301
  const siloedNullifier = siloNullifier(contractAddress, nullifier);
302
+ const [exists, isPending, _] = await this.nullifiers.checkExists(siloedNullifier);
303
+ this.log.debug(`Checked siloed nullifier ${siloedNullifier} (exists=${exists}, pending=${isPending})`);
299
304
 
300
305
  if (this.doMerkleOperations) {
301
306
  // Get leaf if present, low leaf if absent
@@ -310,11 +315,9 @@ export class AvmPersistableStateManager {
310
315
 
311
316
  assert(update == exists, 'WorldStateDB contains nullifier leaf, but merkle tree does not.... This is a bug!');
312
317
 
313
- this.log.debug(
314
- `nullifiers(${contractAddress})@${nullifier} ?? leafIndex: ${leafIndex}, exists: ${exists}, pending: ${isPending}.`,
315
- );
316
-
317
- if (!exists) {
318
+ if (exists) {
319
+ this.log.debug(`Siloed nullifier ${siloedNullifier} exists at leafIndex=${leafIndex}`);
320
+ } else {
318
321
  // Sanity check that the leaf value is skipped by low leaf when it doesn't exist
319
322
  assert(
320
323
  siloedNullifier.toBigInt() > leafPreimage.nullifier.toBigInt() &&
@@ -323,20 +326,9 @@ export class AvmPersistableStateManager {
323
326
  );
324
327
  }
325
328
 
326
- this.trace.traceNullifierCheck(
327
- contractAddress,
328
- nullifier, // FIXME: Should this be siloed?
329
- exists,
330
- leafPreimage,
331
- new Fr(leafIndex),
332
- leafPath,
333
- );
329
+ this.trace.traceNullifierCheck(siloedNullifier, exists, leafPreimage, new Fr(leafIndex), leafPath);
334
330
  } else {
335
- this.trace.traceNullifierCheck(
336
- contractAddress,
337
- nullifier, // FIXME: Should this be siloed?
338
- exists,
339
- );
331
+ this.trace.traceNullifierCheck(siloedNullifier, exists);
340
332
  }
341
333
  return Promise.resolve(exists);
342
334
  }
@@ -347,9 +339,17 @@ export class AvmPersistableStateManager {
347
339
  * @param nullifier - the unsiloed nullifier to write
348
340
  */
349
341
  public async writeNullifier(contractAddress: AztecAddress, nullifier: Fr) {
350
- this.log.debug(`nullifiers(${contractAddress}) += ${nullifier}.`);
351
-
342
+ this.log.debug(`Inserting new nullifier (address=${nullifier}, nullifier=${contractAddress})`);
352
343
  const siloedNullifier = siloNullifier(contractAddress, nullifier);
344
+ await this.writeSiloedNullifier(siloedNullifier);
345
+ }
346
+
347
+ /**
348
+ * Write a nullifier to the nullifier set, trace the write.
349
+ * @param siloedNullifier - the siloed nullifier to write
350
+ */
351
+ public async writeSiloedNullifier(siloedNullifier: Fr) {
352
+ this.log.debug(`Inserting siloed nullifier=${siloedNullifier}`);
353
353
 
354
354
  if (this.doMerkleOperations) {
355
355
  // Maybe overkill, but we should check if the nullifier is already present in the tree before attempting to insert
@@ -360,34 +360,35 @@ export class AvmPersistableStateManager {
360
360
  siloedNullifier,
361
361
  );
362
362
  if (update) {
363
- this.log.verbose(`Nullifier already present in tree: ${nullifier} at index ${index}.`);
363
+ this.log.verbose(`Siloed nullifier ${siloedNullifier} already present in tree at index ${index}!`);
364
364
  // If the nullifier is already present, we should not insert it again
365
365
  // instead we provide the direct membership path
366
366
  const path = await this.merkleTrees.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, index);
367
367
  // This just becomes a nullifier read hint
368
368
  this.trace.traceNullifierCheck(
369
- contractAddress,
370
- nullifier,
369
+ siloedNullifier,
371
370
  /*exists=*/ update,
372
371
  preimage as NullifierLeafPreimage,
373
372
  new Fr(index),
374
373
  path,
375
374
  );
376
375
  throw new NullifierCollisionError(
377
- `Nullifier ${nullifier} at contract ${contractAddress} already exists in parent cache or host.`,
376
+ `Siloed nullifier ${siloedNullifier} already exists in parent cache or host.`,
378
377
  );
379
378
  } else {
380
379
  // Cache pending nullifiers for later access
381
- await this.nullifiers.append(contractAddress, nullifier);
380
+ await this.nullifiers.append(siloedNullifier);
382
381
  // We append the new nullifier
383
382
  const appendResult = await this.merkleTrees.appendNullifier(siloedNullifier);
383
+ this.log.debug(
384
+ `Nullifier tree root after insertion ${this.merkleTrees.treeMap.get(MerkleTreeId.NULLIFIER_TREE)!.getRoot()}`,
385
+ );
384
386
  const lowLeafPreimage = appendResult.lowWitness.preimage as NullifierLeafPreimage;
385
387
  const lowLeafIndex = appendResult.lowWitness.index;
386
388
  const lowLeafPath = appendResult.lowWitness.siblingPath;
387
389
  const insertionPath = appendResult.insertionPath;
388
390
  this.trace.traceNewNullifier(
389
- contractAddress,
390
- nullifier,
391
+ siloedNullifier,
391
392
  lowLeafPreimage,
392
393
  new Fr(lowLeafIndex),
393
394
  lowLeafPath,
@@ -396,8 +397,14 @@ export class AvmPersistableStateManager {
396
397
  }
397
398
  } else {
398
399
  // Cache pending nullifiers for later access
399
- await this.nullifiers.append(contractAddress, nullifier);
400
- this.trace.traceNewNullifier(contractAddress, nullifier);
400
+ await this.nullifiers.append(siloedNullifier);
401
+ this.trace.traceNewNullifier(siloedNullifier);
402
+ }
403
+ }
404
+
405
+ public async writeSiloedNullifiersFromPrivate(siloedNullifiers: Fr[]) {
406
+ for (const siloedNullifier of siloedNullifiers.filter(n => !n.isEmpty())) {
407
+ await this.writeSiloedNullifier(siloedNullifier);
401
408
  }
402
409
  }
403
410
 
@@ -469,7 +476,7 @@ export class AvmPersistableStateManager {
469
476
  if (exists) {
470
477
  const instance = new SerializableContractInstance(instanceWithAddress);
471
478
  this.log.debug(
472
- `Got contract instance (address=${contractAddress}): exists=${exists}, instance=${JSON.stringify(instance)}`,
479
+ `Got contract instance (address=${contractAddress}): exists=${exists}, instance=${jsonStringify(instance)}`,
473
480
  );
474
481
  this.trace.traceGetContractInstance(contractAddress, exists, instance);
475
482
 
@@ -491,17 +498,23 @@ export class AvmPersistableStateManager {
491
498
 
492
499
  if (exists) {
493
500
  const instance = new SerializableContractInstance(instanceWithAddress);
494
-
495
501
  const contractClass = await this.worldStateDB.getContractClass(instance.contractClassId);
502
+ const bytecodeCommitment = await this.worldStateDB.getBytecodeCommitment(instance.contractClassId);
503
+
496
504
  assert(
497
505
  contractClass,
498
506
  `Contract class not found in DB, but a contract instance was found with this class ID (${instance.contractClassId}). This should not happen!`,
499
507
  );
500
508
 
509
+ assert(
510
+ bytecodeCommitment,
511
+ `Bytecode commitment was not found in DB for contract class (${instance.contractClassId}). This should not happen!`,
512
+ );
513
+
501
514
  const contractClassPreimage = {
502
515
  artifactHash: contractClass.artifactHash,
503
516
  privateFunctionsRoot: contractClass.privateFunctionsRoot,
504
- publicBytecodeCommitment: computePublicBytecodeCommitment(contractClass.packedBytecode),
517
+ publicBytecodeCommitment: bytecodeCommitment,
505
518
  };
506
519
 
507
520
  this.trace.traceGetBytecode(
@@ -511,6 +524,7 @@ export class AvmPersistableStateManager {
511
524
  instance,
512
525
  contractClassPreimage,
513
526
  );
527
+
514
528
  return contractClass.packedBytecode;
515
529
  } else {
516
530
  // If the contract instance is not found, we assume it has not been deployed.