This repository has been archived by the owner on Jan 19, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 89
Extract db-related methods from baseTrie #74
Merged
Merged
Changes from 7 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
94a14b5
Mv raw methods to DB class
s1na 7ec6972
Add ScratchDB which CheckpointTrie uses
s1na 0692c38
Rename checkpoint-trie to checkpointTrie
s1na d157e34
Add comments to scratch
s1na c490a14
Fix linting errors
s1na b8ada89
Regenerate docs
s1na 829393d
Rm raw methods and their tests
s1na 3a17d8c
Rename DB._db to DB._leveldb
s1na 2b307fb
Make createScratchReadStream private
s1na 5c99116
Fix linting error and jsdoc comments
s1na defcc7e
Regenerate docs
s1na a4c7f54
Add tests for db, scratch and more cases for checkpoint
s1na 4ea8d58
Fix checkpoint copy, add tests for checkpoint and secure copy
s1na File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,23 @@ | ||
const assert = require('assert') | ||
const level = require('level-mem') | ||
const async = require('async') | ||
const rlp = require('rlp') | ||
const ethUtil = require('ethereumjs-util') | ||
const semaphore = require('semaphore') | ||
const DB = require('./db') | ||
const TrieNode = require('./trieNode') | ||
const ReadStream = require('./readStream') | ||
const PrioritizedTaskExecutor = require('./prioritizedTaskExecutor') | ||
const { callTogether, asyncFirstSeries } = require('./util/async') | ||
const { callTogether } = require('./util/async') | ||
const { stringToNibbles, matchingNibbleLength, doKeysMatch } = require('./util/nibbles') | ||
|
||
/** | ||
* 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 | ||
* 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 | ||
* @public | ||
* @param {Object} [db] An instance of [levelup](https://github.com/rvagg/node-levelup/) or a compatible API. If the db is `null` or left undefined, then the trie will be stored in memory via [memdown](https://github.com/rvagg/memdown) | ||
* @param {Object} [db] An instance of [levelup](https://github.com/rvagg/node-levelup/), a compatible API or an instance of `DB`. | ||
* If the db is `null` or left undefined, then the trie will be stored in memory via [memdown](https://github.com/rvagg/memdown) | ||
* @param {Buffer|String} [root] A hex `String` or `Buffer` for the root of a previously stored trie | ||
* @prop {Buffer} root The current root of the `trie` | ||
* @prop {Boolean} isCheckpoint determines if you are saving to a checkpoint or directly to the db | ||
|
@@ -26,11 +29,7 @@ module.exports = class Trie { | |
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] | ||
this.db = db || new DB() | ||
|
||
Object.defineProperty(this, 'root', { | ||
set(value) { | ||
|
@@ -49,8 +48,6 @@ module.exports = class Trie { | |
}) | ||
|
||
this.root = root | ||
|
||
this.putRaw = this._putRaw | ||
} | ||
|
||
/** | ||
|
@@ -133,38 +130,12 @@ module.exports = class Trie { | |
}) | ||
} | ||
|
||
/** | ||
* 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) | ||
} | ||
|
||
holgerd77 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// retrieves a node from dbs by hash | ||
_lookupNode (node, cb) { | ||
if (TrieNode.isRawNode(node)) { | ||
cb(new TrieNode(node)) | ||
} else { | ||
this.getRaw(node, (err, value) => { | ||
this.db.get(node, (err, value) => { | ||
if (err) { | ||
throw err | ||
} | ||
|
@@ -178,60 +149,11 @@ module.exports = class Trie { | |
} | ||
} | ||
|
||
/** | ||
* 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) | ||
} | ||
|
||
async.each(this._putDBs, dbPut, cb) | ||
} | ||
|
||
/** | ||
* 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) | ||
} | ||
|
||
async.each(this._putDBs, del, cb) | ||
} | ||
|
||
holgerd77 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// writes a single node to dbs | ||
_putNode (node, cb) { | ||
const hash = node.hash() | ||
const serialized = node.serialize() | ||
this._putRaw(hash, serialized, cb) | ||
} | ||
|
||
// writes many nodes to db | ||
_batchNodes (opStack, cb) { | ||
function dbBatch (db, cb) { | ||
db.batch(opStack, { | ||
keyEncoding: 'binary', | ||
valueEncoding: 'binary' | ||
}, cb) | ||
} | ||
|
||
async.each(this._putDBs, dbBatch, cb) | ||
holgerd77 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.db.put(hash, serialized, cb) | ||
} | ||
|
||
/** | ||
|
@@ -560,7 +482,7 @@ module.exports = class Trie { | |
this.root = lastRoot | ||
} | ||
|
||
this._batchNodes(opStack, cb) | ||
this.db.batch(opStack, cb) | ||
} | ||
|
||
_deleteNode (key, stack, cb) { | ||
|
@@ -732,7 +654,8 @@ module.exports = class Trie { | |
// creates a new trie backed by the same db | ||
// and starting at the same root | ||
copy () { | ||
return new Trie(this.db, this.root) | ||
const db = this.db.copy() | ||
return new Trie(db, this.root) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. |
||
} | ||
|
||
/** | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,20 @@ | ||
const async = require('async') | ||
const level = require('level-mem') | ||
const WriteStream = require('level-ws') | ||
const BaseTrie = require('./baseTrie') | ||
const proof = require('./proof.js') | ||
const ScratchReadStream = require('./scratchReadStream') | ||
const DB = require('./db') | ||
const ScratchDB = require('./scratch') | ||
const { callTogether } = require('./util/async') | ||
|
||
module.exports = class CheckpointTrie extends BaseTrie { | ||
constructor (...args) { | ||
super(...args) | ||
// Reference to main DB instance | ||
this._mainDB = this.db | ||
// DB instance used for checkpoints | ||
this._scratch = null | ||
// Roots of trie at the moment of checkpoint | ||
this._checkpoints = [] | ||
} | ||
|
||
|
@@ -71,7 +76,8 @@ module.exports = class CheckpointTrie extends BaseTrie { | |
|
||
/** | ||
* Reverts the trie to the state it was at when `checkpoint` was first called. | ||
* If during a nested checkpoint, only sets parent as current checkpoint. | ||
* If during a nested checkpoint, sets root to most recent checkpoint, and sets | ||
* parent checkpoint as current. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a better explanation of existing functionality, good. |
||
* @method revert | ||
* @param {Function} cb the callback | ||
*/ | ||
|
@@ -97,9 +103,10 @@ module.exports = class CheckpointTrie extends BaseTrie { | |
* @method copy | ||
*/ | ||
copy () { | ||
const trie = new CheckpointTrie(this.db, this.root) | ||
const db = this._mainDB.copy() | ||
const trie = new CheckpointTrie(db, this.root) | ||
trie._scratch = this._scratch | ||
// trie._checkpoints = this._checkpoints.slice() | ||
trie._checkpoints = this._checkpoints.slice() | ||
return trie | ||
} | ||
|
||
|
@@ -111,60 +118,37 @@ module.exports = class CheckpointTrie extends BaseTrie { | |
createScratchReadStream (scratch) { | ||
const trie = this.copy() | ||
scratch = scratch || this._scratch | ||
scratch = new DB(scratch._db) | ||
// Only read from the scratch | ||
trie._getDBs = [scratch] | ||
trie.db = scratch | ||
trie._scratch = scratch | ||
return new ScratchReadStream(trie) | ||
} | ||
holgerd77 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Puts kv-pair directly to db, ignoring checkpoints. | ||
* @private | ||
*/ | ||
_overridePutRaw (key, val, cb) { | ||
const dbPut = (db, cb2) => { | ||
db.put(key, val, { | ||
keyEncoding: 'binary', | ||
valueEncoding: 'binary' | ||
}, cb2) | ||
} | ||
async.each(this.__putDBs, dbPut, cb) | ||
} | ||
|
||
/** | ||
* Enter into checkpoint mode. | ||
* @private | ||
*/ | ||
_enterCpMode () { | ||
this._scratch = level() | ||
this._getDBs = [this._scratch].concat(this._getDBs) | ||
this.__putDBs = this._putDBs | ||
this._putDBs = [this._scratch] | ||
this._putRaw = this.putRaw | ||
this.putRaw = this._overridePutRaw | ||
this._scratch = new ScratchDB(this._mainDB) | ||
this.db = this._scratch | ||
holgerd77 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
* Exit from checkpoint mode. | ||
* @private | ||
*/ | ||
_exitCpMode (commitState, cb) { | ||
var scratch = this._scratch | ||
const scratch = this._scratch | ||
this._scratch = null | ||
this._getDBs = this._getDBs.slice(1) | ||
this._putDBs = this.__putDBs | ||
this.putRaw = this._putRaw | ||
this.db = this._mainDB | ||
|
||
const flushScratch = (db, cb) => { | ||
if (commitState) { | ||
this.createScratchReadStream(scratch) | ||
.pipe(WriteStream(db)) | ||
.pipe(WriteStream(this.db)) | ||
.on('close', cb) | ||
} | ||
|
||
if (commitState) { | ||
async.map(this._putDBs, flushScratch, cb) | ||
} else { | ||
cb() | ||
async.nextTick(cb) | ||
holgerd77 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
const level = require('level-mem') | ||
const ethUtil = require('ethereumjs-util') | ||
|
||
const ENCODING_OPTS = { keyEncoding: 'binary', valueEncoding: 'binary' } | ||
|
||
/** | ||
* DB is a thin wrapper around the underlying levelup db, | ||
* which validates inputs and sets encoding type. | ||
*/ | ||
module.exports = class DB { | ||
constructor (db) { | ||
this._db = db || level() | ||
} | ||
|
||
/** | ||
* Retrieves a raw value from db. | ||
* @param {Buffer|String} key | ||
* @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._db.get(key, ENCODING_OPTS, (err, v) => { | ||
if (err || !v) { | ||
cb(null, null) | ||
} else { | ||
cb(null, v) | ||
} | ||
}) | ||
} | ||
|
||
/** | ||
* Writes a value directly to db. | ||
* @param {Buffer|String} key The key as a `Buffer` or `String` | ||
* @param {Buffer} value The value to be stored | ||
* @param {Function} cb A callback `Function`, which is given the argument | ||
* `err` - for errors that may have occured | ||
*/ | ||
put (key, val, cb) { | ||
this._db.put(key, val, ENCODING_OPTS, cb) | ||
} | ||
|
||
/** | ||
* Removes a raw value in the underlying db. | ||
* @param {Buffer|String} key | ||
* @param {Function} cb A callback `Function`, which is given the argument | ||
* `err` - for errors that may have occured | ||
*/ | ||
del (key, cb) { | ||
this._db.del(key, ENCODING_OPTS, cb) | ||
} | ||
|
||
/** | ||
* Performs a batch operation on db. | ||
* @param {Array} opStack A stack of levelup operations | ||
* @param {Function} cb A callback `Function`, which is given the argument | ||
* `err` - for errors that may have occured | ||
*/ | ||
batch (opStack, cb) { | ||
this._db.batch(opStack, ENCODING_OPTS, cb) | ||
} | ||
|
||
/** | ||
* Returns a copy of DB. | ||
*/ | ||
copy () { | ||
return new DB(this._db) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
module.exports = require('./checkpoint-trie') | ||
module.exports = require('./checkpointTrie') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
const DB = require('./db') | ||
const { asyncFirstSeries } = require('./util/async') | ||
|
||
const ENCODING_OPTS = { keyEncoding: 'binary', valueEncoding: 'binary' } | ||
|
||
/** | ||
* An in-memory wrap over `DB` with a backend DB | ||
* which will be queried when a key is not found | ||
* in the in-memory scratch. This class is used to implement | ||
* checkpointing functionality in CheckpointTrie. | ||
*/ | ||
module.exports = class ScratchDB extends DB { | ||
constructor (db) { | ||
super() | ||
this._backend = db._db | ||
} | ||
|
||
/** | ||
* Similar to `DB.get`, but first searches in-memory | ||
* scratch DB, if key not found, searches backend DB. | ||
*/ | ||
get (key, cb) { | ||
const getDBs = this._backend ? [this._db, this._backend] : [this._db] | ||
const dbGet = (db, cb2) => { | ||
db.get(key, ENCODING_OPTS, (err, v) => { | ||
if (err || !v) { | ||
cb2(null, null) | ||
} else { | ||
cb2(null, v) | ||
} | ||
}) | ||
} | ||
|
||
asyncFirstSeries(getDBs, dbGet, cb) | ||
} | ||
} | ||
holgerd77 marked this conversation as resolved.
Show resolved
Hide resolved
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you update the API docs here as well?