UNPKG

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