5
5
ValueEncoding ,
6
6
bytesToHex ,
7
7
equalsBytes ,
8
+ intToHex ,
8
9
zeros ,
9
10
} from '@ethereumjs/util'
10
11
import debug from 'debug'
@@ -14,7 +15,7 @@ import { CheckpointDB } from './db/checkpoint.js'
14
15
import { InternalNode } from './node/internalNode.js'
15
16
import { LeafNode } from './node/leafNode.js'
16
17
import { type VerkleNode } from './node/types.js'
17
- import { decodeNode } from './node/util.js'
18
+ import { decodeNode , isLeafNode } from './node/util.js'
18
19
import {
19
20
type Proof ,
20
21
ROOT_DB_KEY ,
@@ -133,7 +134,9 @@ export class VerkleTree {
133
134
}
134
135
}
135
136
136
- return new VerkleTree ( opts )
137
+ const trie = new VerkleTree ( opts )
138
+ await trie . _createRootNode ( )
139
+ return trie
137
140
}
138
141
139
142
database ( db ?: DB < Uint8Array , Uint8Array > ) {
@@ -194,7 +197,6 @@ export class VerkleTree {
194
197
const suffix = key [ key . length - 1 ]
195
198
this . DEBUG && this . debug ( `Stem: ${ bytesToHex ( stem ) } ; Suffix: ${ suffix } ` , [ 'GET' ] )
196
199
const res = await this . findPath ( stem )
197
-
198
200
if ( res . node instanceof LeafNode ) {
199
201
// The retrieved leaf node contains an array of 256 possible values.
200
202
// The index of the value we want is at the key's last byte
@@ -213,11 +215,169 @@ export class VerkleTree {
213
215
* @param value - the value to store
214
216
* @returns A Promise that resolves once value is stored.
215
217
*/
216
- // TODO: Rewrite following logic in verkle.spec.ts "findPath validation" test
217
- async put ( _key : Uint8Array , _value : Uint8Array ) : Promise < void > {
218
- throw new Error ( 'not implemented' )
218
+ async put ( key : Uint8Array , value : Uint8Array ) : Promise < void > {
219
+ if ( key . length !== 32 ) throw new Error ( `expected key with length 32; got ${ key . length } ` )
220
+ const stem = key . slice ( 0 , 31 )
221
+ const suffix = key [ key . length - 1 ]
222
+ this . DEBUG && this . debug ( `Stem: ${ bytesToHex ( stem ) } ; Suffix: ${ suffix } ` , [ 'PUT' ] )
223
+
224
+ const putStack : [ Uint8Array , VerkleNode ] [ ] = [ ]
225
+ // Find path to nearest node
226
+ const foundPath = await this . findPath ( stem )
227
+
228
+ // Sanity check - we should at least get the root node back
229
+ if ( foundPath . stack . length === 0 ) {
230
+ throw new Error ( `Root node not found in trie` )
231
+ }
232
+
233
+ // Step 1) Create or update the leaf node
234
+ let leafNode : LeafNode
235
+ // First see if leaf node already exists
236
+ if ( foundPath . node !== null ) {
237
+ // Sanity check to verify we have the right node type
238
+ if ( ! isLeafNode ( foundPath . node ) ) {
239
+ throw new Error (
240
+ `expected leaf node found at ${ bytesToHex ( stem ) } . Got internal node instead`
241
+ )
242
+ }
243
+ leafNode = foundPath . node
244
+ // Sanity check to verify we have the right leaf node
245
+ if ( ! equalsBytes ( leafNode . stem , stem ) ) {
246
+ throw new Error (
247
+ `invalid leaf node found. Expected stem: ${ bytesToHex ( stem ) } ; got ${ bytesToHex (
248
+ foundPath . node . stem
249
+ ) } `
250
+ )
251
+ }
252
+ } else {
253
+ // Leaf node doesn't exist, create a new one
254
+ leafNode = await LeafNode . create (
255
+ stem ,
256
+ new Array ( 256 ) . fill ( new Uint8Array ( 32 ) ) ,
257
+ this . verkleCrypto
258
+ )
259
+ this . DEBUG && this . debug ( `Creating new leaf node at stem: ${ bytesToHex ( stem ) } ` , [ 'PUT' ] )
260
+ }
261
+ // Update value in leaf node and push to putStack
262
+ leafNode . setValue ( suffix , value )
263
+ this . DEBUG &&
264
+ this . debug (
265
+ `Updating value for suffix: ${ suffix } at leaf node with stem: ${ bytesToHex ( stem ) } ` ,
266
+ [ 'PUT' ]
267
+ )
268
+ putStack . push ( [ leafNode . hash ( ) , leafNode ] )
269
+
270
+ // `path` is the path to the last node pushed to the `putStack`
271
+ let lastPath = leafNode . stem
272
+
273
+ // Step 2) Determine if a new internal node is needed
274
+ if ( foundPath . stack . length > 1 ) {
275
+ // Only insert new internal node if we have more than 1 node in the path
276
+ // since a single node indicates only the root node is in the path
277
+ const nearestNodeTuple = foundPath . stack . pop ( ) !
278
+ const nearestNode = nearestNodeTuple [ 0 ]
279
+ lastPath = nearestNodeTuple [ 1 ]
280
+ const updatedParentTuple = this . updateParent ( leafNode , nearestNode , lastPath )
281
+ putStack . push ( [ updatedParentTuple . node . hash ( ) , updatedParentTuple . node ] )
282
+ lastPath = updatedParentTuple . lastPath
283
+
284
+ // Step 3) Walk up trie and update child references in parent internal nodes
285
+ while ( foundPath . stack . length > 1 ) {
286
+ const [ nextNode , nextPath ] = foundPath . stack . pop ( ) ! as [ InternalNode , Uint8Array ]
287
+ // Compute the child index to be updated on `nextNode`
288
+ const childIndex = lastPath [ matchingBytesLength ( lastPath , nextPath ) ]
289
+ // Update child reference
290
+ nextNode . setChild ( childIndex , {
291
+ commitment : putStack [ putStack . length - 1 ] [ 1 ] . commitment ,
292
+ path : lastPath ,
293
+ } )
294
+ this . DEBUG &&
295
+ this . debug (
296
+ `Updating child reference for node with path: ${ bytesToHex (
297
+ lastPath
298
+ ) } at index ${ childIndex } in internal node at path ${ bytesToHex ( nextPath ) } `,
299
+ [ 'PUT' ]
300
+ )
301
+ // Hold onto `path` to current node for updating next parent node child index
302
+ lastPath = nextPath
303
+ putStack . push ( [ nextNode . hash ( ) , nextNode ] )
304
+ }
305
+ }
306
+
307
+ // Step 4) Update root node
308
+ const rootNode = foundPath . stack . pop ( ) ! [ 0 ] as InternalNode
309
+ rootNode . setChild ( stem [ 0 ] , {
310
+ commitment : putStack [ putStack . length - 1 ] [ 1 ] . commitment ,
311
+ path : lastPath ,
312
+ } )
313
+ this . root ( this . verkleCrypto . serializeCommitment ( rootNode . commitment ) )
314
+ this . DEBUG &&
315
+ this . debug (
316
+ `Updating child reference for node with path: ${ bytesToHex ( lastPath ) } at index ${
317
+ lastPath [ 0 ]
318
+ } in root node`,
319
+ [ 'PUT' ]
320
+ )
321
+ this . DEBUG && this . debug ( `Updating root node hash to ${ bytesToHex ( this . _root ) } ` , [ 'PUT' ] )
322
+ putStack . push ( [ this . _root , rootNode ] )
323
+ await this . saveStack ( putStack )
219
324
}
220
325
326
+ /**
327
+ * Helper method for updating or creating the parent internal node for a given leaf node
328
+ * @param leafNode the child leaf node that will be referenced by the new/updated internal node
329
+ * returned by this method
330
+ * @param nearestNode the nearest node to the new leaf node
331
+ * @param pathToNode the path to `nearestNode`
332
+ * @returns a tuple of the updated parent node and the path to that parent (i.e. the partial stem of the leaf node that leads to the parent)
333
+ */
334
+ updateParent (
335
+ leafNode : LeafNode ,
336
+ nearestNode : VerkleNode ,
337
+ pathToNode : Uint8Array
338
+ ) : { node : InternalNode ; lastPath : Uint8Array } {
339
+ // Compute the portion of leafNode.stem and nearestNode.path that match (i.e. the partial path closest to leafNode.stem)
340
+ const partialMatchingStemIndex = matchingBytesLength ( leafNode . stem , pathToNode )
341
+ let internalNode : InternalNode
342
+ if ( isLeafNode ( nearestNode ) ) {
343
+ // We need to create a new internal node and set nearestNode and leafNode as child nodes of it
344
+ // Create new internal node
345
+ internalNode = InternalNode . create ( this . verkleCrypto )
346
+ // Set leafNode and nextNode as children of the new internal node
347
+ internalNode . setChild ( leafNode . stem [ partialMatchingStemIndex ] , {
348
+ commitment : leafNode . commitment ,
349
+ path : leafNode . stem ,
350
+ } )
351
+ internalNode . setChild ( nearestNode . stem [ partialMatchingStemIndex ] , {
352
+ commitment : nearestNode . commitment ,
353
+ path : nearestNode . stem ,
354
+ } )
355
+ // Find the path to the new internal node (the matching portion of the leaf node and next node's stems)
356
+ pathToNode = leafNode . stem . slice ( 0 , partialMatchingStemIndex )
357
+ this . DEBUG &&
358
+ this . debug ( `Creating new internal node at path ${ bytesToHex ( pathToNode ) } ` , [ 'PUT' ] )
359
+ } else {
360
+ // Nearest node is an internal node. We need to update the appropriate child reference
361
+ // to the new leaf node
362
+ internalNode = nearestNode
363
+ internalNode . setChild ( leafNode . stem [ partialMatchingStemIndex ] , {
364
+ commitment : leafNode . commitment ,
365
+ path : leafNode . stem ,
366
+ } )
367
+ this . DEBUG &&
368
+ this . debug (
369
+ `Updating child reference for leaf node with stem: ${ bytesToHex (
370
+ leafNode . stem
371
+ ) } at index ${
372
+ leafNode . stem [ partialMatchingStemIndex ]
373
+ } in internal node at path ${ bytesToHex (
374
+ leafNode . stem . slice ( 0 , partialMatchingStemIndex )
375
+ ) } `,
376
+ [ 'PUT' ]
377
+ )
378
+ }
379
+ return { node : internalNode , lastPath : pathToNode }
380
+ }
221
381
/**
222
382
* Tries to find a path to the node for the given key.
223
383
* It returns a `stack` of nodes to the closest node.
@@ -231,12 +391,13 @@ export class VerkleTree {
231
391
stack : [ ] ,
232
392
remaining : key ,
233
393
}
234
- if ( equalsBytes ( this . root ( ) , this . EMPTY_TREE_ROOT ) ) return result
394
+
395
+ // TODO: Decide if findPath should return an empty stack if we have an empty trie or a path with just the empty root node
396
+ // if (equalsBytes(this.root(), this.EMPTY_TREE_ROOT)) return result
235
397
236
398
// Get root node
237
399
let rawNode = await this . _db . get ( this . root ( ) )
238
- if ( rawNode === undefined )
239
- throw new Error ( 'root node should exist when root not empty tree root' )
400
+ if ( rawNode === undefined ) throw new Error ( 'root node should exist' )
240
401
241
402
const rootNode = decodeNode ( rawNode , this . verkleCrypto ) as InternalNode
242
403
@@ -246,7 +407,7 @@ export class VerkleTree {
246
407
247
408
// Root node doesn't contain a child node's commitment on the first byte of the path so we're done
248
409
if ( equalsBytes ( child . commitment , this . verkleCrypto . zeroCommitment ) ) {
249
- this . DEBUG && this . debug ( `Partial Path ${ key [ 0 ] } - found no child.` , [ 'FIND_PATH' ] )
410
+ this . DEBUG && this . debug ( `Partial Path ${ intToHex ( key [ 0 ] ) } - found no child.` , [ 'FIND_PATH' ] )
250
411
return result
251
412
}
252
413
let finished = false
@@ -260,7 +421,7 @@ export class VerkleTree {
260
421
// Calculate the index of the last matching byte in the key
261
422
const matchingKeyLength = matchingBytesLength ( key , child . path )
262
423
const foundNode = equalsBytes ( key , child . path )
263
- if ( foundNode || child . path . length >= key . length || decodedNode instanceof LeafNode ) {
424
+ if ( foundNode || child . path . length >= key . length || isLeafNode ( decodedNode ) ) {
264
425
// If the key and child.path are equal, then we found the node
265
426
// If the child.path is the same length or longer than the key but doesn't match it
266
427
// or the found node is a leaf node, we've found another node where this node should
@@ -282,16 +443,15 @@ export class VerkleTree {
282
443
// We found a different node than the one specified by `key`
283
444
// so the sought node doesn't exist
284
445
result . remaining = key . slice ( matchingKeyLength )
446
+ const pathToNearestNode = isLeafNode ( decodedNode ) ? decodedNode . stem : child . path
285
447
this . DEBUG &&
286
448
this . debug (
287
- `Path ${ bytesToHex (
288
- key . slice ( 0 , matchingKeyLength )
289
- ) } - found path to nearest node ${ bytesToHex (
449
+ `Path ${ bytesToHex ( pathToNearestNode ) } - found path to nearest node ${ bytesToHex (
290
450
decodedNode . hash ( )
291
451
) } but target node not found.`,
292
452
[ 'FIND_PATH' ]
293
453
)
294
- result . stack . push ( [ decodedNode , key . slice ( 0 , matchingKeyLength ) ] )
454
+ result . stack . push ( [ decodedNode , pathToNearestNode ] )
295
455
return result
296
456
}
297
457
// Push internal node to path stack
0 commit comments