diff --git a/.gitignore b/.gitignore index f58cb27..5f9a88d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,6 @@ proof.js readStream.js secure-interface.js secure.js +scratchReadStream.js trieNode.js util.js diff --git a/docs/index.md b/docs/index.md index 398614c..b4fd83c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,7 +10,7 @@ You can create a secure Trie where the keys are automatically hashed using **SHA ## Trie -[src/baseTrie.js:27-55][2] +[src/baseTrie.js:25-781][2] Use `require('merkel-patricia-tree')` for the base interface. In Ethereum applications stick with the Secure Trie Overlay `require('merkel-patricia-tree/secure')`. The API for the raw and the secure interface are about the same @@ -27,7 +27,7 @@ Use `require('merkel-patricia-tree')` for the base interface. In Ethereum applic ### get -[src/baseTrie.js:64-77][9] +[src/baseTrie.js:65-77][9] Gets a value given a `key` @@ -38,7 +38,7 @@ Gets a value given a `key` ### put -[src/baseTrie.js:87-113][11] +[src/baseTrie.js:87-111][11] Stores a given `value` at the given `key` @@ -50,7 +50,7 @@ Stores a given `value` at the given `key` ### del -[src/baseTrie.js:122-140][12] +[src/baseTrie.js:120-136][12] deletes a value given a `key` @@ -61,7 +61,7 @@ deletes a value given a `key` ### getRaw -[src/baseTrie.js:149-165][13] +[src/baseTrie.js:145-162][13] Retrieves a raw value in the underlying db @@ -72,7 +72,7 @@ Retrieves a raw value in the underlying db ### putRaw -[src/baseTrie.js:205-205][14] +[src/baseTrie.js:192-201][14] Writes a value directly to the underlining db @@ -84,7 +84,7 @@ Writes a value directly to the underlining db ### delRaw -[src/baseTrie.js:214-221][15] +[src/baseTrie.js:210-218][15] Removes a raw value in the underlying db @@ -95,9 +95,9 @@ Removes a raw value in the underlying db ### findPath -[src/baseTrie.js:256-304][16] +[src/baseTrie.js:252-298][16] -Trys to find a path to the node for the given key +Tries to find a path to the node for the given key It returns a `stack` of nodes to the closet node #### Parameters @@ -111,7 +111,7 @@ It returns a `stack` of nodes to the closet node ### createReadStream -[src/baseTrie.js:723-725][17] +[src/baseTrie.js:730-732][17] The `data` event is given an `Object` hat has two properties; the `key` and the `value`. Both should be Buffers. @@ -119,7 +119,7 @@ Returns **[stream.Readable][18]** Returns a [stream][19] of the contents of the ### batch -[src/baseTrie.js:749-761][20] +[src/baseTrie.js:756-766][20] The given hash of operations (key additions or deletions) are executed on the DB @@ -143,7 +143,7 @@ trie.batch(ops) ### checkRoot -[src/baseTrie.js:770-775][22] +[src/baseTrie.js:775-780][22] Checks if a given root exists @@ -232,9 +232,9 @@ Compare two 'nibble array' keys - `keyA` - `keyB` -[1]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/secure.js#L10-L15 "Source code on GitHub" +[1]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/secure.js#L10-L15 "Source code on GitHub" -[2]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L27-L55 "Source code on GitHub" +[2]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L25-L781 "Source code on GitHub" [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object @@ -248,44 +248,44 @@ Compare two 'nibble array' keys [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean -[9]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L64-L77 "Source code on GitHub" +[9]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L65-L77 "Source code on GitHub" [10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function -[11]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L87-L113 "Source code on GitHub" +[11]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L87-L111 "Source code on GitHub" -[12]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L122-L140 "Source code on GitHub" +[12]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L120-L136 "Source code on GitHub" -[13]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L149-L165 "Source code on GitHub" +[13]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L145-L162 "Source code on GitHub" -[14]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L205-L205 "Source code on GitHub" +[14]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L192-L201 "Source code on GitHub" -[15]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L214-L221 "Source code on GitHub" +[15]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L210-L218 "Source code on GitHub" -[16]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L256-L304 "Source code on GitHub" +[16]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L252-L298 "Source code on GitHub" -[17]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L723-L725 "Source code on GitHub" +[17]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L730-L732 "Source code on GitHub" [18]: https://nodejs.org/api/stream.html#stream_class_stream_readable [19]: https://nodejs.org/dist/latest-v5.x/docs/api/stream.html#stream_class_stream_readable -[20]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L749-L761 "Source code on GitHub" +[20]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L756-L766 "Source code on GitHub" [21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array -[22]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/baseTrie.js#L770-L775 "Source code on GitHub" +[22]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/baseTrie.js#L775-L780 "Source code on GitHub" -[23]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/proof.js#L12-L29 "Source code on GitHub" +[23]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/proof.js#L12-L29 "Source code on GitHub" [24]: #trie -[25]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/proof.js#L39-L100 "Source code on GitHub" +[25]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/proof.js#L39-L100 "Source code on GitHub" -[26]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/trieNode.js#L164-L179 "Source code on GitHub" +[26]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/trieNode.js#L164-L179 "Source code on GitHub" -[27]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/util.js#L63-L79 "Source code on GitHub" +[27]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/util.js#L63-L79 "Source code on GitHub" -[28]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/util.js#L37-L57 "Source code on GitHub" +[28]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/util.js#L37-L57 "Source code on GitHub" -[29]: https://github.com/alextsg/merkle-patricia-tree/blob/e6c8107eb75c70bc8ea39ca334b309db9a34edce/src/util.js#L28-L31 "Source code on GitHub" +[29]: https://github.com/alextsg/merkle-patricia-tree/blob/c2da4c34d8001120dfc08aa45e2a9894a7927aab/src/util.js#L28-L31 "Source code on GitHub" diff --git a/src/baseTrie.js b/src/baseTrie.js index c6bef05..d9502d2 100644 --- a/src/baseTrie.js +++ b/src/baseTrie.js @@ -12,8 +12,6 @@ const doKeysMatch = require('./util').doKeysMatch const callTogether = require('./util').callTogether const asyncFirstSeries = require('./util').asyncFirstSeries -module.exports = Trie - /** * Use `require('merkel-patricia-tree')` for the base interface. In Ethereum applications stick with the Secure Trie Overlay `require('merkel-patricia-tree/secure')`. The API for the raw and the secure interface are about the same * @class Trie @@ -24,752 +22,760 @@ module.exports = Trie * @prop {Boolean} isCheckpoint determines if you are saving to a checkpoint or directly to the db * @prop {Buffer} EMPTY_TRIE_ROOT the Root for an empty trie */ -function Trie (db, root) { - var self = this - this.EMPTY_TRIE_ROOT = ethUtil.SHA3_RLP - this.sem = semaphore(1) - - // setup dbs - this.db = db || level() - - this._getDBs = [this.db] - this._putDBs = [this.db] - - Object.defineProperty(this, 'root', { - set: function (value) { - if (value) { - value = ethUtil.toBuffer(value) - assert(value.length === 32, 'Invalid root length. Roots are 32 bytes') - } else { - value = self.EMPTY_TRIE_ROOT - } - - this._root = value - }, - get: function () { - return this._root - } - }) - - this.root = root -} - -/** - * Gets a value given a `key` - * @method get - * @memberof Trie - * @param {Buffer|String} key - the key to search for - * @param {Function} cb A callback `Function` which is given the arguments `err` - for errors that may have occured and `value` - the found value in a `Buffer` or if no value was found `null` - */ -Trie.prototype.get = function (key, cb) { - var self = this - - key = ethUtil.toBuffer(key) - - self.findPath(key, function (err, node, remainder, stack) { - var value = null - if (node && remainder.length === 0) { - value = node.value - } - - cb(err, value) - }) -} +module.exports = class Trie { + constructor (db, root) { + const self = this + this.EMPTY_TRIE_ROOT = ethUtil.SHA3_RLP + this.sem = semaphore(1) + + // setup dbs + this.db = db || level() + + this._getDBs = [this.db] + this._putDBs = [this.db] + + Object.defineProperty(this, 'root', { + set(value) { + if (value) { + value = ethUtil.toBuffer(value) + assert(value.length === 32, 'Invalid root length. Roots are 32 bytes') + } else { + value = self.EMPTY_TRIE_ROOT + } -/** - * Stores a given `value` at the given `key` - * @method put - * @memberof Trie - * @param {Buffer|String} key - * @param {Buffer|String} Value - * @param {Function} cb A callback `Function` which is given the argument `err` - for errors that may have occured - */ -Trie.prototype.put = function (key, value, cb) { - var self = this - - key = ethUtil.toBuffer(key) - value = ethUtil.toBuffer(value) - - if (!value || value.toString() === '') { - self.del(key, cb) - } else { - cb = callTogether(cb, self.sem.leave) - - self.sem.take(function () { - if (self.root.toString('hex') !== ethUtil.SHA3_RLP.toString('hex')) { - // first try to find the give key or its nearst node - self.findPath(key, function (err, foundValue, keyRemainder, stack) { - if (err) { - return cb(err) - } - // then update - self._updateNode(key, value, keyRemainder, stack, cb) - }) - } else { - self._createInitialNode(key, value, cb) // if no root initialize this trie + this._root = value + }, + get() { + return this._root } }) - } -} -/** - * deletes a value given a `key` - * @method del - * @memberof Trie - * @param {Buffer|String} key - * @param {Function} callback the callback `Function` - */ -Trie.prototype.del = function (key, cb) { - var self = this + this.root = root - key = ethUtil.toBuffer(key) - cb = callTogether(cb, self.sem.leave) + this.putRaw = this._putRaw + } - self.sem.take(function () { - self.findPath(key, function (err, foundValue, keyRemainder, stack) { - if (err) { - return cb(err) - } - if (foundValue) { - self._deleteNode(key, stack, cb) - } else { - cb() + /** + * Gets a value given a `key` + * @method get + * @memberof Trie + * @param {Buffer|String} key - the key to search for + * @param {Function} cb A callback `Function` which is given the arguments `err` - for errors that may have occured and `value` - the found value in a `Buffer` or if no value was found `null` + */ + get (key, cb) { + key = ethUtil.toBuffer(key) + + this.findPath(key, (err, node, remainder, stack) => { + let value = null + + if (node && remainder.length === 0) { + value = node.value } - }) - }) -} -/** - * Retrieves a raw value in the underlying db - * @method getRaw - * @memberof Trie - * @param {Buffer} key - * @param {Function} callback A callback `Function`, which is given the arguments `err` - for errors that may have occured and `value` - the found value in a `Buffer` or if no value was found `null`. - */ -Trie.prototype.getRaw = function (key, cb) { - key = ethUtil.toBuffer(key) - - function dbGet (db, cb2) { - db.get(key, { - keyEncoding: 'binary', - valueEncoding: 'binary' - }, function (err, foundNode) { - if (err || !foundNode) { - cb2(null, null) - } else { - cb2(null, foundNode) - } + cb(err, value) }) } - asyncFirstSeries(this._getDBs, dbGet, cb) -} -// retrieves a node from dbs by hash -Trie.prototype._lookupNode = function (node, cb) { - if (TrieNode.isRawNode(node)) { - cb(new TrieNode(node)) - } else { - this.getRaw(node, function (err, value) { - if (err) { - throw err - } - - if (value) { - value = new TrieNode(rlp.decode(value)) - } + /** + * Stores a given `value` at the given `key` + * @method put + * @memberof Trie + * @param {Buffer|String} key + * @param {Buffer|String} Value + * @param {Function} cb A callback `Function` which is given the argument `err` - for errors that may have occured + */ + put (key, value, cb) { + key = ethUtil.toBuffer(key) + value = ethUtil.toBuffer(value) + + if (!value || value.toString() === '') { + this.del(key, cb) + } else { + cb = callTogether(cb, this.sem.leave) + + this.sem.take(() => { + if (this.root.toString('hex') !== ethUtil.SHA3_RLP.toString('hex')) { + // first try to find the give key or its nearst node + this.findPath(key, (err, foundValue, keyRemainder, stack) => { + if (err) { + return cb(err) + } + // then update + this._updateNode(key, value, keyRemainder, stack, cb) + }) + } else { + this._createInitialNode(key, value, cb) // if no root initialize this trie + } + }) + } + } - cb(value) + /** + * deletes a value given a `key` + * @method del + * @memberof Trie + * @param {Buffer|String} key + * @param {Function} callback the callback `Function` + */ + del (key, cb) { + key = ethUtil.toBuffer(key) + cb = callTogether(cb, this.sem.leave) + + this.sem.take(() => { + this.findPath(key, (err, foundValue, keyRemainder, stack) => { + if (err) { + return cb(err) + } + if (foundValue) { + this._deleteNode(key, stack, cb) + } else { + cb() + } + }) }) } -} -// TODO: remove the proxy method when changing the caching -Trie.prototype._putRaw = function (key, val, cb) { - function dbPut (db, cb2) { - db.put(key, val, { - keyEncoding: 'binary', - valueEncoding: 'binary' - }, cb2) + /** + * Retrieves a raw value in the underlying db + * @method getRaw + * @memberof Trie + * @param {Buffer} key + * @param {Function} callback A callback `Function`, which is given the arguments `err` - for errors that may have occured and `value` - the found value in a `Buffer` or if no value was found `null`. + */ + getRaw (key, cb) { + key = ethUtil.toBuffer(key) + + function dbGet (db, cb2) { + db.get(key, { + keyEncoding: 'binary', + valueEncoding: 'binary' + }, (err, foundNode) => { + if (err || !foundNode) { + cb2(null, null) + } else { + cb2(null, foundNode) + } + }) + } + + asyncFirstSeries(this._getDBs, dbGet, cb) } - async.each(this._putDBs, dbPut, cb) -} -/** - * Writes a value directly to the underlining db - * @method putRaw - * @memberof Trie - * @param {Buffer|String} key The key as a `Buffer` or `String` - * @param {Buffer} value The value to be stored - * @param {Function} callback A callback `Function`, which is given the argument `err` - for errors that may have occured - */ -Trie.prototype.putRaw = Trie.prototype._putRaw + // retrieves a node from dbs by hash + _lookupNode (node, cb) { + if (TrieNode.isRawNode(node)) { + cb(new TrieNode(node)) + } else { + this.getRaw(node, (err, value) => { + if (err) { + throw err + } -/** - * Removes a raw value in the underlying db - * @method delRaw - * @memberof Trie - * @param {Buffer|String} key - * @param {Function} callback A callback `Function`, which is given the argument `err` - for errors that may have occured - */ -Trie.prototype.delRaw = function (key, cb) { - function del (db, cb2) { - db.del(key, { - keyEncoding: 'binary' - }, cb2) + if (value) { + value = new TrieNode(rlp.decode(value)) + } + + cb(value) + }) + } } - async.each(this._putDBs, del, cb) -} -// writes a single node to dbs -Trie.prototype._putNode = function (node, cb) { - var hash = node.hash() - var serialized = node.serialize() - this._putRaw(hash, serialized, cb) -} + /** + * Writes a value directly to the underlining db + * @method putRaw + * @memberof Trie + * @param {Buffer|String} key The key as a `Buffer` or `String` + * @param {Buffer} value The value to be stored + * @param {Function} callback A callback `Function`, which is given the argument `err` - for errors that may have occured + */ + // TODO: remove the proxy method when changing the caching + _putRaw (key, val, cb) { + function dbPut (db, cb2) { + db.put(key, val, { + keyEncoding: 'binary', + valueEncoding: 'binary' + }, cb2) + } -// writes many nodes to db -Trie.prototype._batchNodes = function (opStack, cb) { - function dbBatch (db, cb) { - db.batch(opStack, { - keyEncoding: 'binary', - valueEncoding: 'binary' - }, cb) + async.each(this._putDBs, dbPut, cb) } - async.each(this._putDBs, dbBatch, cb) -} - -/** - * Trys to find a path to the node for the given key - * It returns a `stack` of nodes to the closet node - * @method findPath - * @memberof Trie - * @param {String|Buffer} - key - the search key - * @param {Function} - cb - the callback function. Its is given the following - * arguments - * - err - any errors encontered - * - node - the last node found - * - keyRemainder - the remaining key nibbles not accounted for - * - stack - an array of nodes that forms the path to node we are searching for - */ + /** + * Removes a raw value in the underlying db + * @method delRaw + * @memberof Trie + * @param {Buffer|String} key + * @param {Function} callback A callback `Function`, which is given the argument `err` - for errors that may have occured + */ + delRaw (key, cb) { + function del (db, cb2) { + db.del(key, { + keyEncoding: 'binary' + }, cb2) + } -Trie.prototype.findPath = function (targetKey, cb) { - var self = this - var root = self.root - var stack = [] - targetKey = TrieNode.stringToNibbles(targetKey) + async.each(this._putDBs, del, cb) + } - this._walkTrie(root, processNode, cb) + // writes a single node to dbs + _putNode (node, cb) { + const hash = node.hash() + const serialized = node.serialize() + this._putRaw(hash, serialized, cb) + } - function processNode (nodeRef, node, keyProgress, walkController) { - var nodeKey = node.key || [] - var keyRemainder = targetKey.slice(matchingNibbleLength(keyProgress, targetKey)) - var matchingLen = matchingNibbleLength(keyRemainder, nodeKey) + // writes many nodes to db + _batchNodes (opStack, cb) { + function dbBatch (db, cb) { + db.batch(opStack, { + keyEncoding: 'binary', + valueEncoding: 'binary' + }, cb) + } - stack.push(node) + async.each(this._putDBs, dbBatch, cb) + } - if (node.type === 'branch') { - if (keyRemainder.length === 0) { - walkController.return(null, node, [], stack) - // we exhausted the key without finding a node - } else { - var branchIndex = keyRemainder[0] - var branchNode = node.getValue(branchIndex) - if (!branchNode) { - // there are no more nodes to find and we didn't find the key + /** + * Tries to find a path to the node for the given key + * It returns a `stack` of nodes to the closet node + * @method findPath + * @memberof Trie + * @param {String|Buffer} - key - the search key + * @param {Function} - cb - the callback function. Its is given the following + * arguments + * - err - any errors encontered + * - node - the last node found + * - keyRemainder - the remaining key nibbles not accounted for + * - stack - an array of nodes that forms the path to node we are searching for + */ + findPath (targetKey, cb) { + const stack = [] + targetKey = TrieNode.stringToNibbles(targetKey) + + this._walkTrie(this.root, processNode, cb) + + function processNode (nodeRef, node, keyProgress, walkController) { + const nodeKey = node.key || [] + const keyRemainder = targetKey.slice(matchingNibbleLength(keyProgress, targetKey)) + const matchingLen = matchingNibbleLength(keyRemainder, nodeKey) + + stack.push(node) + + if (node.type === 'branch') { + if (keyRemainder.length === 0) { + walkController.return(null, node, [], stack) + // we exhausted the key without finding a node + } else { + const branchIndex = keyRemainder[0] + const branchNode = node.getValue(branchIndex) + if (!branchNode) { + // there are no more nodes to find and we didn't find the key + walkController.return(null, null, keyRemainder, stack) + } else { + // node found, continuing search + walkController.only(branchIndex) + } + } + } else if (node.type === 'leaf') { + if (doKeysMatch(keyRemainder, nodeKey)) { + // keys match, return node with empty key + walkController.return(null, node, [], stack) + } else { + // reached leaf but keys dont match + walkController.return(null, null, keyRemainder, stack) + } + } else if (node.type === 'extention') { + if (matchingLen !== nodeKey.length) { + // keys dont match, fail walkController.return(null, null, keyRemainder, stack) } else { - // node found, continuing search - walkController.only(branchIndex) + // keys match, continue search + walkController.next() } } - } else if (node.type === 'leaf') { - if (doKeysMatch(keyRemainder, nodeKey)) { - // keys match, return node with empty key - walkController.return(null, node, [], stack) - } else { - // reached leaf but keys dont match - walkController.return(null, null, keyRemainder, stack) + } + } + + /* + * Finds all nodes that store k,v values + */ + _findNode (key, root, stack, cb) { + this.findPath(key, () => { + cb.apply(null, arguments) + }) + } + + /* + * Finds all nodes that store k,v values + */ + _findValueNodes (onFound, cb) { + this._walkTrie(this.root, (nodeRef, node, key, walkController) => { + let fullKey = key + + if (node.key) { + fullKey = key.concat(node.key) } - } else if (node.type === 'extention') { - if (matchingLen !== nodeKey.length) { - // keys dont match, fail - walkController.return(null, null, keyRemainder, stack) + + if (node.type === 'leaf') { + // found leaf node! + onFound(nodeRef, node, fullKey, walkController.next) + } else if (node.type === 'branch' && node.value) { + // found branch with value + onFound(nodeRef, node, fullKey, walkController.next) } else { - // keys match, continue search + // keep looking for value nodes walkController.next() } - } + }, cb) } -} -/* - * Finds all nodes that store k,v values - */ -Trie.prototype._findNode = function (key, root, stack, cb) { - this.findPath(key, function () { - cb.apply(null, arguments) - }) -} + /* + * Finds all nodes that are stored directly in the db + * (some nodes are stored raw inside other nodes) + */ + _findDbNodes (onFound, cb) { + this._walkTrie(this.root, (nodeRef, node, key, walkController) => { + if (TrieNode.isRawNode(nodeRef)) { + walkController.next() + } else { + onFound(nodeRef, node, key, walkController.next) + } + }, cb) + } -/* - * Finds all nodes that store k,v values - */ -Trie.prototype._findValueNodes = function (onFound, cb) { - this._walkTrie(this.root, function (nodeRef, node, key, walkController) { - var fullKey = key + /** + * Updates a node + * @method _updateNode + * @private + * @param {Buffer} key + * @param {Buffer| String} value + * @param {Array} keyRemainder + * @param {Array} stack - + * @param {Function} cb - the callback + */ + _updateNode (key, value, keyRemainder, stack, cb) { + const toSave = [] + const lastNode = stack.pop() + + // add the new nodes + key = TrieNode.stringToNibbles(key) - if (node.key) { - fullKey = key.concat(node.key) - } + // Check if the last node is a leaf and the key matches to this + let matchLeaf = false - if (node.type === 'leaf') { - // found leaf node! - onFound(nodeRef, node, fullKey, walkController.next) - } else if (node.type === 'branch' && node.value) { - // found branch with value - onFound(nodeRef, node, fullKey, walkController.next) - } else { - // keep looking for value nodes - walkController.next() - } - }, cb) -} + if (lastNode.type === 'leaf') { + let l = 0 -/* - * Finds all nodes that are stored directly in the db - * (some nodes are stored raw inside other nodes) - */ -Trie.prototype._findDbNodes = function (onFound, cb) { - this._walkTrie(this.root, function (nodeRef, node, key, walkController) { - if (TrieNode.isRawNode(nodeRef)) { - walkController.next() - } else { - onFound(nodeRef, node, key, walkController.next) - } - }, cb) -} + for (let i = 0; i < stack.length; i++) { + const n = stack[i] -/** - * Updates a node - * @method _updateNode - * @private - * @param {Buffer} key - * @param {Buffer| String} value - * @param {Array} keyRemainder - * @param {Array} stack - - * @param {Function} cb - the callback - */ -Trie.prototype._updateNode = function (key, value, keyRemainder, stack, cb) { - var toSave = [] - var lastNode = stack.pop() - - // add the new nodes - key = TrieNode.stringToNibbles(key) - - // Check if the last node is a leaf and the key matches to this - var matchLeaf = false - if (lastNode.type === 'leaf') { - var l = 0 - for (var i = 0; i < stack.length; i++) { - var n = stack[i] - if (n.type === 'branch') { - l++ - } else { - l += n.key.length + if (n.type === 'branch') { + l++ + } else { + l += n.key.length + } + } + + if ((matchingNibbleLength(lastNode.key, key.slice(l)) === lastNode.key.length) && (keyRemainder.length === 0)) { + matchLeaf = true } } - if ((matchingNibbleLength(lastNode.key, key.slice(l)) === lastNode.key.length) && (keyRemainder.length === 0)) { - matchLeaf = true - } - } - if (matchLeaf) { - // just updating a found value - lastNode.value = value - stack.push(lastNode) - } else if (lastNode.type === 'branch') { - stack.push(lastNode) - if (keyRemainder !== 0) { - // add an extention to a branch node - keyRemainder.shift() - // create a new leaf - var newLeaf = new TrieNode('leaf', keyRemainder, value) - stack.push(newLeaf) - } else { + if (matchLeaf) { + // just updating a found value lastNode.value = value - } - } else { - // create a branch node - var lastKey = lastNode.key - var matchingLength = matchingNibbleLength(lastKey, keyRemainder) - var newBranchNode = new TrieNode('branch') - - // create a new extention node - if (matchingLength !== 0) { - var newKey = lastNode.key.slice(0, matchingLength) - var newExtNode = new TrieNode('extention', newKey, value) - stack.push(newExtNode) - lastKey.splice(0, matchingLength) - keyRemainder.splice(0, matchingLength) - } + stack.push(lastNode) + } else if (lastNode.type === 'branch') { + stack.push(lastNode) + if (keyRemainder !== 0) { + // add an extention to a branch node + keyRemainder.shift() + // create a new leaf + const newLeaf = new TrieNode('leaf', keyRemainder, value) + stack.push(newLeaf) + } else { + lastNode.value = value + } + } else { + // create a branch node + const lastKey = lastNode.key + const matchingLength = matchingNibbleLength(lastKey, keyRemainder) + const newBranchNode = new TrieNode('branch') + + // create a new extention node + if (matchingLength !== 0) { + const newKey = lastNode.key.slice(0, matchingLength) + const newExtNode = new TrieNode('extention', newKey, value) + stack.push(newExtNode) + lastKey.splice(0, matchingLength) + keyRemainder.splice(0, matchingLength) + } + + stack.push(newBranchNode) - stack.push(newBranchNode) + if (lastKey.length !== 0) { + const branchKey = lastKey.shift() - if (lastKey.length !== 0) { - var branchKey = lastKey.shift() - if (lastKey.length !== 0 || lastNode.type === 'leaf') { - // shriking extention or leaf - lastNode.key = lastKey - var formatedNode = this._formatNode(lastNode, false, toSave) - newBranchNode.setValue(branchKey, formatedNode) + if (lastKey.length !== 0 || lastNode.type === 'leaf') { + // shriking extention or leaf + lastNode.key = lastKey + const formatedNode = this._formatNode(lastNode, false, toSave) + newBranchNode.setValue(branchKey, formatedNode) + } else { + // remove extention or attaching + this._formatNode(lastNode, false, true, toSave) + newBranchNode.setValue(branchKey, lastNode.value) + } } else { - // remove extention or attaching - this._formatNode(lastNode, false, true, toSave) - newBranchNode.setValue(branchKey, lastNode.value) + newBranchNode.value = lastNode.value } - } else { - newBranchNode.value = lastNode.value - } - if (keyRemainder.length !== 0) { - keyRemainder.shift() - // add a leaf node to the new branch node - var newLeafNode = new TrieNode('leaf', keyRemainder, value) - stack.push(newLeafNode) - } else { - newBranchNode.value = value + if (keyRemainder.length !== 0) { + keyRemainder.shift() + // add a leaf node to the new branch node + const newLeafNode = new TrieNode('leaf', keyRemainder, value) + stack.push(newLeafNode) + } else { + newBranchNode.value = value + } } + + this._saveStack(key, stack, toSave, cb) } - this._saveStack(key, stack, toSave, cb) -} + // walk tree + _walkTrie (root, onNode, onDone) { + const self = this + root = root || this.root + onDone = onDone || function () {} + let aborted = false + let returnValues = [] -// walk tree + if (root.toString('hex') === ethUtil.SHA3_RLP.toString('hex')) { + return onDone() + } -Trie.prototype._walkTrie = function (root, onNode, onDone) { - var self = this - root = root || self.root - onDone = onDone || function () {} - var aborted = false - var returnValues = [] + this._lookupNode(root, node => { + processNode(root, node, null, err => { + if (err) { + return onDone(err) + } - if (root.toString('hex') === ethUtil.SHA3_RLP.toString('hex')) { - return onDone() - } + onDone.apply(null, returnValues) + }) + }) + + // the maximum pool size should be high enough to utilise the parallelizability of reading nodes from disk and + // low enough to utilize the prioritisation of node lookup. + const maxPoolSize = 500 + const taskExecutor = new PrioritizedTaskExecutor(maxPoolSize) - self._lookupNode(root, function (node) { - processNode(root, node, null, function (err) { - if (err) { - return onDone(err) + function processNode (nodeRef, node, key, cb) { + if (!node || aborted) { + return cb() } - onDone.apply(null, returnValues) - }) - }) - - // the maximum pool size should be high enough to utilise the parallelizability of reading nodes from disk and - // low enough to utilize the prioritisation of node lookup. - var maxPoolSize = 500 - var taskExecutor = new PrioritizedTaskExecutor(maxPoolSize) - - function processNode (nodeRef, node, key, cb) { - if (!node) return cb() - if (aborted) return cb() - var stopped = false - key = key || [] - - var walkController = { - stop: function () { - stopped = true - cb() - }, - // end all traversal and return values to the onDone cb - return: function () { - aborted = true - returnValues = arguments - cb() - }, - next: function () { - if (aborted) { - return cb() - } - if (stopped) { - return cb() - } - var children = node.getChildren() - async.forEachOf(children, function (childData, index, cb) { - var keyExtension = childData[0] - var childRef = childData[1] - var childKey = key.concat(keyExtension) - var priority = childKey.length - taskExecutor.execute(priority, function (taskCallback) { - self._lookupNode(childRef, function (childNode) { + + let stopped = false + key = key || [] + + const walkController = { + stop: function () { + stopped = true + cb() + }, + // end all traversal and return values to the onDone cb + return: function () { + aborted = true + returnValues = arguments + cb() + }, + next: function () { + if (aborted || stopped) { + return cb() + } + + const children = node.getChildren() + async.forEachOf(children, (childData, index, cb) => { + const keyExtension = childData[0] + const childRef = childData[1] + const childKey = key.concat(keyExtension) + const priority = childKey.length + taskExecutor.execute(priority, taskCallback => { + self._lookupNode(childRef, childNode => { + taskCallback() + processNode(childRef, childNode, childKey, cb) + }) + }) + }, cb) + }, + only: function (childIndex) { + const childRef = node.getValue(childIndex) + const childKey = key.slice() + childKey.push(childIndex) + const priority = childKey.length + taskExecutor.execute(priority, taskCallback => { + self._lookupNode(childRef, childNode => { taskCallback() processNode(childRef, childNode, childKey, cb) }) }) - }, cb) - }, - only: function (childIndex) { - var childRef = node.getValue(childIndex) - var childKey = key.slice() - childKey.push(childIndex) - var priority = childKey.length - taskExecutor.execute(priority, function (taskCallback) { - self._lookupNode(childRef, function (childNode) { - taskCallback() - processNode(childRef, childNode, childKey, cb) - }) - }) + } } + + onNode(nodeRef, node, key, walkController) } - onNode(nodeRef, node, key, walkController) } -} -/** - * saves a stack - * @method _saveStack - * @private - * @param {Array} key - the key. Should follow the stack - * @param {Array} stack - a stack of nodes to the value given by the key - * @param {Array} opStack - a stack of levelup operations to commit at the end of this funciton - * @param {Function} cb - */ -Trie.prototype._saveStack = function (key, stack, opStack, cb) { - var lastRoot - - // update nodes - while (stack.length) { - var node = stack.pop() - if (node.type === 'leaf') { - key.splice(key.length - node.key.length) - } else if (node.type === 'extention') { - key.splice(key.length - node.key.length) - if (lastRoot) { - node.value = lastRoot - } - } else if (node.type === 'branch') { - if (lastRoot) { - var branchKey = key.pop() - node.setValue(branchKey, lastRoot) + /** + * saves a stack + * @method _saveStack + * @private + * @param {Array} key - the key. Should follow the stack + * @param {Array} stack - a stack of nodes to the value given by the key + * @param {Array} opStack - a stack of levelup operations to commit at the end of this funciton + * @param {Function} cb + */ + _saveStack (key, stack, opStack, cb) { + let lastRoot + + // update nodes + while (stack.length) { + const node = stack.pop() + if (node.type === 'leaf') { + key.splice(key.length - node.key.length) + } else if (node.type === 'extention') { + key.splice(key.length - node.key.length) + if (lastRoot) { + node.value = lastRoot + } + } else if (node.type === 'branch') { + if (lastRoot) { + const branchKey = key.pop() + node.setValue(branchKey, lastRoot) + } } + lastRoot = this._formatNode(node, stack.length === 0, opStack) } - lastRoot = this._formatNode(node, stack.length === 0, opStack) - } - if (lastRoot) { - this.root = lastRoot + if (lastRoot) { + this.root = lastRoot + } + + this._batchNodes(opStack, cb) } - this._batchNodes(opStack, cb) -} + _deleteNode (key, stack, cb) { + function processBranchNode (key, branchKey, branchNode, parentNode, stack) { + // branchNode is the node ON the branch node not THE branch node + const branchNodeKey = branchNode.key + if (!parentNode || parentNode.type === 'branch') { + // branch->? + if (parentNode) { + stack.push(parentNode) + } -Trie.prototype._deleteNode = function (key, stack, cb) { - function processBranchNode (key, branchKey, branchNode, parentNode, stack) { - // branchNode is the node ON the branch node not THE branch node - var branchNodeKey = branchNode.key - if (!parentNode || parentNode.type === 'branch') { - // branch->? - if (parentNode) { - stack.push(parentNode) + if (branchNode.type === 'branch') { + // create an extention node + // branch->extention->branch + const extentionNode = new TrieNode('extention', [branchKey], null) + stack.push(extentionNode) + key.push(branchKey) + } else { + // branch key is an extention or a leaf + // branch->(leaf or extention) + branchNodeKey.unshift(branchKey) + branchNode.key = branchNodeKey + + // hackery. This is equvilant to array.concat except we need keep the + // rerfance to the `key` that was passed in. + branchNodeKey.unshift(0) + branchNodeKey.unshift(key.length) + key.splice.apply(key, branchNodeKey) + } + stack.push(branchNode) + } else { + // parent is a extention + let parentKey = parentNode.key + + if (branchNode.type === 'branch') { + // ext->branch + parentKey.push(branchKey) + key.push(branchKey) + parentNode.key = parentKey + stack.push(parentNode) + } else { + // branch node is an leaf or extention and parent node is an exstention + // add two keys together + // dont push the parent node + branchNodeKey.unshift(branchKey) + key = key.concat(branchNodeKey) + parentKey = parentKey.concat(branchNodeKey) + branchNode.key = parentKey + } + + stack.push(branchNode) } - if (branchNode.type === 'branch') { - // create an extention node - // branch->extention->branch - var extentionNode = new TrieNode('extention', [branchKey], null) - stack.push(extentionNode) - key.push(branchKey) + return key + } + + let lastNode = stack.pop() + let parentNode = stack.pop() + const opStack = [] + + if (!Array.isArray(key)) { + // convert key to nibbles + key = TrieNode.stringToNibbles(key) + } + + if (!parentNode) { + // the root here has to be a leaf. + this.root = this.EMPTY_TRIE_ROOT + cb() + } else { + if (lastNode.type === 'branch') { + lastNode.value = null } else { - // branch key is an extention or a leaf - // branch->(leaf or extention) - branchNodeKey.unshift(branchKey) - branchNode.key = branchNodeKey - - // hackery. This is equvilant to array.concat except we need keep the - // rerfance to the `key` that was passed in. - branchNodeKey.unshift(0) - branchNodeKey.unshift(key.length) - key.splice.apply(key, branchNodeKey) + // the lastNode has to be a leaf if its not a branch. And a leaf's parent + // if it has one must be a branch. + const lastNodeKey = lastNode.key + key.splice(key.length - lastNodeKey.length) + // delete the value + this._formatNode(lastNode, false, true, opStack) + parentNode.setValue(key.pop(), null) + lastNode = parentNode + parentNode = stack.pop() } - stack.push(branchNode) - } else { - // parent is a extention - var parentKey = parentNode.key - if (branchNode.type === 'branch') { - // ext->branch - parentKey.push(branchKey) - key.push(branchKey) - parentNode.key = parentKey - stack.push(parentNode) + + // nodes on the branch + const branchNodes = [] + // count the number of nodes on the branch + lastNode.raw.forEach((node, i) => { + const val = lastNode.getValue(i) + + if (val) { + branchNodes.push([i, val]) + } + }) + + // if there is only one branch node left, collapse the branch node + if (branchNodes.length === 1) { + // add the one remaing branch node to node above it + const branchNode = branchNodes[0][1] + const branchNodeKey = branchNodes[0][0] + + // look up node + this._lookupNode(branchNode, foundNode => { + key = processBranchNode(key, branchNodeKey, foundNode, parentNode, stack, opStack) + this._saveStack(key, stack, opStack, cb) + }) } else { - // branch node is an leaf or extention and parent node is an exstention - // add two keys together - // dont push the parent node - branchNodeKey.unshift(branchKey) - key = key.concat(branchNodeKey) - parentKey = parentKey.concat(branchNodeKey) - branchNode.key = parentKey + // simple removing a leaf and recaluclation the stack + if (parentNode) { + stack.push(parentNode) + } + + stack.push(lastNode) + this._saveStack(key, stack, opStack, cb) } - stack.push(branchNode) } - - return key } - var lastNode = stack.pop() - var parentNode = stack.pop() - var opStack = [] - var self = this - - if (!Array.isArray(key)) { - // convert key to nibbles - key = TrieNode.stringToNibbles(key) + // Creates the initial node from an empty tree + _createInitialNode (key, value, cb) { + const newNode = new TrieNode('leaf', key, value) + this.root = newNode.hash() + this._putNode(newNode, cb) } - if (!parentNode) { - // the root here has to be a leaf. - this.root = this.EMPTY_TRIE_ROOT - cb() - } else { - if (lastNode.type === 'branch') { - lastNode.value = null - } else { - // the lastNode has to be a leaf if its not a branch. And a leaf's parent - // if it has one must be a branch. - var lastNodeKey = lastNode.key - key.splice(key.length - lastNodeKey.length) - // delete the value - this._formatNode(lastNode, false, true, opStack) - parentNode.setValue(key.pop(), null) - lastNode = parentNode - parentNode = stack.pop() + // formats node to be saved by levelup.batch. + // returns either the hash that will be used key or the rawNode + _formatNode (node, topLevel, remove, opStack) { + if (arguments.length === 3) { + opStack = remove + remove = false } - // nodes on the branch - var branchNodes = [] - // count the number of nodes on the branch - lastNode.raw.forEach(function (node, i) { - var val = lastNode.getValue(i) - if (val) branchNodes.push([i, val]) - }) + const rlpNode = node.serialize() - // if there is only one branch node left, collapse the branch node - if (branchNodes.length === 1) { - // add the one remaing branch node to node above it - var branchNode = branchNodes[0][1] - var branchNodeKey = branchNodes[0][0] + if (rlpNode.length >= 32 || topLevel) { + const hashRoot = node.hash() - // look up node - this._lookupNode(branchNode, function (foundNode) { - key = processBranchNode(key, branchNodeKey, foundNode, parentNode, stack, opStack) - self._saveStack(key, stack, opStack, cb) - }) - } else { - // simple removing a leaf and recaluclation the stack - if (parentNode) { - stack.push(parentNode) + if (remove && this.isCheckpoint) { + opStack.push({ + type: 'del', + key: hashRoot + }) + } else { + opStack.push({ + type: 'put', + key: hashRoot, + value: rlpNode + }) } - stack.push(lastNode) - self._saveStack(key, stack, opStack, cb) + return hashRoot } - } -} - -// Creates the initial node from an empty tree -Trie.prototype._createInitialNode = function (key, value, cb) { - var newNode = new TrieNode('leaf', key, value) - this.root = newNode.hash() - this._putNode(newNode, cb) -} -// formats node to be saved by levelup.batch. -// returns either the hash that will be used key or the rawNode -Trie.prototype._formatNode = function (node, topLevel, remove, opStack) { - if (arguments.length === 3) { - opStack = remove - remove = false + return node.raw } - var rlpNode = node.serialize() - if (rlpNode.length >= 32 || topLevel) { - var hashRoot = node.hash() - - if (remove && this.isCheckpoint) { - opStack.push({ - type: 'del', - key: hashRoot - }) - } else { - opStack.push({ - type: 'put', - key: hashRoot, - value: rlpNode - }) - } - return hashRoot + /** + * The `data` event is given an `Object` hat has two properties; the `key` and the `value`. Both should be Buffers. + * @method createReadStream + * @memberof Trie + * @return {stream.Readable} Returns a [stream](https://nodejs.org/dist/latest-v5.x/docs/api/stream.html#stream_class_stream_readable) of the contents of the `trie` + */ + createReadStream () { + return new ReadStream(this) } - return node.raw -} -/** - * The `data` event is given an `Object` hat has two properties; the `key` and the `value`. Both should be Buffers. - * @method createReadStream - * @memberof Trie - * @return {stream.Readable} Returns a [stream](https://nodejs.org/dist/latest-v5.x/docs/api/stream.html#stream_class_stream_readable) of the contents of the `trie` - */ -Trie.prototype.createReadStream = function () { - return new ReadStream(this) -} - -// creates a new trie backed by the same db -// and starting at the same root -Trie.prototype.copy = function () { - return new Trie(this.db, this.root) -} + // creates a new trie backed by the same db + // and starting at the same root + copy () { + return new Trie(this.db, this.root) + } -/** - * The given hash of operations (key additions or deletions) are executed on the DB - * @method batch - * @memberof Trie - * @example - * var ops = [ - * { type: 'del', key: 'father' } - * , { type: 'put', key: 'name', value: 'Yuri Irsenovich Kim' } - * , { type: 'put', key: 'dob', value: '16 February 1941' } - * , { type: 'put', key: 'spouse', value: 'Kim Young-sook' } - * , { type: 'put', key: 'occupation', value: 'Clown' } - * ] - * trie.batch(ops) - * @param {Array} ops - * @param {Function} cb - */ -Trie.prototype.batch = function (ops, cb) { - var self = this - - async.eachSeries(ops, function (op, cb2) { - if (op.type === 'put') { - self.put(op.key, op.value, cb2) - } else if (op.type === 'del') { - self.del(op.key, cb2) - } else { - cb2() - } - }, cb) -} + /** + * The given hash of operations (key additions or deletions) are executed on the DB + * @method batch + * @memberof Trie + * @example + * var ops = [ + * { type: 'del', key: 'father' } + * , { type: 'put', key: 'name', value: 'Yuri Irsenovich Kim' } + * , { type: 'put', key: 'dob', value: '16 February 1941' } + * , { type: 'put', key: 'spouse', value: 'Kim Young-sook' } + * , { type: 'put', key: 'occupation', value: 'Clown' } + * ] + * trie.batch(ops) + * @param {Array} ops + * @param {Function} cb + */ + batch (ops, cb) { + async.eachSeries(ops, (op, cb2) => { + if (op.type === 'put') { + this.put(op.key, op.value, cb2) + } else if (op.type === 'del') { + this.del(op.key, cb2) + } else { + cb2() + } + }, cb) + } -/** - * Checks if a given root exists - * @method checkRoot - * @memberof Trie - * @param {Buffer} root - * @param {Function} cb - */ -Trie.prototype.checkRoot = function (root, cb) { - root = ethUtil.toBuffer(root) - this._lookupNode(root, function (value) { - cb(null, !!value) - }) + /** + * Checks if a given root exists + * @method checkRoot + * @memberof Trie + * @param {Buffer} root + * @param {Function} cb + */ + checkRoot (root, cb) { + root = ethUtil.toBuffer(root) + this._lookupNode(root, value => { + cb(null, !!value) + }) + } } diff --git a/src/checkpoint-interface.js b/src/checkpoint-interface.js index b736b36..2a26c58 100644 --- a/src/checkpoint-interface.js +++ b/src/checkpoint-interface.js @@ -1,10 +1,10 @@ const level = require('level-mem') const async = require('async') -const inherits = require('util').inherits -const Readable = require('readable-stream').Readable const WriteStream = require('level-ws') const callTogether = require('./util').callTogether +const ScratchReadStream = require('./scratchReadStream') + module.exports = checkpointInterface function checkpointInterface (trie) { @@ -149,33 +149,3 @@ function createScratchReadStream (scratch) { trie._scratch = scratch return new ScratchReadStream(trie) } - -// ScratchReadStream -// this is used to minimally dump the scratch into the db - -inherits(ScratchReadStream, Readable) - -function ScratchReadStream (trie) { - this.trie = trie - this.next = null - Readable.call(this, { - objectMode: true - }) -} - -ScratchReadStream.prototype._read = function () { - var self = this - if (!self._started) { - self._started = true - self.trie._findDbNodes(function (nodeRef, node, key, next) { - self.push({ - key: nodeRef, - value: node.serialize() - }) - next() - }, function () { - // close stream - self.push(null) - }) - } -} diff --git a/src/scratchReadStream.js b/src/scratchReadStream.js new file mode 100644 index 0000000..05cc454 --- /dev/null +++ b/src/scratchReadStream.js @@ -0,0 +1,29 @@ +const { Readable } = require('readable-stream') + +// ScratchReadStream +// this is used to minimally dump the scratch into the db +module.exports = class ScratchReadStream extends Readable { + constructor (trie) { + super({ objectMode: true }) + + this.trie = trie + this.next = null + } + + _read () { + if (!this._started) { + this._started = true + this.trie._findDbNodes((nodeRef, node, key, next) => { + this.push({ + key: nodeRef, + value: node.serialize() + }) + + next() + }, () => { + // close stream + this.push(null) + }) + } + } +}