UNPKG

23.1 kBPlain TextView Raw
1import Semaphore from 'semaphore-async-await'
2import { keccak, KECCAK256_RLP } from 'ethereumjs-util'
3import { DB, BatchDBOp, PutBatch } from './db'
4import { TrieReadStream as ReadStream } from './readStream'
5import { bufferToNibbles, matchingNibbleLength, doKeysMatch } from './util/nibbles'
6import { WalkController } from './util/walkController'
7import {
8 TrieNode,
9 decodeNode,
10 decodeRawNode,
11 isRawNode,
12 BranchNode,
13 ExtensionNode,
14 LeafNode,
15 EmbeddedNode,
16 Nibbles,
17} from './trieNode'
18// eslint-disable-next-line implicit-dependencies/no-implicit
19import type { LevelUp } from 'levelup'
20const assert = require('assert')
21
22export type Proof = Buffer[]
23
24interface Path {
25 node: TrieNode | null
26 remaining: Nibbles
27 stack: TrieNode[]
28}
29
30export type FoundNodeFunction = (
31 nodeRef: Buffer,
32 node: TrieNode | null,
33 key: Nibbles,
34 walkController: WalkController
35) => void
36
37/**
38 * The basic trie interface, use with `import { BaseTrie as Trie } from 'merkle-patricia-tree'`.
39 * In Ethereum applications stick with the {@link SecureTrie} overlay.
40 * The API for the base and the secure interface are about the same.
41 */
42export class Trie {
43 /** The root for an empty trie */
44 EMPTY_TRIE_ROOT: Buffer
45 protected lock: Semaphore
46
47 /** The backend DB */
48 db: DB
49 private _root: Buffer
50 private _deleteFromDB: boolean
51
52 /**
53 * test
54 * @param db - A [levelup](https://github.com/Level/levelup) instance. By default (if the db is `null` or
55 * left undefined) creates an in-memory [memdown](https://github.com/Level/memdown) instance.
56 * @param root - A `Buffer` for the root of a previously stored trie
57 * @param deleteFromDB - Delete nodes from DB on delete operations (disallows switching to an older state root) (default: `false`)
58 */
59 constructor(db?: LevelUp | null, root?: Buffer, deleteFromDB: boolean = false) {
60 this.EMPTY_TRIE_ROOT = KECCAK256_RLP
61 this.lock = new Semaphore(1)
62
63 this.db = db ? new DB(db) : new DB()
64 this._root = this.EMPTY_TRIE_ROOT
65 this._deleteFromDB = deleteFromDB
66
67 if (root) {
68 this.root = root
69 }
70 }
71
72 /**
73 * Sets the current root of the `trie`
74 */
75 set root(value: Buffer) {
76 if (!value) {
77 value = this.EMPTY_TRIE_ROOT
78 }
79 assert(value.length === 32, 'Invalid root length. Roots are 32 bytes')
80 this._root = value
81 }
82
83 /**
84 * Gets the current root of the `trie`
85 */
86 get root(): Buffer {
87 return this._root
88 }
89
90 /**
91 * This method is deprecated.
92 * Please use {@link Trie.root} instead.
93 *
94 * @param value
95 * @deprecated
96 */
97 setRoot(value?: Buffer) {
98 this.root = value ?? this.EMPTY_TRIE_ROOT
99 }
100
101 /**
102 * Checks if a given root exists.
103 */
104 async checkRoot(root: Buffer): Promise<boolean> {
105 try {
106 const value = await this._lookupNode(root)
107 return value !== null
108 } catch (error: any) {
109 if (error.message == 'Missing node in DB') {
110 return false
111 } else {
112 throw error
113 }
114 }
115 }
116
117 /**
118 * BaseTrie has no checkpointing so return false
119 */
120 get isCheckpoint() {
121 return false
122 }
123
124 /**
125 * Gets a value given a `key`
126 * @param key - the key to search for
127 * @param throwIfMissing - if true, throws if any nodes are missing. Used for verifying proofs. (default: false)
128 * @returns A Promise that resolves to `Buffer` if a value was found or `null` if no value was found.
129 */
130 async get(key: Buffer, throwIfMissing = false): Promise<Buffer | null> {
131 const { node, remaining } = await this.findPath(key, throwIfMissing)
132 let value = null
133 if (node && remaining.length === 0) {
134 value = node.value
135 }
136 return value
137 }
138
139 /**
140 * Stores a given `value` at the given `key` or do a delete if `value` is empty
141 * (delete operations are only executed on DB with `deleteFromDB` set to `true`)
142 * @param key
143 * @param value
144 * @returns A Promise that resolves once value is stored.
145 */
146 async put(key: Buffer, value: Buffer): Promise<void> {
147 // If value is empty, delete
148 if (!value || value.toString() === '') {
149 return await this.del(key)
150 }
151
152 await this.lock.wait()
153 if (this.root.equals(KECCAK256_RLP)) {
154 // If no root, initialize this trie
155 await this._createInitialNode(key, value)
156 } else {
157 // First try to find the given key or its nearest node
158 const { remaining, stack } = await this.findPath(key)
159 // then update
160 await this._updateNode(key, value, remaining, stack)
161 }
162 this.lock.signal()
163 }
164
165 /**
166 * Deletes a value given a `key` from the trie
167 * (delete operations are only executed on DB with `deleteFromDB` set to `true`)
168 * @param key
169 * @returns A Promise that resolves once value is deleted.
170 */
171 async del(key: Buffer): Promise<void> {
172 await this.lock.wait()
173 const { node, stack } = await this.findPath(key)
174 if (node) {
175 await this._deleteNode(key, stack)
176 }
177 this.lock.signal()
178 }
179
180 /**
181 * Tries to find a path to the node for the given key.
182 * It returns a `stack` of nodes to the closest node.
183 * @param key - the search key
184 * @param throwIfMissing - if true, throws if any nodes are missing. Used for verifying proofs. (default: false)
185 */
186 async findPath(key: Buffer, throwIfMissing = false): Promise<Path> {
187 // eslint-disable-next-line no-async-promise-executor
188 return new Promise(async (resolve, reject) => {
189 const stack: TrieNode[] = []
190 const targetKey = bufferToNibbles(key)
191
192 const onFound: FoundNodeFunction = async (nodeRef, node, keyProgress, walkController) => {
193 if (node === null) {
194 return reject(new Error('Path not found'))
195 }
196 const keyRemainder = targetKey.slice(matchingNibbleLength(keyProgress, targetKey))
197 stack.push(node)
198
199 if (node instanceof BranchNode) {
200 if (keyRemainder.length === 0) {
201 // we exhausted the key without finding a node
202 resolve({ node, remaining: [], stack })
203 } else {
204 const branchIndex = keyRemainder[0]
205 const branchNode = node.getBranch(branchIndex)
206 if (!branchNode) {
207 // there are no more nodes to find and we didn't find the key
208 resolve({ node: null, remaining: keyRemainder, stack })
209 } else {
210 // node found, continuing search
211 // this can be optimized as this calls getBranch again.
212 walkController.onlyBranchIndex(node, keyProgress, branchIndex)
213 }
214 }
215 } else if (node instanceof LeafNode) {
216 if (doKeysMatch(keyRemainder, node.key)) {
217 // keys match, return node with empty key
218 resolve({ node, remaining: [], stack })
219 } else {
220 // reached leaf but keys dont match
221 resolve({ node: null, remaining: keyRemainder, stack })
222 }
223 } else if (node instanceof ExtensionNode) {
224 const matchingLen = matchingNibbleLength(keyRemainder, node.key)
225 if (matchingLen !== node.key.length) {
226 // keys don't match, fail
227 resolve({ node: null, remaining: keyRemainder, stack })
228 } else {
229 // keys match, continue search
230 walkController.allChildren(node, keyProgress)
231 }
232 }
233 }
234
235 // walk trie and process nodes
236 try {
237 await this.walkTrie(this.root, onFound)
238 } catch (error: any) {
239 if (error.message == 'Missing node in DB' && !throwIfMissing) {
240 // pass
241 } else {
242 reject(error)
243 }
244 }
245
246 // Resolve if _walkTrie finishes without finding any nodes
247 resolve({ node: null, remaining: [], stack })
248 })
249 }
250
251 /**
252 * Walks a trie until finished.
253 * @param root
254 * @param onFound - callback to call when a node is found. This schedules new tasks. If no tasks are available, the Promise resolves.
255 * @returns Resolves when finished walking trie.
256 */
257 async walkTrie(root: Buffer, onFound: FoundNodeFunction): Promise<void> {
258 await WalkController.newWalk(onFound, this, root)
259 }
260
261 /**
262 * @hidden
263 * Backwards compatibility
264 * @param root -
265 * @param onFound -
266 */
267 async _walkTrie(root: Buffer, onFound: FoundNodeFunction): Promise<void> {
268 await this.walkTrie(root, onFound)
269 }
270
271 /**
272 * Creates the initial node from an empty tree.
273 * @private
274 */
275 async _createInitialNode(key: Buffer, value: Buffer): Promise<void> {
276 const newNode = new LeafNode(bufferToNibbles(key), value)
277 this.root = newNode.hash()
278 await this.db.put(this.root, newNode.serialize())
279 }
280
281 /**
282 * Retrieves a node from db by hash.
283 */
284 async lookupNode(node: Buffer | Buffer[]): Promise<TrieNode | null> {
285 if (isRawNode(node)) {
286 return decodeRawNode(node as Buffer[])
287 }
288 let value = null
289 let foundNode = null
290 value = await this.db.get(node as Buffer)
291 if (value) {
292 foundNode = decodeNode(value)
293 } else {
294 // Dev note: this error message text is used for error checking in `checkRoot`, `verifyProof`, and `findPath`
295 throw new Error('Missing node in DB')
296 }
297 return foundNode
298 }
299
300 /**
301 * @hidden
302 * Backwards compatibility
303 * @param node The node hash to lookup from the DB
304 */
305 async _lookupNode(node: Buffer | Buffer[]): Promise<TrieNode | null> {
306 return this.lookupNode(node)
307 }
308
309 /**
310 * Updates a node.
311 * @private
312 * @param key
313 * @param value
314 * @param keyRemainder
315 * @param stack
316 */
317 async _updateNode(
318 k: Buffer,
319 value: Buffer,
320 keyRemainder: Nibbles,
321 stack: TrieNode[]
322 ): Promise<void> {
323 const toSave: BatchDBOp[] = []
324 const lastNode = stack.pop()
325 if (!lastNode) {
326 throw new Error('Stack underflow')
327 }
328
329 // add the new nodes
330 const key = bufferToNibbles(k)
331
332 // Check if the last node is a leaf and the key matches to this
333 let matchLeaf = false
334
335 if (lastNode instanceof LeafNode) {
336 let l = 0
337 for (let i = 0; i < stack.length; i++) {
338 const n = stack[i]
339 if (n instanceof BranchNode) {
340 l++
341 } else {
342 l += n.key.length
343 }
344 }
345
346 if (
347 matchingNibbleLength(lastNode.key, key.slice(l)) === lastNode.key.length &&
348 keyRemainder.length === 0
349 ) {
350 matchLeaf = true
351 }
352 }
353
354 if (matchLeaf) {
355 // just updating a found value
356 lastNode.value = value
357 stack.push(lastNode as TrieNode)
358 } else if (lastNode instanceof BranchNode) {
359 stack.push(lastNode)
360 if (keyRemainder.length !== 0) {
361 // add an extension to a branch node
362 keyRemainder.shift()
363 // create a new leaf
364 const newLeaf = new LeafNode(keyRemainder, value)
365 stack.push(newLeaf)
366 } else {
367 lastNode.value = value
368 }
369 } else {
370 // create a branch node
371 const lastKey = lastNode.key
372 const matchingLength = matchingNibbleLength(lastKey, keyRemainder)
373 const newBranchNode = new BranchNode()
374
375 // create a new extension node
376 if (matchingLength !== 0) {
377 const newKey = lastNode.key.slice(0, matchingLength)
378 const newExtNode = new ExtensionNode(newKey, value)
379 stack.push(newExtNode)
380 lastKey.splice(0, matchingLength)
381 keyRemainder.splice(0, matchingLength)
382 }
383
384 stack.push(newBranchNode)
385
386 if (lastKey.length !== 0) {
387 const branchKey = lastKey.shift() as number
388
389 if (lastKey.length !== 0 || lastNode instanceof LeafNode) {
390 // shrinking extension or leaf
391 lastNode.key = lastKey
392 const formattedNode = this._formatNode(lastNode, false, toSave)
393 newBranchNode.setBranch(branchKey, formattedNode as EmbeddedNode)
394 } else {
395 // remove extension or attaching
396 this._formatNode(lastNode, false, toSave, true)
397 newBranchNode.setBranch(branchKey, lastNode.value)
398 }
399 } else {
400 newBranchNode.value = lastNode.value
401 }
402
403 if (keyRemainder.length !== 0) {
404 keyRemainder.shift()
405 // add a leaf node to the new branch node
406 const newLeafNode = new LeafNode(keyRemainder, value)
407 stack.push(newLeafNode)
408 } else {
409 newBranchNode.value = value
410 }
411 }
412
413 await this._saveStack(key, stack, toSave)
414 }
415
416 /**
417 * Deletes a node from the trie.
418 * @private
419 */
420 async _deleteNode(k: Buffer, stack: TrieNode[]): Promise<void> {
421 const processBranchNode = (
422 key: Nibbles,
423 branchKey: number,
424 branchNode: TrieNode,
425 parentNode: TrieNode,
426 stack: TrieNode[]
427 ) => {
428 // branchNode is the node ON the branch node not THE branch node
429 if (!parentNode || parentNode instanceof BranchNode) {
430 // branch->?
431 if (parentNode) {
432 stack.push(parentNode)
433 }
434
435 if (branchNode instanceof BranchNode) {
436 // create an extension node
437 // branch->extension->branch
438 // @ts-ignore
439 const extensionNode = new ExtensionNode([branchKey], null)
440 stack.push(extensionNode)
441 key.push(branchKey)
442 } else {
443 const branchNodeKey = branchNode.key
444 // branch key is an extension or a leaf
445 // branch->(leaf or extension)
446 branchNodeKey.unshift(branchKey)
447 branchNode.key = branchNodeKey.slice(0)
448 key = key.concat(branchNodeKey)
449 }
450 stack.push(branchNode)
451 } else {
452 // parent is an extension
453 let parentKey = parentNode.key
454
455 if (branchNode instanceof BranchNode) {
456 // ext->branch
457 parentKey.push(branchKey)
458 key.push(branchKey)
459 parentNode.key = parentKey
460 stack.push(parentNode)
461 } else {
462 const branchNodeKey = branchNode.key
463 // branch node is an leaf or extension and parent node is an exstention
464 // add two keys together
465 // dont push the parent node
466 branchNodeKey.unshift(branchKey)
467 key = key.concat(branchNodeKey)
468 parentKey = parentKey.concat(branchNodeKey)
469 branchNode.key = parentKey
470 }
471
472 stack.push(branchNode)
473 }
474
475 return key
476 }
477
478 let lastNode = stack.pop() as TrieNode
479 assert(lastNode)
480 let parentNode = stack.pop()
481 const opStack: BatchDBOp[] = []
482
483 let key = bufferToNibbles(k)
484
485 if (!parentNode) {
486 // the root here has to be a leaf.
487 this.root = this.EMPTY_TRIE_ROOT
488 return
489 }
490
491 if (lastNode instanceof BranchNode) {
492 lastNode.value = null
493 } else {
494 // the lastNode has to be a leaf if it's not a branch.
495 // And a leaf's parent, if it has one, must be a branch.
496 if (!(parentNode instanceof BranchNode)) {
497 throw new Error('Expected branch node')
498 }
499 const lastNodeKey = lastNode.key
500 key.splice(key.length - lastNodeKey.length)
501 // delete the value
502 this._formatNode(lastNode, false, opStack, true)
503 parentNode.setBranch(key.pop() as number, null)
504 lastNode = parentNode
505 parentNode = stack.pop()
506 }
507
508 // nodes on the branch
509 // count the number of nodes on the branch
510 const branchNodes: [number, EmbeddedNode][] = lastNode.getChildren()
511
512 // if there is only one branch node left, collapse the branch node
513 if (branchNodes.length === 1) {
514 // add the one remaing branch node to node above it
515 const branchNode = branchNodes[0][1]
516 const branchNodeKey = branchNodes[0][0]
517
518 // look up node
519 const foundNode = await this._lookupNode(branchNode)
520 if (foundNode) {
521 key = processBranchNode(
522 key,
523 branchNodeKey,
524 foundNode as TrieNode,
525 parentNode as TrieNode,
526 stack
527 )
528 await this._saveStack(key, stack, opStack)
529 }
530 } else {
531 // simple removing a leaf and recaluclation the stack
532 if (parentNode) {
533 stack.push(parentNode)
534 }
535
536 stack.push(lastNode)
537 await this._saveStack(key, stack, opStack)
538 }
539 }
540
541 /**
542 * Saves a stack of nodes to the database.
543 * @private
544 * @param key - the key. Should follow the stack
545 * @param stack - a stack of nodes to the value given by the key
546 * @param opStack - a stack of levelup operations to commit at the end of this funciton
547 */
548 async _saveStack(key: Nibbles, stack: TrieNode[], opStack: BatchDBOp[]): Promise<void> {
549 let lastRoot
550
551 // update nodes
552 while (stack.length) {
553 const node = stack.pop() as TrieNode
554 if (node instanceof LeafNode) {
555 key.splice(key.length - node.key.length)
556 } else if (node instanceof ExtensionNode) {
557 key.splice(key.length - node.key.length)
558 if (lastRoot) {
559 node.value = lastRoot
560 }
561 } else if (node instanceof BranchNode) {
562 if (lastRoot) {
563 const branchKey = key.pop()
564 node.setBranch(branchKey!, lastRoot)
565 }
566 }
567 lastRoot = this._formatNode(node, stack.length === 0, opStack) as Buffer
568 }
569
570 if (lastRoot) {
571 this.root = lastRoot
572 }
573
574 await this.db.batch(opStack)
575 }
576
577 /**
578 * Formats node to be saved by `levelup.batch`.
579 * @private
580 * @param node - the node to format.
581 * @param topLevel - if the node is at the top level.
582 * @param opStack - the opStack to push the node's data.
583 * @param remove - whether to remove the node (only used for CheckpointTrie).
584 * @returns The node's hash used as the key or the rawNode.
585 */
586 _formatNode(
587 node: TrieNode,
588 topLevel: boolean,
589 opStack: BatchDBOp[],
590 remove: boolean = false
591 ): Buffer | (EmbeddedNode | null)[] {
592 const rlpNode = node.serialize()
593
594 if (rlpNode.length >= 32 || topLevel) {
595 // Do not use TrieNode.hash() here otherwise serialize()
596 // is applied twice (performance)
597 const hashRoot = keccak(rlpNode)
598
599 if (remove) {
600 if (this._deleteFromDB) {
601 opStack.push({
602 type: 'del',
603 key: hashRoot,
604 })
605 }
606 } else {
607 opStack.push({
608 type: 'put',
609 key: hashRoot,
610 value: rlpNode,
611 })
612 }
613
614 return hashRoot
615 }
616
617 return node.raw()
618 }
619
620 /**
621 * The given hash of operations (key additions or deletions) are executed on the trie
622 * (delete operations are only executed on DB with `deleteFromDB` set to `true`)
623 * @example
624 * const ops = [
625 * { type: 'del', key: Buffer.from('father') }
626 * , { type: 'put', key: Buffer.from('name'), value: Buffer.from('Yuri Irsenovich Kim') }
627 * , { type: 'put', key: Buffer.from('dob'), value: Buffer.from('16 February 1941') }
628 * , { type: 'put', key: Buffer.from('spouse'), value: Buffer.from('Kim Young-sook') }
629 * , { type: 'put', key: Buffer.from('occupation'), value: Buffer.from('Clown') }
630 * ]
631 * await trie.batch(ops)
632 * @param ops
633 */
634 async batch(ops: BatchDBOp[]): Promise<void> {
635 for (const op of ops) {
636 if (op.type === 'put') {
637 if (!op.value) {
638 throw new Error('Invalid batch db operation')
639 }
640 await this.put(op.key, op.value)
641 } else if (op.type === 'del') {
642 await this.del(op.key)
643 }
644 }
645 }
646
647 /**
648 * Saves the nodes from a proof into the trie. If no trie is provided a new one wil be instantiated.
649 * @param proof
650 * @param trie
651 */
652 static async fromProof(proof: Proof, trie?: Trie): Promise<Trie> {
653 const opStack = proof.map((nodeValue) => {
654 return {
655 type: 'put',
656 key: keccak(nodeValue),
657 value: nodeValue,
658 } as PutBatch
659 })
660
661 if (!trie) {
662 trie = new Trie()
663 if (opStack[0]) {
664 trie.root = opStack[0].key
665 }
666 }
667
668 await trie.db.batch(opStack)
669 return trie
670 }
671
672 /**
673 * prove has been renamed to {@link Trie.createProof}.
674 * @deprecated
675 * @param trie
676 * @param key
677 */
678 static async prove(trie: Trie, key: Buffer): Promise<Proof> {
679 return this.createProof(trie, key)
680 }
681
682 /**
683 * Creates a proof from a trie and key that can be verified using {@link Trie.verifyProof}.
684 * @param trie
685 * @param key
686 */
687 static async createProof(trie: Trie, key: Buffer): Promise<Proof> {
688 const { stack } = await trie.findPath(key)
689 const p = stack.map((stackElem) => {
690 return stackElem.serialize()
691 })
692 return p
693 }
694
695 /**
696 * Verifies a proof.
697 * @param rootHash
698 * @param key
699 * @param proof
700 * @throws If proof is found to be invalid.
701 * @returns The value from the key, or null if valid proof of non-existence.
702 */
703 static async verifyProof(rootHash: Buffer, key: Buffer, proof: Proof): Promise<Buffer | null> {
704 let proofTrie = new Trie(null, rootHash)
705 try {
706 proofTrie = await Trie.fromProof(proof, proofTrie)
707 } catch (e: any) {
708 throw new Error('Invalid proof nodes given')
709 }
710 try {
711 const value = await proofTrie.get(key, true)
712 return value
713 } catch (err: any) {
714 if (err.message == 'Missing node in DB') {
715 throw new Error('Invalid proof provided')
716 } else {
717 throw err
718 }
719 }
720 }
721
722 /**
723 * The `data` event is given an `Object` that has two properties; the `key` and the `value`. Both should be Buffers.
724 * @return Returns a [stream](https://nodejs.org/dist/latest-v12.x/docs/api/stream.html#stream_class_stream_readable) of the contents of the `trie`
725 */
726 createReadStream(): ReadStream {
727 return new ReadStream(this)
728 }
729
730 /**
731 * Creates a new trie backed by the same db.
732 */
733 copy(): Trie {
734 const db = this.db.copy()
735 return new Trie(db._leveldb, this.root)
736 }
737
738 /**
739 * Finds all nodes that are stored directly in the db
740 * (some nodes are stored raw inside other nodes)
741 * called by {@link ScratchReadStream}
742 * @private
743 */
744 async _findDbNodes(onFound: FoundNodeFunction): Promise<void> {
745 const outerOnFound: FoundNodeFunction = async (nodeRef, node, key, walkController) => {
746 if (isRawNode(nodeRef)) {
747 if (node !== null) {
748 walkController.allChildren(node, key)
749 }
750 } else {
751 onFound(nodeRef, node, key, walkController)
752 }
753 }
754 await this.walkTrie(this.root, outerOnFound)
755 }
756
757 /**
758 * Finds all nodes that store k,v values
759 * called by {@link TrieReadStream}
760 * @private
761 */
762 async _findValueNodes(onFound: FoundNodeFunction): Promise<void> {
763 const outerOnFound: FoundNodeFunction = async (nodeRef, node, key, walkController) => {
764 let fullKey = key
765
766 if (node instanceof LeafNode) {
767 fullKey = key.concat(node.key)
768 // found leaf node!
769 onFound(nodeRef, node, fullKey, walkController)
770 } else if (node instanceof BranchNode && node.value) {
771 // found branch with value
772 onFound(nodeRef, node, fullKey, walkController)
773 } else {
774 // keep looking for value nodes
775 if (node !== null) {
776 walkController.allChildren(node, key)
777 }
778 }
779 }
780 await this.walkTrie(this.root, outerOnFound)
781 }
782}