@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.
- package/dest/avm/avm_memory_types.d.ts +3 -1
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +22 -24
- package/dest/avm/avm_simulator.d.ts +5 -1
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +16 -9
- package/dest/avm/avm_tree.d.ts +56 -7
- package/dest/avm/avm_tree.d.ts.map +1 -1
- package/dest/avm/avm_tree.js +155 -82
- package/dest/avm/errors.d.ts +19 -0
- package/dest/avm/errors.d.ts.map +1 -1
- package/dest/avm/errors.js +29 -1
- package/dest/avm/journal/journal.d.ts +8 -7
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +47 -29
- package/dest/avm/journal/nullifiers.d.ts +11 -58
- package/dest/avm/journal/nullifiers.d.ts.map +1 -1
- package/dest/avm/journal/nullifiers.js +27 -107
- package/dest/avm/opcodes/contract.d.ts +2 -2
- package/dest/avm/opcodes/contract.d.ts.map +1 -1
- package/dest/avm/opcodes/contract.js +4 -4
- package/dest/avm/opcodes/control_flow.d.ts +2 -2
- package/dest/avm/opcodes/control_flow.d.ts.map +1 -1
- package/dest/avm/opcodes/control_flow.js +4 -4
- package/dest/avm/opcodes/environment_getters.d.ts +2 -2
- package/dest/avm/opcodes/environment_getters.d.ts.map +1 -1
- package/dest/avm/opcodes/environment_getters.js +4 -4
- package/dest/avm/opcodes/instruction.d.ts +1 -1
- package/dest/avm/opcodes/instruction.d.ts.map +1 -1
- package/dest/avm/opcodes/instruction.js +1 -1
- package/dest/avm/opcodes/memory.d.ts +4 -4
- package/dest/avm/opcodes/memory.d.ts.map +1 -1
- package/dest/avm/opcodes/memory.js +17 -13
- package/dest/avm/opcodes/misc.d.ts +2 -2
- package/dest/avm/opcodes/misc.d.ts.map +1 -1
- package/dest/avm/opcodes/misc.js +4 -4
- package/dest/avm/serialization/buffer_cursor.d.ts +2 -0
- package/dest/avm/serialization/buffer_cursor.d.ts.map +1 -1
- package/dest/avm/serialization/buffer_cursor.js +8 -3
- package/dest/avm/serialization/bytecode_serialization.d.ts +1 -0
- package/dest/avm/serialization/bytecode_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/bytecode_serialization.js +27 -13
- package/dest/avm/serialization/instruction_serialization.d.ts +1 -0
- package/dest/avm/serialization/instruction_serialization.d.ts.map +1 -1
- package/dest/avm/serialization/instruction_serialization.js +9 -6
- package/dest/avm/test_utils.d.ts.map +1 -1
- package/dest/avm/test_utils.js +3 -2
- package/dest/common/errors.d.ts.map +1 -1
- package/dest/common/errors.js +3 -2
- package/dest/public/dual_side_effect_trace.d.ts +2 -2
- package/dest/public/dual_side_effect_trace.d.ts.map +1 -1
- package/dest/public/dual_side_effect_trace.js +7 -7
- package/dest/public/enqueued_call_side_effect_trace.d.ts +8 -23
- package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
- package/dest/public/enqueued_call_side_effect_trace.js +31 -92
- package/dest/public/executor_metrics.d.ts +4 -2
- package/dest/public/executor_metrics.d.ts.map +1 -1
- package/dest/public/executor_metrics.js +20 -3
- package/dest/public/fixtures/index.d.ts.map +1 -1
- package/dest/public/fixtures/index.js +66 -35
- package/dest/public/public_db_sources.d.ts +3 -1
- package/dest/public/public_db_sources.d.ts.map +1 -1
- package/dest/public/public_db_sources.js +26 -11
- package/dest/public/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor.js +12 -5
- package/dest/public/public_tx_context.d.ts +5 -6
- package/dest/public/public_tx_context.d.ts.map +1 -1
- package/dest/public/public_tx_context.js +19 -17
- package/dest/public/public_tx_simulator.d.ts +13 -2
- package/dest/public/public_tx_simulator.d.ts.map +1 -1
- package/dest/public/public_tx_simulator.js +263 -217
- package/dest/public/side_effect_trace.d.ts +2 -2
- package/dest/public/side_effect_trace.d.ts.map +1 -1
- package/dest/public/side_effect_trace.js +8 -6
- package/dest/public/side_effect_trace_interface.d.ts +2 -2
- package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
- package/dest/public/transitional_adapters.d.ts.map +1 -1
- package/dest/public/transitional_adapters.js +2 -6
- package/package.json +9 -9
- package/src/avm/avm_memory_types.ts +26 -24
- package/src/avm/avm_simulator.ts +20 -13
- package/src/avm/avm_tree.ts +196 -93
- package/src/avm/errors.ts +31 -0
- package/src/avm/journal/journal.ts +61 -47
- package/src/avm/journal/nullifiers.ts +29 -121
- package/src/avm/opcodes/contract.ts +2 -2
- package/src/avm/opcodes/control_flow.ts +2 -2
- package/src/avm/opcodes/environment_getters.ts +2 -2
- package/src/avm/opcodes/instruction.ts +1 -1
- package/src/avm/opcodes/memory.ts +15 -10
- package/src/avm/opcodes/misc.ts +2 -2
- package/src/avm/serialization/buffer_cursor.ts +9 -3
- package/src/avm/serialization/bytecode_serialization.ts +29 -13
- package/src/avm/serialization/instruction_serialization.ts +12 -6
- package/src/avm/test_utils.ts +9 -1
- package/src/common/errors.ts +2 -1
- package/src/public/dual_side_effect_trace.ts +6 -30
- package/src/public/enqueued_call_side_effect_trace.ts +35 -154
- package/src/public/executor_metrics.ts +23 -1
- package/src/public/fixtures/index.ts +97 -43
- package/src/public/public_db_sources.ts +29 -15
- package/src/public/public_processor.ts +11 -4
- package/src/public/public_tx_context.ts +21 -23
- package/src/public/public_tx_simulator.ts +45 -8
- package/src/public/side_effect_trace.ts +7 -9
- package/src/public/side_effect_trace_interface.ts +2 -4
- package/src/public/transitional_adapters.ts +0 -11
package/src/avm/avm_tree.ts
CHANGED
|
@@ -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
|
|
151
|
-
treeId,
|
|
152
|
-
|
|
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
|
-
|
|
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
|
|
246
|
-
treeId,
|
|
247
|
-
|
|
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 (
|
|
252
|
-
|
|
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
|
|
330
|
+
private getIndexedUpdate<ID extends IndexedTreeId, T extends IndexedTreeLeafPreimage>(treeId: ID, index: bigint): T {
|
|
317
331
|
const updates = this.indexedUpdates.get(treeId);
|
|
318
|
-
|
|
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
|
-
|
|
323
|
-
|
|
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
|
-
*
|
|
363
|
-
* If the
|
|
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
|
|
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
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
405
|
-
|
|
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
|
-
|
|
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
|
-
|
|
410
|
-
|
|
471
|
+
return { preimage: leafPreimage as T, index: leafIndex, update: alreadyPresent };
|
|
472
|
+
}
|
|
411
473
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(
|
|
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=*/
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
`
|
|
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(`
|
|
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(`
|
|
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
|
-
|
|
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
|
-
`
|
|
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(
|
|
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
|
-
|
|
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(
|
|
400
|
-
this.trace.traceNewNullifier(
|
|
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=${
|
|
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:
|
|
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.
|