@@ -51,11 +51,10 @@ export class AvmEphemeralForest {
51
51
constructor (
52
52
public treeDb : MerkleTreeReadOperations ,
53
53
public treeMap : Map < MerkleTreeId , EphemeralAvmTree > ,
54
- // This contains the preimage and the leaf index of leaf in the ephemeral tree that contains the lowest key (i.e. nullifier value or public data tree slot)
55
- public indexedTreeMin : Map < IndexedTreeId , [ IndexedTreeLeafPreimage , bigint ] > ,
56
54
// This contains the [leaf index,indexed leaf preimages] tuple that were updated or inserted in the ephemeral tree
57
55
// This is needed since we have a sparse collection of keys sorted leaves in the ephemeral tree
58
56
public indexedUpdates : Map < IndexedTreeId , Map < bigint , IndexedTreeLeafPreimage > > ,
57
+ public indexedSortedKeys : Map < IndexedTreeId , [ Fr , bigint ] [ ] > ,
59
58
) { }
60
59
61
60
static async create ( treeDb : MerkleTreeReadOperations ) : Promise < AvmEphemeralForest > {
@@ -65,15 +64,18 @@ export class AvmEphemeralForest {
65
64
const tree = await EphemeralAvmTree . create ( treeInfo . size , treeInfo . depth , treeDb , treeType ) ;
66
65
treeMap . set ( treeType , tree ) ;
67
66
}
68
- return new AvmEphemeralForest ( treeDb , treeMap , new Map ( ) , new Map ( ) ) ;
67
+ const indexedSortedKeys = new Map < IndexedTreeId , [ Fr , bigint ] [ ] > ( ) ;
68
+ indexedSortedKeys . set ( MerkleTreeId . NULLIFIER_TREE as IndexedTreeId , [ ] ) ;
69
+ indexedSortedKeys . set ( MerkleTreeId . PUBLIC_DATA_TREE as IndexedTreeId , [ ] ) ;
70
+ return new AvmEphemeralForest ( treeDb , treeMap , new Map ( ) , indexedSortedKeys ) ;
69
71
}
70
72
71
73
fork ( ) : AvmEphemeralForest {
72
74
return new AvmEphemeralForest (
73
75
this . treeDb ,
74
76
cloneDeep ( this . treeMap ) ,
75
- cloneDeep ( this . indexedTreeMin ) ,
76
77
cloneDeep ( this . indexedUpdates ) ,
78
+ cloneDeep ( this . indexedSortedKeys ) ,
77
79
) ;
78
80
}
79
81
@@ -166,7 +168,8 @@ export class AvmEphemeralForest {
166
168
const insertionPath = tree . getSiblingPath ( insertionIndex ) ! ;
167
169
168
170
// Even though we append an empty leaf into the tree as a part of update - it doesnt seem to impact future inserts...
169
- this . _updateMinInfo ( MerkleTreeId . PUBLIC_DATA_TREE , [ updatedPreimage ] , [ index ] ) ;
171
+ this . _updateSortedKeys ( treeId , [ updatedPreimage . slot ] , [ index ] ) ;
172
+
170
173
return {
171
174
leafIndex : insertionIndex ,
172
175
insertionPath,
@@ -193,8 +196,10 @@ export class AvmEphemeralForest {
193
196
) ;
194
197
const insertionPath = this . appendIndexedTree ( treeId , index , updatedLowLeaf , newPublicDataLeaf ) ;
195
198
196
- // Since we are appending, we might have a new minimum public data leaf
197
- this . _updateMinInfo ( MerkleTreeId . PUBLIC_DATA_TREE , [ newPublicDataLeaf , updatedLowLeaf ] , [ insertionIndex , index ] ) ;
199
+ // Even though the low leaf key is not updated, we still need to update the sorted keys in case we have
200
+ // not seen the low leaf before
201
+ this . _updateSortedKeys ( treeId , [ newPublicDataLeaf . slot , updatedLowLeaf . slot ] , [ insertionIndex , index ] ) ;
202
+
198
203
return {
199
204
leafIndex : insertionIndex ,
200
205
insertionPath : insertionPath ,
@@ -208,28 +213,25 @@ export class AvmEphemeralForest {
208
213
} ;
209
214
}
210
215
211
- /**
212
- * This is just a helper to compare the preimages and update the minimum public data leaf
213
- * @param treeId - The tree to be queried for a sibling path.
214
- * @param T - The type of the preimage (PublicData or Nullifier)
215
- * @param preimages - The preimages to be compared
216
- * @param indices - The indices of the preimages
217
- */
218
- private _updateMinInfo < T extends IndexedTreeLeafPreimage > (
219
- treeId : IndexedTreeId ,
220
- preimages : T [ ] ,
221
- indices : bigint [ ] ,
222
- ) : void {
223
- let currentMin = this . getMinInfo ( treeId ) ;
224
- if ( currentMin === undefined ) {
225
- currentMin = { preimage : preimages [ 0 ] , index : indices [ 0 ] } ;
226
- }
227
- for ( let i = 0 ; i < preimages . length ; i ++ ) {
228
- if ( preimages [ i ] . getKey ( ) <= currentMin . preimage . getKey ( ) ) {
229
- currentMin = { preimage : preimages [ i ] , index : indices [ i ] } ;
216
+ private _updateSortedKeys ( treeId : IndexedTreeId , keys : Fr [ ] , index : bigint [ ] ) : void {
217
+ // This is a reference
218
+ const existingKeyVector = this . indexedSortedKeys . get ( treeId ) ! ;
219
+ // Should already be sorted so not need to re-sort if we just update or splice
220
+ for ( let i = 0 ; i < keys . length ; i ++ ) {
221
+ const foundIndex = existingKeyVector . findIndex ( x => x [ 1 ] === index [ i ] ) ;
222
+ if ( foundIndex === - 1 ) {
223
+ // New element, we splice it into the correct location
224
+ const spliceIndex =
225
+ this . searchForKey (
226
+ keys [ i ] ,
227
+ existingKeyVector . map ( x => x [ 0 ] ) ,
228
+ ) + 1 ;
229
+ existingKeyVector . splice ( spliceIndex , 0 , [ keys [ i ] , index [ i ] ] ) ;
230
+ } else {
231
+ // Update the existing element
232
+ existingKeyVector [ foundIndex ] [ 0 ] = keys [ i ] ;
230
233
}
231
234
}
232
- this . setMinInfo ( treeId , currentMin . preimage , currentMin . index ) ;
233
235
}
234
236
235
237
/**
@@ -258,8 +260,14 @@ export class AvmEphemeralForest {
258
260
const newNullifierLeaf = new NullifierLeafPreimage ( nullifier , preimage . nextNullifier , preimage . nextIndex ) ;
259
261
const insertionPath = this . appendIndexedTree ( treeId , index , updatedLowNullifier , newNullifierLeaf ) ;
260
262
261
- // Since we are appending, we might have a new minimum nullifier leaf
262
- this . _updateMinInfo ( MerkleTreeId . NULLIFIER_TREE , [ newNullifierLeaf , updatedLowNullifier ] , [ insertionIndex , index ] ) ;
263
+ // Even though the low nullifier key is not updated, we still need to update the sorted keys in case we have
264
+ // not seen the low nullifier before
265
+ this . _updateSortedKeys (
266
+ treeId ,
267
+ [ newNullifierLeaf . nullifier , updatedLowNullifier . nullifier ] ,
268
+ [ insertionIndex , index ] ,
269
+ ) ;
270
+
263
271
return {
264
272
leafIndex : insertionIndex ,
265
273
insertionPath : insertionPath ,
@@ -286,31 +294,6 @@ export class AvmEphemeralForest {
286
294
return insertionPath ! ;
287
295
}
288
296
289
- /**
290
- * This is wrapper around treeId to get the correct minimum leaf preimage
291
- */
292
- private getMinInfo < ID extends IndexedTreeId , T extends IndexedTreeLeafPreimage > (
293
- treeId : ID ,
294
- ) : { preimage : T ; index : bigint } | undefined {
295
- const start = this . indexedTreeMin . get ( treeId ) ;
296
- if ( start === undefined ) {
297
- return undefined ;
298
- }
299
- const [ preimage , index ] = start ;
300
- return { preimage : preimage as T , index } ;
301
- }
302
-
303
- /**
304
- * This is wrapper around treeId to set the correct minimum leaf preimage
305
- */
306
- private setMinInfo < ID extends IndexedTreeId , T extends IndexedTreeLeafPreimage > (
307
- treeId : ID ,
308
- preimage : T ,
309
- index : bigint ,
310
- ) : void {
311
- this . indexedTreeMin . set ( treeId , [ preimage , index ] ) ;
312
- }
313
-
314
297
/**
315
298
* This is wrapper around treeId to set values in the indexedUpdates map
316
299
*/
@@ -353,6 +336,28 @@ export class AvmEphemeralForest {
353
336
return updates . has ( index ) ;
354
337
}
355
338
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
+
356
361
/**
357
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)
358
363
* If the value is not found in the tree, it does an external lookup to the merkleDB
@@ -365,23 +370,42 @@ export class AvmEphemeralForest {
365
370
treeId : ID ,
366
371
key : Fr ,
367
372
) : Promise < PreimageWitness < T > > {
373
+ const keyOrderedVector = this . indexedSortedKeys . get ( treeId ) ! ;
374
+
375
+ const vectorIndex = this . searchForKey (
376
+ key ,
377
+ keyOrderedVector . map ( x => x [ 0 ] ) ,
378
+ ) ;
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
+ }
368
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
369
- const minPreimage = this . getMinInfo ( treeId ) ;
370
390
const start = minPreimage ?. preimage ;
371
391
const bigIntKey = key . toBigInt ( ) ;
372
- // If the first element we have is already greater than the value, we need to do an external lookup
373
- if ( minPreimage === undefined || ( start ?. getKey ( ) ?? 0n ) >= key . toBigInt ( ) ) {
374
- // The low public data witness is in the previous tree
392
+
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
375
397
const { index, alreadyPresent } = ( await this . treeDb . getPreviousValueIndex ( treeId , bigIntKey ) ) ! ;
376
398
const preimage = await this . treeDb . getLeafPreimage ( treeId , index ) ;
377
399
378
- // Since we have never seen this before - we should insert it into our tree
379
- const siblingPath = ( await this . treeDb . getSiblingPath ( treeId , index ) ) . toFields ( ) ;
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();
380
403
381
- // Is it enough to just insert the sibling path without inserting the leaf? - right now probably since we will update this low nullifier index in append
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
382
405
this . treeMap . get ( treeId ) ! . insertSiblingPath ( index , siblingPath ) ;
383
406
384
407
const lowPublicDataPreimage = preimage as T ;
408
+
385
409
return { preimage : lowPublicDataPreimage , index : index , update : alreadyPresent } ;
386
410
}
387
411
@@ -392,18 +416,18 @@ export class AvmEphemeralForest {
392
416
// (3) Max Condition: curr.next_index == 0 and curr.key < key
393
417
// Note the min condition does not need to be handled since indexed trees are prefilled with at least the 0 element
394
418
let found = false ;
395
- let curr = minPreimage . preimage as T ;
419
+ let curr = minPreimage ! . preimage as T ;
396
420
let result : PreimageWitness < T > | undefined = undefined ;
397
421
// Temp to avoid infinite loops - the limit is the number of leaves we may have to read
398
422
const LIMIT = 2n ** BigInt ( getTreeHeight ( treeId ) ) - 1n ;
399
423
let counter = 0n ;
400
- let lowPublicDataIndex = minPreimage . index ;
424
+ let lowPublicDataIndex = minPreimage ! . index ;
401
425
while ( ! found && counter < LIMIT ) {
402
426
if ( curr . getKey ( ) === bigIntKey ) {
403
427
// We found an exact match - therefore this is an update
404
428
found = true ;
405
429
result = { preimage : curr , index : lowPublicDataIndex , update : true } ;
406
- } else if ( curr . getKey ( ) < bigIntKey && ( curr . getNextKey ( ) === 0n || curr . getNextKey ( ) > bigIntKey ) ) {
430
+ } else if ( curr . getKey ( ) < bigIntKey && ( curr . getNextIndex ( ) === 0n || curr . getNextKey ( ) > bigIntKey ) ) {
407
431
// We found it via sandwich or max condition, this is a low nullifier
408
432
found = true ;
409
433
result = { preimage : curr , index : lowPublicDataIndex , update : false } ;
0 commit comments