Re-implementation of Patricia-Merkle Trie (#203)

* Start of trie-db re-implementation

* WIP commit

* More WIP

* WIP, seems complete, untested

* Ported trie-db operational

* Add Ethereum trie tests

* Use u8aConcat to construct u8a

* Basic RocksDb interface

* Combine engines into DB, expand tests

* Update Trie with transaction wrapper

* LevelDb (sync) interface

* Remove unused (for now) package (compatibility)

* trie-d -> trie-db-old, trie-db-ext -> trie-db

* Version bump

* Skip old trie tests (package to be removed)

* Skip tests (fixed)

* Remove non-used RocksDb spec

* Skip FileFlat tests (deprecated?)
This commit is contained in:
Jaco Greeff
2018-09-01 22:17:57 +02:00
committed by GitHub
parent 29ddd7d357
commit e1135a665b
87 changed files with 3091 additions and 823 deletions
-837
View File
@@ -1,837 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { HashFn, NodeFactory, SemaphorePromise } from './types';
import async from 'async';
// @ts-ignore FIXME, we need to properly check the full file
import levelup from 'levelup';
import memdown from 'memdown';
// import logger from '@polkadot/util/logger';
import assert from '@polkadot/util/assert';
import isU8a from '@polkadot/util/is/u8a';
import promisify from '@polkadot/util/promisify';
// import u8aToHex from '@polkadot/util/u8a/toHex';
import keccakAsU8a from '@polkadot/util-crypto/keccak/asU8a';
import decodeRlp from '@polkadot/util-rlp/decode';
import { EMPTY_ROOT_U8A, EMPTY_ROOT_STR } from './constants';
import encoder from './encoder';
import { nibblesMatchingLength, nibblesIsEqual, nibblesFromU8a } from './nibbles';
import ReadStream from './streams/TrieRead';
import createFactory from './nodeFactory';
import isRawNode from './util/isRawNode';
import prioritizedTaskExecutor from './util/taskExecutor';
import semaphore from './util/semaphore';
type RawPutFn = (key: Uint8Array, value: Uint8Array) => Promise<any>;
// const l = logger('trie/base');
export default class BaseTrie {
// @ts-ignore we are assigning via this.root in constructor
_root: Uint8Array;
_getDBs: any[]; // FIXME
_putDBs: any[]; // FIXME
dbDown: any; // FIXME
db: any; // FIXME
hashing: HashFn;
nodeFactory: NodeFactory;
putRaw: RawPutFn;
semaphore: SemaphorePromise;
constructor (db: any, root: Uint8Array = EMPTY_ROOT_U8A, hashing: HashFn = keccakAsU8a) {
this.putRaw = this._putRaw;
this.semaphore = semaphore(1);
this.dbDown = db || memdown();
this.db = levelup(encoder(this.dbDown));
this._getDBs = [this.db];
this._putDBs = [this.db];
this.hashing = hashing;
this.nodeFactory = createFactory(hashing);
this.root = root;
// l.debug(() => ['Created BaseTrie', typeof db, u8aToHex(root)]);
}
get root (): Uint8Array {
return this._root;
}
set root (value: Uint8Array) {
if (value) {
assert(isU8a(value) && value.length === 32, 'Invalid root length. Roots are Uint8Array 32 bytes');
} else {
value = EMPTY_ROOT_U8A;
}
this._root = value;
}
/**
* Gets a value given a `key`
* @method get
* @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`
*/
async get (key: Uint8Array): Promise<Uint8Array | null> {
// l.debug(() => ['get', 'key ->', u8aToHex(key)]);
const result = await this.findPath(key);
let value = null;
if (result && result.node && result.remainder.length === 0) {
value = result.node.value;
}
return value;
}
/**
* Stores a given `value` at the given `key`
* @method put
* @param {Buffer|String} key
* @param {Buffer|String} Value
* @returns {Function} Promise<void>
*/
async put (key: Uint8Array, value: Uint8Array) {
// l.debug(() => ['put', 'key ->', u8aToHex(key), 'value ->', u8aToHex(value, 256)]);
if (!value || value.toString() === '') {
return this.del(key);
}
return this.semaphore(async () => {
if (this.root.toString() === EMPTY_ROOT_STR) {
return this._createInitialNode(key, value); // if no root initialize this trie
}
// first try to find the give key or its nearst node
const result = await this.findPath(key);
return this._updateNode(key, value, result && result.remainder, result && result.stack);
});
}
/**
* deletes a value given a `key`
* @method del
* @param {Buffer|String} key
* @param {Function} callback the callback `Function`
*/
del (key: Uint8Array) {
// l.debug(() => ['del', 'key ->', u8aToHex(key)]);
return this.semaphore(async () => {
const result = await this.findPath(key);
if (result && result.node) {
return this._deleteNode(key, result.stack);
}
});
}
/**
* Retrieves a raw value in the underlying db
* @method getRaw
* @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`.
*/
async getRaw (key: Uint8Array): Promise<Uint8Array> {
let found: Uint8Array;
await Promise.all(
this._getDBs.map(async (db) => {
if (!found) {
try {
found = await db.get(key);
} catch (error) {
// l.error('getRaw', 'key ->', u8aToHex(key), error);
// not doing anything
}
}
return found;
})
);
// @ts-ignore FIXME, we need to properly check the full file
return found;
}
// retrieves a node from dbs by hash
// @ts-ignore FIXME, we need to properly check the full file
async _lookupNode (node) {
if (isRawNode(node)) {
return this.nodeFactory.fromRaw(node);
}
let value = await this.getRaw(node);
if (value) {
try {
// @ts-ignore FIXME, we need to properly check the full file
value = this.nodeFactory.fromRaw(decodeRlp(value));
} catch (error) {
// l.error('_lookupNode decoding', typeof value, value);
throw error;
}
}
return value;
}
// TODO: remove the proxy method when changing the caching
async _putRaw (key: Uint8Array, value: Uint8Array) {
return Promise.all(
this._putDBs.map((db) => {
return db.put(key, value);
})
);
}
/**
* Removes a raw value in the underlying db
* @method delRaw
* @param {Buffer|String} key
* @param {Function} callback A callback `Function`, which is given the argument `err` - for errors that may have occured
*/
delRaw (key: Uint8Array) {
return Promise.all(
this._putDBs.map((db) => {
return db.del(key);
})
);
}
// writes a single node to dbs
// @ts-ignore FIXME, we need to properly check the full file
_putNode (node) {
const hash = node.hash();
const serialized = node.serialize();
return this._putRaw(hash, serialized);
}
// writes many nodes to db
// @ts-ignore FIXME, we need to properly check the full file
_batchNodes (opStack) {
return Promise.all(
this._putDBs.map((db) => {
return db.batch(opStack);
})
);
}
/**
* Trys to find a path to the node for the given key
* It returns a `stack` of nodes to the closet node
* @method findPath
* @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
* - remainder - the remaining key nibbles not accounted for
* - stack - an array of nodes that forms the path to node we are searching for
*/
// @ts-ignore FIXME, we need to properly check the full file
findPath (_targetKey) {
const stack: any[] = []; // FIXME
const targetKey = nibblesFromU8a(_targetKey);
// l.debug(() => ['findPath', targetKey.join(', ')]);
// @ts-ignore FIXME, we need to properly check the full file
const processNode = (nodeRef, node, keyProgress, walkController) => {
const nodeKey = node.key || [];
const remainder = targetKey.slice(nibblesMatchingLength(keyProgress, targetKey));
const matchingLen = nibblesMatchingLength(remainder, nodeKey);
stack.push(node);
if (node.type === 'branch') {
if (remainder.length === 0) {
walkController.return(null, { node, remainder: [], stack });
// we exhausted the key without finding a node
} else {
const branchIndex = remainder[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, { node: null, remainder, stack });
} else {
// node found, continuing search
walkController.only(branchIndex);
}
}
} else if (node.type === 'leaf') {
if (nibblesIsEqual(remainder, nodeKey)) {
// keys match, return node with empty key
walkController.return(null, { node, remainder: [], stack });
} else {
// reached leaf but keys dont match
walkController.return(null, { node: null, remainder, stack });
}
} else if (node.type === 'extention') {
if (matchingLen !== nodeKey.length) {
// keys dont match, fail
walkController.return(null, { node: null, remainder, stack });
} else {
// keys match, continue search
walkController.next();
}
}
};
return promisify(this, this._walkTrie, this.root, processNode);
}
/*
* Finds all nodes that store k,v values
*/
// NOTE seems unused
// _findNode = function (key, root, stack, cb) {
// this.findPath(key, (...args) => {
// cb.apply(null, args);
// });
// };
/*
* Finds all nodes that store k,v values
*/
// @ts-ignore FIXME, we need to properly check the full file
_findValueNodes (onFound, cb) {
// @ts-ignore FIXME, we need to properly check the full file
this._walkTrie(this.root, (nodeRef, node, key, walkController) => { // tslint:disable-line
let fullKey = key;
if (node.key) {
fullKey = key.concat(node.key);
}
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);
}
/*
* Finds all nodes that are stored directly in the db
* (some nodes are stored raw inside other nodes)
*/
// @ts-ignore FIXME, we need to properly check the full file
_findDbNodes (onFound, cb) {
// @ts-ignore FIXME, we need to properly check the full file
this._walkTrie(this.root, (nodeRef, node, key, walkController) => { // tslint:disable-line
if (isRawNode(nodeRef)) {
walkController.next();
} else {
onFound(nodeRef, node, key, walkController.next);
}
}, cb);
}
/**
* Updates a node
* @method _updateNode
* @param {Buffer} key
* @param {Buffer| String} value
* @param {Array} keyRemainder
* @param {Array} stack -
* @param {Function} cb - the callback
*/
// @ts-ignore FIXME, we need to properly check the full file
async _updateNode (key, value, keyRemainder, stack) {
// @ts-ignore FIXME, we need to properly check the full file
const toSave = [];
const lastNode = stack.pop();
// add the new nodes
key = nibblesFromU8a(key);
// Check if the last node is a leaf and the key matches to this
let matchLeaf = false;
if (lastNode.type === 'leaf') {
let l = 0;
for (let i = 0; i < stack.length; i++) {
const n = stack[i];
if (n.type === 'branch') {
l++;
} else {
l += n.key.length;
}
}
if ((nibblesMatchingLength(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
stack.push(
this.nodeFactory.fromLeaf(keyRemainder, value)
);
} else {
lastNode.value = value;
}
} else {
// create a branch node
const lastKey = lastNode.key;
const matchingLength = nibblesMatchingLength(lastKey, keyRemainder);
// @ts-ignore FIXME, we need to properly check the full file
const newBranchNode = this.nodeFactory.fromBranch();
// create a new extention node
if (matchingLength !== 0) {
const newKey = lastNode.key.slice(0, matchingLength);
const newExtNode = this.nodeFactory.fromExtention(newKey, value);
stack.push(newExtNode);
lastKey.splice(0, matchingLength);
keyRemainder.splice(0, matchingLength);
}
stack.push(newBranchNode);
if (lastKey.length !== 0) {
const branchKey = lastKey.shift();
if (lastKey.length !== 0 || lastNode.type === 'leaf') {
// shriking extention or leaf
lastNode.key = lastKey;
// @ts-ignore FIXME, we need to properly check the full file
const formatedNode = this._formatNode(lastNode, false, toSave);
// @ts-ignore FIXME, we need to properly check the full file
newBranchNode.setValue(branchKey, formatedNode);
} else {
// remove extention or attaching
// @ts-ignore FIXME, we need to properly check the full file
this._formatNode(lastNode, false, true, toSave);
// @ts-ignore FIXME, we need to properly check the full file
newBranchNode.setValue(branchKey, lastNode.value);
}
} else {
newBranchNode.value = lastNode.value;
}
if (keyRemainder.length !== 0) {
keyRemainder.shift();
// add a leaf node to the new branch node
stack.push(
this.nodeFactory.fromLeaf(keyRemainder, value)
);
} else {
newBranchNode.value = value;
}
}
// @ts-ignore FIXME, we need to properly check the full file
return this._saveStack(key, stack, toSave);
}
// walk tree
// @ts-ignore FIXME, we need to properly check the full file
async _walkTrie (root, onNode, onDone) {
root = root || this.root;
let isAborted: boolean = false;
// @ts-ignore FIXME, we need to properly check the full file
let returnValues: any[] = [];
if (root.toString() === EMPTY_ROOT_STR) {
onDone();
return;
}
// 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 = prioritizedTaskExecutor(maxPoolSize);
// @ts-ignore FIXME, we need to properly check the full file
const processNode = (nodeRef, node, key, cb) => {
if (!node || isAborted) {
return cb();
}
const self = this;
let isStopped = false;
key = key || [];
const walkController = {
stop: function () {
isStopped = true;
cb();
},
// end all traversal and return values to the onDone cb
return: function () {
isAborted = true;
// @ts-ignore FIXME, we need to properly check the full file
returnValues = arguments;
cb();
},
next: async function () {
if (isAborted || isStopped) {
return cb();
}
const children = node.getChildren();
// @ts-ignore FIXME callback
async.forEachOf(children, (childData: any[], index: number, cb) => {
// @ts-ignore FIXME, we need to properly check the full file
const keyExtension = childData[0];
// @ts-ignore FIXME, we need to properly check the full file
const childRef = childData[1];
const childKey = key.concat(keyExtension);
const priority = childKey.length;
// tslint:disable-next-line
taskExecutor(priority, async (taskCallback) => {
const childNode = await self._lookupNode(childRef);
// @ts-ignore FIXME, we need to properly check the full file
taskCallback();
processNode(childRef, childNode, childKey, cb);
});
}, cb);
},
// @ts-ignore FIXME, we need to properly check the full file
only: function (childIndex) {
const childRef = node.getValue(childIndex);
const childKey = key.slice();
childKey.push(childIndex);
const priority = childKey.length;
// tslint:disable-next-line
taskExecutor(priority, async (taskCallback) => {
const childNode = await self._lookupNode(childRef);
// @ts-ignore FIXME, we need to properly check the full file
taskCallback();
processNode(childRef, childNode, childKey, cb);
});
}
};
onNode(nodeRef, node, key, walkController);
};
const node = await this._lookupNode(root);
processNode(root, node, null, (error: Error | null) => {
if (error) {
onDone(error);
return;
}
// @ts-ignore FIXME, we need to properly check the full file
onDone.apply(null, returnValues);
});
}
/**
* saves a stack
* @method _saveStack
* @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
*/
// @ts-ignore FIXME, we need to properly check the full file
_saveStack (key, stack, opStack: any[]) {
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);
}
}
// @ts-ignore FIXME, we need to properly check the full file
lastRoot = this._formatNode(node, stack.length === 0, opStack);
}
if (lastRoot) {
this.root = lastRoot;
}
return this._batchNodes(opStack);
}
// @ts-ignore FIXME, we need to properly check the full file
async _deleteNode (key: Uint8Array, stack) {
// @ts-ignore FIXME, we need to properly check the full file
const 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);
}
if (branchNode.type === 'branch') {
// create an extention node
// branch->extention->branch
// @ts-ignore FIXME, we need to properly check the full file
stack.push(this.nodeFactory.fromExtention([branchKey], null));
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);
}
return key;
};
// @ts-ignore FIXME, we need to properly check the full file
const opStack: any[] = [];
let lastNode = stack.pop();
let parentNode = stack.pop();
if (!Array.isArray(key)) {
// @ts-ignore FIXME, we need to properly check the full file
key = nibblesFromU8a(key);
}
if (!parentNode) {
// the root here has to be a leaf.
this.root = EMPTY_ROOT_U8A;
} 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.
const lastNodeKey = lastNode.key;
// @ts-ignore FIXME, we need to properly check the full file
key.splice(key.length - lastNodeKey.length);
// delete the value
this._formatNode(lastNode, false, true, opStack);
// @ts-ignore FIXME, we need to properly check the full file
parentNode.setValue(key.pop(), null);
lastNode = parentNode;
parentNode = stack.pop();
}
// nodes on the branch
// @ts-ignore FIXME, we need to properly check the full file
const branchNodes = [];
// count the number of nodes on the branch
// @ts-ignore FIXME, we need to properly check the full file
lastNode.raw.forEach(function (node, i) {
const val = lastNode.getValue(i);
// @ts-ignore FIXME, we need to properly check the full file
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
// @ts-ignore FIXME, we need to properly check the full file
const branchNode = branchNodes[0][1];
// @ts-ignore FIXME, we need to properly check the full file
const branchNodeKey = branchNodes[0][0];
// look up node
const foundNode = await this._lookupNode(branchNode);
// @ts-ignore FIXME, we need to properly check the full file
key = processBranchNode(key, branchNodeKey, foundNode, parentNode, stack, opStack);
return this._saveStack(key, stack, opStack);
} else {
// simple removing a leaf and recaluclation the stack
if (parentNode) {
stack.push(parentNode);
}
stack.push(lastNode);
return this._saveStack(key, stack, opStack);
}
}
}
// Creates the initial node from an empty tree
_createInitialNode (key: Uint8Array, value: Uint8Array) {
const newNode = this.nodeFactory.fromLeaf(key, value);
this.root = newNode.hash();
return this._putNode(newNode);
}
// formats node to be saved by levelup.batch.
// returns either the hash that will be used key or the rawNode
// @ts-ignore FIXME, we need to properly check the full file
_formatNode (node: any, topLevel: any, remove: any, opStack: any) {
if (arguments.length === 3) {
opStack = remove;
remove = false;
}
const rlpNode = node.serialize();
if (rlpNode.length >= 32 || topLevel) {
const hashRoot = node.hash();
// FIXME Actually 2 things wrong here - this is on checkpoint, so one higher-level class...
// @ts-ignore FIXME, we need to properly check the full file
if (remove && this.isCheckpoint) {
opStack.push({
type: 'del',
key: hashRoot
});
} else {
opStack.push({
type: 'put',
key: hashRoot,
value: rlpNode
});
}
return hashRoot;
}
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
* @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);
}
// creates a new trie backed by the same db
// and starting at the same root
copy () {
// l.debug(() => ['Copying BaseTrie', typeof this.dbDown, u8aToHex(this.root)]);
return new BaseTrie(this.dbDown, this.root, this.hashing);
}
/**
* The given hash of operations (key additions or deletions) are executed on the DB
* @method batch
* @example
* const 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
*/
// @ts-ignore FIXME, we need to properly check the full file
batch (ops: any[]) {
return Promise.all(
// @ts-ignore FIXME, we need to properly check the full file
ops.map((op) => {
if (op.type === 'put') {
return this.put(op.key, op.value);
} else if (op.type === 'del') {
return this.del(op.key);
}
})
);
}
/**
* Checks if a given root exists
* @method checkRoot
* @param {Buffer} root
* @param {Function} cb
*/
async checkRoot (root: Uint8Array): Promise<boolean> {
const value = await this._lookupNode(root);
return !!value;
}
}
-163
View File
@@ -1,163 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { HashFn } from './types';
// @ts-ignore FIXME, we need to properly check the full file
import levelws from 'level-ws';
// @ts-ignore FIXME, we need to properly check the full file
import levelup from 'levelup';
import memdown from 'memdown';
import assert from '@polkadot/util/assert';
// import logger from '@polkadot/util/logger';
// import u8aToHex from '@polkadot/util/u8a/toHex';
import BaseTrie from './BaseTrie';
import encoder from './encoder';
import ScratchReadStream from './streams/ScratchRead';
// const l = logger('trie/checkpt');
function putRaw (key: Uint8Array, value: Uint8Array) {
return Promise.all(
// @ts-ignore FIXME, we need to properly check the full file
this.__putDBs.map((db) => {
return db.put(key, value);
})
);
}
export default class CheckpointTrie extends BaseTrie {
_checkpoints: Array<Uint8Array>;
_scratch: any; // FIXME
// @ts-ignore FIXME, we need to properly check the full file
__putDBs: any[]; // tslint:disable-line
// @ts-ignore FIXME, we need to properly check the full file
constructor (db: any, root?: Uint8Array, hashing?: HashFn) {
super(db, root, hashing);
this._scratch = null;
this._checkpoints = [];
// l.debug(() => 'Created CheckpointTrie');
}
get isCheckpoint () {
return !!this._checkpoints.length;
}
copy () {
// l.debug(() => ['Copying CheckpointTrie', typeof this.dbDown, u8aToHex(this.root)]);
return new CheckpointTrie(this.dbDown, this.root, this.hashing);
}
async checkpoint () {
// l.debug(() => 'Marking checkpoint');
const wasCheckpoint = this.isCheckpoint;
this._checkpoints.push(this.root);
if (!wasCheckpoint && this.isCheckpoint) {
this._enterCpMode();
}
}
commit () {
// l.debug(() => 'Committing checkpoint');
return this.semaphore(async () => {
assert(this.isCheckpoint, 'trying to commit when not checkpointed');
this._checkpoints.pop();
if (this.isCheckpoint) {
return;
}
return this._exitCpMode(true);
});
}
revert () {
// l.debug(() => 'Reverting checkpoint');
return this.semaphore(async () => {
if (!this.isCheckpoint) {
return;
}
this.root = this._checkpoints.pop() as Uint8Array;
if (this.isCheckpoint) {
return;
}
return this._exitCpMode(false);
});
}
// @ts-ignore FIXME, we need to properly check the full file
createScratchReadStream (scratch) {
const trie = this.copy();
scratch = scratch || this._scratch;
// only read from the scratch
trie._getDBs = [scratch];
trie._scratch = scratch;
return new ScratchReadStream(trie);
}
_enterCpMode () {
// l.debug(() => 'Entering checkpoint mode');
this._scratch = levelup(encoder(memdown()));
this._getDBs = [this._scratch].concat(this._getDBs);
this.__putDBs = this._putDBs;
this._putDBs = [this._scratch];
this._putRaw = this.putRaw;
this.putRaw = putRaw;
}
// @ts-ignore FIXME, we need to properly check the full file
_exitCpMode (commitState) {
// l.debug(() => 'Exiting checkpoint mode');
const scratch = this._scratch;
this._scratch = null;
this._getDBs = this._getDBs.slice(1);
this._putDBs = this.__putDBs;
this.putRaw = this._putRaw;
if (!commitState) {
return;
}
return Promise.all(
this._putDBs.map((db) => {
return new Promise((resolve) => {
if (!db.createWriteStream) {
db = levelws(db);
}
this
// @ts-ignore FIXME, we need to properly check the full file
.createScratchReadStream(scratch)
// @ts-ignore FIXME, we need to properly check the full file
.pipe(
// @ts-ignore FIXME, we need to properly check the full file
db.createWriteStream(encoder.options)
)
.on('close', resolve);
});
})
);
}
}
-173
View File
@@ -1,173 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { DecodedRlp, Trie$Node$Type, HashFn } from './types';
import isU8a from '@polkadot/util/is/u8a';
import u8aToHex from '@polkadot/util/u8a/toHex';
import encodeRlp from '@polkadot/util-rlp/encode';
import nibblesFromU8a from './nibbles/fromU8a';
import nibblesToU8a from './nibbles/toU8a';
function addHexPrefix (key: Array<number>, isTerminator: boolean): Array<number> {
// odd
if (key.length % 2) {
key.unshift(1);
} else {
// even
key.unshift(0);
key.unshift(0);
}
if (isTerminator) {
key[0] += 2;
}
return key;
}
function removeHexPrefix (key: Uint8Array): Uint8Array {
return key[0] % 2
? key.slice(1)
: key.slice(2);
}
export default class TrieNode {
hashing: HashFn;
raw: DecodedRlp;
type: Trie$Node$Type;
constructor (hashing: HashFn, type: Trie$Node$Type, raw: DecodedRlp) {
this.hashing = hashing;
this.raw = raw;
this.type = type;
}
get value () {
return this.getValue();
}
set value (v) {
this.setValue(v);
}
get key () {
return this.getKey();
}
set key (k) {
// @ts-ignore FIXME, we need to properly check the full file
this.setKey(k);
}
setValue (key: number | Uint8Array, value?: Uint8Array) {
if (this.type !== 'branch') {
this.raw[1] = key as Uint8Array;
return;
}
if (arguments.length === 1) {
value = key as Uint8Array;
key = 16;
}
this.raw[key as number] = value as Uint8Array;
}
getValue (key?: number) {
if (this.type !== 'branch') {
return this.raw[1];
}
if (arguments.length === 0) {
key = 16;
}
// @ts-ignore FIXME, we need to properly check the full file
const val = this.raw[key];
if (val !== null && val !== undefined && val.length !== 0) {
return val;
}
}
setKey (key: Array<number> | Uint8Array) {
if (this.type !== 'branch') {
if (Array.isArray(key)) {
key = key.slice(0); // copy the key
} else {
key = nibblesFromU8a(key);
}
key = addHexPrefix(key, this.type === 'leaf');
this.raw[0] = nibblesToU8a(key);
}
}
getKey () {
if (this.type !== 'branch') {
return removeHexPrefix(
// @ts-ignore FIXME, we need to properly check the full file
nibblesFromU8a(this.raw[0])
);
}
}
serialize (): Uint8Array {
return encodeRlp(this.raw);
}
hash (): Uint8Array {
return this.hashing(this.serialize());
}
toString (): string {
return this.raw.reduce((out, el, index) => {
if (index !== 0) {
out += ', ';
}
if (isU8a(el)) {
out += u8aToHex(el);
} else if (el) {
out += 'object';
} else {
out += 'empty';
}
return out;
}, `${this.type}: [`) + ']';
}
getChildren () {
const children = [];
switch (this.type) {
case 'leaf':
// no children
break;
case 'extention':
// one child
children.push([this.key, this.getValue()]);
break;
case 'branch':
let end = 16;
for (let index = 0; index < end; index++) {
const value = this.getValue(index);
if (value) {
children.push([
[index], value
]);
}
}
break;
}
return children;
}
}
+12 -5
View File
@@ -1,13 +1,20 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
// of the ISC license. See the LICENSE file for details.
import hashing from '@polkadot/util-crypto/keccak/asU8a';
import trieRoot from '@polkadot/trie-hash/root';
const EMPTY_ROOT_U8A = trieRoot([]);
const EMPTY_ROOT_STR = EMPTY_ROOT_U8A.toString();
const EMPTY_NODE = new Uint8Array();
const EMPTY_U8A = trieRoot([]);
const EMPTY_U8A_STR = trieRoot([]);
const EMPTY_HASH = hashing(EMPTY_U8A);
const EMPTY_HASH_STR = EMPTY_HASH.toString();
export {
EMPTY_ROOT_U8A,
EMPTY_ROOT_STR
EMPTY_NODE,
EMPTY_HASH,
EMPTY_HASH_STR,
EMPTY_U8A,
EMPTY_U8A_STR
};
-29
View File
@@ -1,29 +0,0 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { Logger } from '@polkadot/util/types';
import assert from '@polkadot/util/assert';
import isBuffer from '@polkadot/util/is/buffer';
import isU8a from '@polkadot/util/is/u8a';
import toU8a from '@polkadot/util/u8a/toU8a';
export default function decode (l: Logger) {
return (value: Buffer | Uint8Array | null): Uint8Array => {
// l.debug(() => ['decoding', value]);
if (!value || !value.length) {
return new Uint8Array(0);
}
const isBufferIn = isBuffer(value);
const isU8aIn = isU8a(value);
assert(isBufferIn || isU8aIn, `Expected a Buffer | Uint8Array | null input type, received '${typeof value}'`);
return isBufferIn
? toU8a(value)
: value;
};
}
-29
View File
@@ -1,29 +0,0 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { Logger } from '@polkadot/util/types';
import assert from '@polkadot/util/assert';
import isBuffer from '@polkadot/util/is/buffer';
import isU8a from '@polkadot/util/is/u8a';
import u8aToBuffer from '@polkadot/util/u8a/toBuffer';
export default function encode (l: Logger) {
return (value: Buffer | Uint8Array | null): Buffer => {
// l.debug(() => ['encoding', value ? value.toString() : value]);
if (!value || value.length === 0) {
return Buffer.alloc(0);
}
const isBufferIn = isBuffer(value);
const isU8aIn = isU8a(value);
assert(isBufferIn || isU8aIn, `Expected Buffer | Uint8Array | null input, received '${typeof value}'`);
return isBufferIn
? value as Buffer
: u8aToBuffer(value);
};
}
-36
View File
@@ -1,36 +0,0 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
// @ts-ignore FIXME, we need to properly check the full file
import { LevelUp$AbstractStorage } from 'levelup';
// @ts-ignore FIXME, we need to properly check the full file
import encodingDown, { EncoderBufferOptions, EncodingBuffer } from 'encoding-down';
import logger from '@polkadot/util/logger';
import decode from './decode';
import encode from './encode';
const l = logger('trie/codec');
const encoding: EncodingBuffer<Uint8Array> = {
buffer: true,
decode: decode(l),
encode: encode(l),
type: 'Uint8Array'
};
const options: EncoderBufferOptions<Uint8Array> = {
keyEncoding: encoding,
valueEncoding: encoding
};
function encoder (db: LevelUp$AbstractStorage) {
return encodingDown(db, options);
}
// @ts-ignore FIXME, we need to properly check the full file
encoder.options = options;
export default encoder;
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import toU8a from '@polkadot/util/u8a/toU8a';
import u8aToHex from '@polkadot/util/u8a/toHex';
import Trie from './index';
import getTests from '../../../test/getTests';
const trietest = getTests('TrieTests/trietest.json');
const trieanyorder = getTests('TrieTests/trieanyorder.json');
describe('official tests', () => {
let trie;
beforeEach(() => {
trie = new Trie();
});
// FIXME (maybe) These all have empty/null values, we don't allow those atm as a key nor value
describe.skip('ordered tests', () => {
const testNames = Object.keys(trietest);
trietest.forEach(({ name, input, root }) => {
it(name, () => {
input.map((input) =>
trie.put(toU8a(input[0]), toU8a(input[1]))
);
expect(
u8aToHex(trie.getRoot())
).toEqual(
root
);
});
});
});
describe('unordered tests', () => {
const testNames = Object.keys(trieanyorder);
trieanyorder.forEach(({ name, input, root }) => {
const keys = Object.keys(input);
it(name, () => {
keys.map((key) =>
trie.put(toU8a(key), toU8a(input[key]))
)
expect(
u8aToHex(trie.getRoot())
).toEqual(
root
);
});
});
});
});
+267
View File
@@ -0,0 +1,267 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import u8aToHex from '@polkadot/util/u8a/toHex';
import toU8a from '@polkadot/util/u8a/toU8a';
import Trie from './index';
describe('simple save and retrive', () => {
const trie = new Trie();
it('starts with a valid root', () => {
expect(
trie.getRoot()
).toEqual(
new Uint8Array([
86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33
])
)
});
it('save a value', () => {
trie.put(toU8a('test'), toU8a('one'));
expect(
trie.getRoot()
).toEqual(
new Uint8Array([
43, 119, 232, 84, 123, 197, 94, 42, 149, 34, 124, 147, 159, 159, 157, 103, 149, 45, 225, 233, 112, 160, 23, 224, 145, 11, 229, 16, 176, 144, 175, 243
])
);
});
it('should get a value', () => {
expect(
trie.get(toU8a('test'))
).toEqual(toU8a('one'));
});
it('should update a value', () => {
trie.put(toU8a('test'), toU8a('two'));
expect(
trie.get(toU8a('test'))
).toEqual(toU8a('two'));
expect(
trie.getRoot()
).toEqual(
new Uint8Array([
127, 237, 74, 184, 190, 46, 103, 129, 65, 141, 13, 96, 61, 232, 146, 13, 56, 32, 59, 29, 246, 58, 175, 140, 72, 182, 196, 103, 230, 245, 229, 148
])
);
});
it('should delete a value', () => {
trie.del(toU8a('test'));
expect(
trie.get(toU8a('test'))
).toEqual(null);
expect(
trie.getRoot()
).toEqual(
new Uint8Array([
86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33
])
);
});
it('should recreate a value', () => {
trie.put(toU8a('test'), toU8a('one'));
expect(
trie.getRoot()
).toEqual(
new Uint8Array([
43, 119, 232, 84, 123, 197, 94, 42, 149, 34, 124, 147, 159, 159, 157, 103, 149, 45, 225, 233, 112, 160, 23, 224, 145, 11, 229, 16, 176, 144, 175, 243
])
);
});
it('should get updated a value', () => {
expect(
trie.get(toU8a('test'))
).toEqual(toU8a('one'));
});
it('should create a branch here', () => {
trie.put(toU8a('doge'), toU8a('coin'));
expect(
trie.getRoot()
).toEqual(
toU8a('0xde8a34a8c1d558682eae1528b47523a483dd8685d6db14b291451a66066bf0fc')
);
});
it('should get a value that is in a branch', () => {
expect(
trie.get(toU8a('doge'))
).toEqual(toU8a('coin'));
});
it('should delete from a branch', () => {
trie.del(toU8a('doge'));
expect(
trie.get(toU8a('doge'))
).toEqual(null);
expect(
trie.getRoot()
).toEqual(
new Uint8Array([
43, 119, 232, 84, 123, 197, 94, 42, 149, 34, 124, 147, 159, 159, 157, 103, 149, 45, 225, 233, 112, 160, 23, 224, 145, 11, 229, 16, 176, 144, 175, 243
])
);
});
});
describe('storing longer values', () => {
const trie = new Trie();
const longString = 'this will be a really really really long value';
const longStringRoot = '0xb173e2db29e79c78963cff5196f8a983fbe0171388972106b114ef7f5c24dfa3';
it('should store a longer string', () => {
trie.put(toU8a('done'), toU8a(longString));
expect(
trie.getRoot()
).toEqual(new Uint8Array([
79, 170, 48, 228, 218, 25, 91, 13, 86, 114, 153, 98, 252, 127, 54, 124, 242, 163, 153, 215, 249, 196, 65, 209, 186, 130, 39, 6, 181, 246, 231, 111
]));
});
it('should retrieve a longer value (first pass)', () => {
expect(
trie.get(toU8a('done'))
).toEqual(toU8a(longString));
});
it('should store subsequent values', () => {
trie.put(toU8a('doge'), toU8a('coin'));
expect(longStringRoot).toEqual(u8aToHex(trie.getRoot()));
});
it('should retrieve a longer value (second pass)', () => {
expect(
trie.get(toU8a('done'))
).toEqual(toU8a(longString));
});
it('should retrieve subsequent values', () => {
expect(trie.get(toU8a('doge'))).toEqual(toU8a('coin'));
});
it('should when being modiefied delete the old value', () => {
trie.put(toU8a('done'), toU8a('test'));
expect(
trie.getRoot()
).toEqual(new Uint8Array([
219, 23, 9, 241, 116, 187, 56, 113, 244, 253, 200, 19, 242, 27, 188, 158, 217, 68, 67, 249, 222, 154, 243, 190, 184, 56, 173, 54, 214, 42, 91, 206
]));
});
});
describe('testing Extentions and branches', () => {
const trie = new Trie();
it('should store a value', () => {
trie.put(toU8a('doge'), toU8a('coin'));
});
it('should create extention to store this value', () => {
trie.put(toU8a('do'), toU8a('verb'));
expect(
toU8a('0xf803dfcb7e8f1afd45e88eedb4699a7138d6c07b71243d9ae9bff720c99925f9')
).toEqual(trie.getRoot());
});
it('should store this value under the extention ', () => {
trie.put(toU8a('done'), toU8a('finished'));
expect(
'0x409cff4d820b394ed3fb1cd4497bdd19ffa68d30ae34157337a7043c94a3e8cb'
).toEqual(u8aToHex(trie.getRoot()));
});
});
describe('testing Extentions and branches - reverse', () => {
const trie = new Trie();
it('should create extention to store this value', () => {
trie.put(toU8a('do'), toU8a('verb'));
const value = trie.get(toU8a('do'));
expect(value).toEqual(toU8a('verb'));
});
it('should store a value', () => {
trie.put(toU8a('doge'), toU8a('coin'));
});
it('should store this value under the extention ', () => {
trie.put(toU8a('done'), toU8a('finished'));
expect(
'0x409cff4d820b394ed3fb1cd4497bdd19ffa68d30ae34157337a7043c94a3e8cb'
).toEqual(u8aToHex(trie.getRoot()));
});
});
describe('testing deletions cases', () => {
const trie = new Trie();
it('should delete from a branch->branch-branch', () => {
trie.put(new Uint8Array([11, 11, 11]), toU8a('first')),
trie.put(new Uint8Array([12, 22, 22]), toU8a('create the first branch')),
trie.put(new Uint8Array([12, 34, 44]), toU8a('create the last branch'))
trie.del(new Uint8Array([12, 22, 22]));
const value = trie.get(new Uint8Array([12, 22, 22]));
expect(null).toEqual(value);
});
it('should delete from a branch->branch-extention', () => {
trie.put(new Uint8Array([11, 11, 11]), toU8a('first')),
trie.put(new Uint8Array([12, 22, 22]), toU8a('create the first branch')),
trie.put(new Uint8Array([12, 33, 33]), toU8a('create the middle branch')),
trie.put(new Uint8Array([12, 33, 44]), toU8a('create the last branch'))
trie.del(new Uint8Array([12, 22, 22]));
const value = trie.get(new Uint8Array([12, 22, 22]));
expect(null).toEqual(value);
});
it('should delete from a extention->branch-extention', () => {
trie.put(new Uint8Array([11, 11, 11]), toU8a('first'));
trie.put(new Uint8Array([12, 22, 22]), toU8a('create the first branch'));
trie.put(new Uint8Array([12, 33, 33]), toU8a('create the middle branch'));
trie.put(new Uint8Array([12, 33, 44]), toU8a('create the last branch'));
trie.del(new Uint8Array([11, 11, 11]));
const value = trie.get(new Uint8Array([11, 11, 11]));
expect(null).toEqual(value);
});
it('should delete from a extention->branch-branch', () => {
trie.put(new Uint8Array([11, 11, 11]), toU8a('first'));
trie.put(new Uint8Array([12, 22, 22]), toU8a('create the first branch'));
trie.put(new Uint8Array([12, 33, 33]), toU8a('create the middle branch'));
trie.put(new Uint8Array([12, 34, 44]), toU8a('create the last branch'));
trie.del(new Uint8Array([11, 11, 11]));
const value = trie.get(new Uint8Array([11, 11, 11]));
expect(null).toEqual(value);
});
});
+460 -13
View File
@@ -1,23 +1,470 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
// of the ISC license. See the LICENSE file for details.
import { HashFn } from './types';
import { TxDb } from '@polkadot/db/types';
import { Node, NodeBranch, NodeEncodedOrEmpty, NodeKv, NodeNotEmpty, NodeType } from './types';
// import logger from '@polkadot/util/logger';
import CheckpointTrie from './CheckpointTrie';
import { prove, verifyProof } from './proof';
import MemoryDb from '@polkadot/db/Memory';
import isNull from '@polkadot/util/is/null';
import logger from '@polkadot/util/logger';
import u8aConcat from '@polkadot/util/u8a/concat';
import hashing from '@polkadot/util-crypto/keccak/asU8a';
import toNibbles from '@polkadot/trie-hash/util/asNibbles';
// const l = logger('trie');
import { isBranchNode, isEmptyNode, isExtensionNode, isKvNode, isLeafNode } from './util/is';
import { extractKey, keyEquals, keyStartsWith, computeExtensionKey, computeLeafKey, consumeCommonPrefix } from './util/key';
import { decodeNibbles, encodeNibbles } from './util/nibbles';
import { getNodeType, decodeNode, encodeNode } from './util/node';
import { EMPTY_HASH, EMPTY_U8A } from './constants';
export default class Trie extends CheckpointTrie {
static prove = prove;
static verifyProof = verifyProof;
const l = logger('trie/db');
constructor (db: any, root?: Uint8Array, hashing?: HashFn) {
super(db, root, hashing);
export default class Trie {
private db: TxDb;
private txRoot: Uint8Array;
private rootHash: Uint8Array;
// l.debug('Created Trie');
constructor (db?: TxDb, rootHash: Uint8Array = EMPTY_HASH) {
this.db = db || new MemoryDb();
this.rootHash = rootHash;
this.txRoot = rootHash;
}
private createCheckpoint (): Uint8Array {
this.txRoot = this.rootHash;
return this.txRoot;
}
private commitCheckpoint (): Uint8Array {
return this.rootHash;
}
private revertCheckpoint (): Uint8Array {
this.rootHash = this.txRoot;
return this.rootHash;
}
transaction (fn: () => boolean): Uint8Array {
try {
this.createCheckpoint();
return this.db.transaction(fn)
? this.commitCheckpoint()
: this.revertCheckpoint();
} catch (error) {
this.revertCheckpoint();
throw error;
}
}
del (key: Uint8Array) {
l.debug(() => ['del', { key }]);
this._setRootNode(
this._del(
this.getNode(this.rootHash),
toNibbles(key)
)
);
}
get (key: Uint8Array): Uint8Array | null {
l.debug(() => ['get', { key }]);
return this._get(
this.getNode(this.rootHash),
toNibbles(key)
);
}
put (key: Uint8Array, value: Uint8Array): void {
l.debug(() => ['put', { key, value }]);
this._setRootNode(
this._put(
this.getNode(this.rootHash),
toNibbles(key),
value
)
);
}
getRoot (): Uint8Array {
const rootNode = this.getNode(this.rootHash);
if (isNull(rootNode)) {
return EMPTY_U8A;
}
return this.rootHash;
}
setRoot (rootHash: Uint8Array): void {
this.rootHash = rootHash;
// return this._setRootNode(rootNode);
}
private getNode (hash: Uint8Array | null): Node {
if (!hash || hash.length === 0 || keyEquals(hash, EMPTY_HASH)) {
return null;
} else if (hash.length < 32) {
return decodeNode(hash);
}
return decodeNode(
this.db.get(hash)
);
}
private _del (node: Node, trieKey: Uint8Array): Node {
// l.debug(() => ['_del', { node, trieKey }]);
if (isEmptyNode(node)) {
return null;
} else if (isBranchNode(node)) {
return this._delBranchNode(node, trieKey);
} else if (isKvNode(node)) {
return this._delKvNode(node, trieKey);
}
throw new Error('Unreachable');
}
private _delBranchNode (node: NodeBranch, trieKey: Uint8Array): Node {
// l.debug(() => ['_delBranchNode', { node, trieKey }]);
if (trieKey.length === 0) {
node[node.length - 1] = null;
return this._normaliseBranchNode(node);
}
const nodeToDelete = this.getNode(node[trieKey[0]]);
const subNode = this._del(nodeToDelete, trieKey.subarray(1));
const encodedSubNode = this._persistNode(subNode);
if (keyEquals(encodedSubNode, node[trieKey[0]])) {
return node;
}
node[trieKey[0]] = encodedSubNode;
if (isNull(encodedSubNode)) {
return this._normaliseBranchNode(node);
}
return node;
}
private _delKvNode (node: NodeNotEmpty, trieKey: Uint8Array): Node {
// l.debug(() => ['_delKvNode', { node, trieKey }]);
const currentKey = extractKey(node);
const nodeType = getNodeType(node);
if (!keyStartsWith(trieKey, currentKey)) {
return node;
}
if (nodeType === NodeType.LEAF) {
return keyEquals(trieKey, currentKey)
? null
: node;
}
const subKey = trieKey.subarray(currentKey.length);
const subNode = this.getNode(node[1]);
const newSub = this._del(subNode, subKey);
const encodedNewSub = this._persistNode(newSub);
if (keyEquals(encodedNewSub, node[1])) {
return node;
} else if (isNull(newSub)) {
return null;
}
if (isKvNode(newSub)) {
const subNibbles = decodeNibbles(newSub[0]);
const newKey = u8aConcat(currentKey, subNibbles);
return [
encodeNibbles(newKey),
newSub[1]
];
} else if (isBranchNode(newSub)) {
return [
encodeNibbles(currentKey),
encodedNewSub
];
}
throw new Error('Unreachable');
}
private _get (node: Node, trieKey: Uint8Array): NodeEncodedOrEmpty {
// l.debug(() => ['_get', { node, trieKey }]);
if (isEmptyNode(node)) {
return null;
} else if (isBranchNode(node)) {
return this._getBranchNode(node, trieKey);
} else if (isKvNode(node)) {
return this._getKvNode(node, trieKey);
}
throw new Error('Invalid NodeType');
}
private _getBranchNode (node: NodeBranch, trieKey: Uint8Array): NodeEncodedOrEmpty {
// l.debug(() => ['_getBranchNode', { node, trieKey }]);
if (trieKey.length === 0) {
return node[16];
}
const subNode = this.getNode(node[trieKey[0]]);
return this._get(subNode, trieKey.subarray(1));
}
private _getKvNode (node: NodeKv, trieKey: Uint8Array): NodeEncodedOrEmpty {
// l.debug(() => ['_getKvNode', { node, trieKey }]);
const currentKey = extractKey(node);
const nodeType = getNodeType(node);
l.debug(() => [{ currentKey, trieKey, nodeType }]);
if (nodeType === NodeType.LEAF) {
if (keyEquals(trieKey, currentKey)) {
return node[1];
}
return null;
} else if (nodeType === NodeType.EXTENSION) {
if (keyStartsWith(trieKey, currentKey)) {
const subNode = this.getNode(node[1]);
return this._get(subNode, trieKey.subarray(currentKey ? currentKey.length : 0));
}
return null;
}
throw new Error('Unreachable');
}
private _nodeToDbMapping (node: Node): NodeNotEmpty {
// l.debug(() => ['_nodeToDbMapping', { node }]);
if (isEmptyNode(node)) {
return [
null,
null
];
}
const encoded = encodeNode(node);
if (encoded.length < 32) {
return [
// @ts-ignore Ok, this is not correct - this comes back as an embedded node, which mean that our definitions here are completely wrong (or what we think it is, does not align with what it should be as per the spec and implementation)
node,
null
];
}
return [
hashing(encoded),
encoded
];
}
private _normaliseBranchNode (node: NodeNotEmpty): Node {
// l.debug(() => ['_normaliseBranchNode', { node }]);
const mapped = node
.map((value, index) => ({
index,
value
}))
.filter(({ value }) =>
!!value && value.length !== 0
);
if (mapped.length >= 2) {
return node;
}
if (node[16]) {
return [
computeLeafKey(new Uint8Array()),
node[16]
];
}
const [{ index, value }] = mapped;
const subNode = this.getNode(value);
if (isBranchNode(subNode)) {
return [
encodeNibbles(new Uint8Array([index])),
this._persistNode(subNode)
];
} else if (isKvNode(subNode)) {
const subNibbles = decodeNibbles(subNode[0]);
const newKey = u8aConcat(new Uint8Array([index]), subNibbles);
return [
encodeNibbles(newKey),
subNode[1]
];
}
throw new Error('Unreachable');
}
private _persistNode (node: Node): NodeEncodedOrEmpty {
const [key, value] = this._nodeToDbMapping(node);
// l.debug(() => ['_persistNode', { node, key, value }]);
if (value) {
this.db.put(key as Uint8Array, value);
}
return key;
}
private _put (node: Node, trieKey: Uint8Array, value: Uint8Array): NodeNotEmpty {
// l.debug(() => ['_put', { node, trieKey, value }]);
if (isEmptyNode(node)) {
return [
computeLeafKey(trieKey),
value
];
} else if (isKvNode(node)) {
return this._putKvNode(node, trieKey, value);
} else if (isBranchNode(node)) {
return this._putBranchNode(node, trieKey, value);
}
throw new Error('Unreachable');
}
private _putBranchNode (node: NodeBranch, trieKey: Uint8Array, value: Uint8Array): NodeNotEmpty {
// l.debug(() => ['_putBranchNode', { node, trieKey, value }]);
if (trieKey && trieKey.length) {
const subNode = this.getNode(node[trieKey[0]]);
const newNode = this._put(subNode, trieKey.subarray(1), value);
node[trieKey[0]] = this._persistNode(newNode);
} else {
node[node.length - 1] = value;
}
return node;
}
private _putKvNode (node: NodeKv, trieKey: Uint8Array, value: Uint8Array): NodeNotEmpty {
// l.debug(() => ['_putKvNode', { node, trieKey, value }]);
const currentKey = extractKey(node);
const [commonPrefix, currentRemainder, trieRemainder] = consumeCommonPrefix(currentKey, trieKey);
const isExtension = isExtensionNode(node);
const isLeaf = isLeafNode(node);
let newNode: NodeNotEmpty;
l.debug(() => ['_putKvNode', { currentKey, commonPrefix, currentRemainder, trieRemainder }]);
if (currentRemainder.length === 0 && trieRemainder.length === 0) {
if (isLeaf) {
return [
node[0],
value
];
}
const subNode = this.getNode(node[1]);
newNode = this._put(subNode, trieRemainder, value);
} else if (currentRemainder.length === 0) {
if (isExtension) {
const subNode = this.getNode(node[1]);
newNode = this._put(subNode, trieRemainder, value);
} else {
const subPosition = trieRemainder[0];
const subKey = computeLeafKey(trieRemainder.subarray(1));
const subNode: NodeKv = [subKey, value];
newNode = [
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null,
node[1]
];
newNode[subPosition] = this._persistNode(subNode);
}
} else {
newNode = [
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null,
null
];
if (currentRemainder.length === 1 && isExtension) {
newNode[currentRemainder[0]] = node[1];
} else {
const computedKey = isExtension
? computeExtensionKey(currentRemainder.subarray(1))
: computeLeafKey(currentRemainder.subarray(1));
newNode[currentRemainder[0]] = this._persistNode([
computedKey,
node[1]
]);
}
if (trieRemainder.length) {
newNode[trieRemainder[0]] = this._persistNode([
computeLeafKey(trieRemainder.subarray(1)),
value
]);
} else {
newNode[16] = value;
}
}
l.log(() => ['newNode', newNode]);
if (commonPrefix.length) {
return [
computeExtensionKey(commonPrefix),
this._persistNode(newNode)
];
}
return newNode;
}
private _setRootNode (node: Node): void {
// l.debug(() => ['_setRootNode', { node }]);
if (isEmptyNode(node)) {
this.rootHash = EMPTY_HASH;
} else {
const encoded = encodeNode(node);
const newHash = hashing(encoded);
this.db.put(newHash, encoded);
this.rootHash = newHash;
}
}
}
-17
View File
@@ -1,17 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
export default function nibblesFromU8a (key: Uint8Array | Buffer): Array<number> {
const nibbles = [];
for (let i = 0; i < key.length; i++) {
const q = i * 2;
nibbles[q] = key[i] >> 4;
nibbles[q + 1] = key[i] % 16;
}
return nibbles;
}
-15
View File
@@ -1,15 +0,0 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import nibblesFromU8a from './fromU8a';
import nibblesIsEqual from './isEqual';
import nibblesMatchingLength from './matchingLength';
import nibblesToU8a from './toU8a';
export {
nibblesFromU8a,
nibblesIsEqual,
nibblesMatchingLength,
nibblesToU8a
};
-12
View File
@@ -1,12 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import matchingLength from './matchingLength';
export default function isEqual (keyA: Array<number>, keyB: Array<number>) {
const length = matchingLength(keyA, keyB);
return length === keyA.length && length === keyB.length;
}
@@ -1,14 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
export default function matchingLength (nib1: Array<number>, nib2: Array<number>): number {
let index = 0;
while (nib1[index] === nib2[index] && nib1.length > index) {
index++;
}
return index;
}
-16
View File
@@ -1,16 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
export default function nibblesToU8a (arr: Array<number>): Uint8Array {
const u8a = new Uint8Array(arr.length / 2);
for (let i = 0; i < u8a.length; i++) {
const q = i * 2;
u8a[i] = (arr[q] << 4) + arr[q + 1];
}
return u8a;
}
-91
View File
@@ -1,91 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { Logger } from '@polkadot/util/types';
import { HashFn, DecodedRlp, NodeFactory, Trie$Node, Trie$Node$Type } from './types';
import logger from '@polkadot/util/logger';
// import u8aToHex from '@polkadot/util/u8a/toHex';
import nibblesFromU8a from './nibbles/fromU8a';
// import rlpToString from './util/rlpToString';
import TrieNode from './Node';
const l = logger('trie/node');
/**
* Determines the node type.
* @returns {String} - the node type
* - leaf - if the node is a leaf
* - branch - if the node is a branch
* - extention - if the node is an extention
*/
function getNodeType (node: DecodedRlp): Trie$Node$Type {
if (node.length === 17) {
return 'branch';
}
if (node.length === 2) {
const key = nibblesFromU8a(node[0] as Uint8Array);
if (key[0] > 1) {
return 'leaf';
}
return 'extention';
}
throw new Error(`Unable to determine Node type`);
}
function fromBranch (l: Logger, hashing: HashFn) {
return (rlp: DecodedRlp): Trie$Node => {
// l.debug(() => ['fromBranch']);
const node = new TrieNode(hashing, 'branch', Array.apply(null, Array(17)));
// NOTE Removed since
// (1) 'branch' is never called with any keys,
// (b) `this.set` points to what exactly?
// if (key) {
// key.forEach(function (keyVal) {
// this.set.apply(this, keyVal);
// });
// }
return node;
};
}
function fromRaw (l: Logger, hashing: HashFn) {
return (rlp: DecodedRlp): Trie$Node => {
// l.debug(() => ['fromRaw ->', rlpToString(rlp)]);
const node = new TrieNode(hashing, getNodeType(rlp), rlp);
return node;
};
}
function fromType (l: Logger, hashing: HashFn, type: Trie$Node$Type) {
return (key: Uint8Array, value: Uint8Array): Trie$Node => {
// l.debug(() => ['fromType', 'type ->', type, 'key ->', u8aToHex(key), 'value ->', u8aToHex(value, 256)]);
const node = new TrieNode(hashing, type, Array(2));
node.setValue(value);
node.setKey(key);
return node;
};
}
export default (hashing: HashFn): NodeFactory => ({
fromBranch: fromBranch(l, hashing),
fromExtention: fromType(l, hashing, 'extention'),
fromLeaf: fromType(l, hashing, 'leaf'),
fromRaw: fromRaw(l, hashing)
});
-11
View File
@@ -1,11 +0,0 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import prove from './prove';
import verifyProof from './verifyProof';
export {
prove,
verifyProof
};
-35
View File
@@ -1,35 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import assert from '@polkadot/util/assert';
/**
* Returns a merkle proof for a given key
* @method Trie.prove
* @param {Trie} trie
* @param {String} key
* @return Promise<{Array.<TrieNode>}> `proof`
*/
// @ts-ignore FIXME, we need to properly check the full file
export default async function prove (trie, key) {
const result = await trie.findPath(key);
assert(result, 'findPath does not contain a valid path');
const { remainder, stack } = result;
assert(remainder.length === 0, 'Node does not contain the key');
// @ts-ignore FIXME, we need to properly check the full file
return stack.reduce((proofs, node, index) => {
const rlp = node.serialize();
if ((rlp.length >= 32) || (index === 0)) {
proofs.push(rlp);
}
return proofs;
}, []);
}
-88
View File
@@ -1,88 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { HashFn } from '../types';
import assert from '@polkadot/util/assert';
import decodeRlp from '@polkadot/util-rlp/decode';
import keccakAsU8a from '@polkadot/util-crypto/keccak/asU8a';
import { nibblesMatchingLength, nibblesFromU8a } from '../nibbles';
import createNodeFactory from '../nodeFactory';
/**
* Verifies a merkle proof for a given key
* @method Trie.verifyProof
* @param {Buffer | Uint8Array} rootHash
* @param {String} key
* @param {Array.<TrieNode>} proof
* @returns Promise<{String}>
*/
// @ts-ignore FIXME, we need to properly check the full file
export default async function verifyProof (rootHash, _key, proofs, hashing: HashFn = keccakAsU8a) {
const nodeFactory = createNodeFactory(hashing);
const lastIndex = proofs.length - 1;
let key = nibblesFromU8a(_key);
let wantedHash = rootHash;
for (let i = 0; i < proofs.length; i++) {
const proof = proofs[i];
const hash = hashing(proof);
assert(hash.toString() === wantedHash.toString(), `Bad proof node ${i}: hash mismatch`);
// @ts-ignore FIXME, we need to properly check the full file
const node = nodeFactory.fromRaw(decodeRlp(proof));
let cld;
if (node.type === 'branch') {
if (key.length === 0) {
assert(i === lastIndex, 'Additional nodes at end of proof (branch)');
return node.value;
}
cld = node.raw[key[0]];
key = key.slice(1);
if (cld.length === 2) {
// @ts-ignore FIXME, we need to properly check the full file
const embeddedNode = nodeFactory.fromRaw(cld);
assert(i === lastIndex, 'Additional nodes at end of proof (embeddedNode)');
// @ts-ignore FIXME, we need to properly check the full file
assert(nibblesMatchingLength(embeddedNode.key, key) === embeddedNode.key.length, 'Key length does not match with the proof one (embeddedNode)');
// @ts-ignore FIXME, we need to properly check the full file
key = key.slice(embeddedNode.key.length);
assert(key.length === 0, 'Key does not match with the proof one (embeddedNode)');
return embeddedNode.value;
} else {
wantedHash = cld;
}
} else if ((node.type === 'extention') || (node.type === 'leaf')) {
// @ts-ignore FIXME, we need to properly check the full file
assert(nibblesMatchingLength(node.key, key) === node.key.length, 'Key does not match with the proof one (extention|leaf)');
cld = node.value;
// @ts-ignore FIXME, we need to properly check the full file
key = key.slice(node.key.length);
if (key.length === 0) {
assert(i === lastIndex, 'Additional nodes at end of proof (extention|leaf)');
return cld;
} else {
wantedHash = cld;
}
} else {
throw new Error('Invalid node type');
}
}
throw new Error('Unexpected end of proof');
}
@@ -1,58 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { Trie$Base } from '../types';
// @ts-ignore FIXME, we need to properly check the full file
import { Readable } from 'readable-stream';
// import logger from '@polkadot/util/logger';
// import u8aToHex from '@polkadot/util/u8a/toHex';
// import rlpToString from '../util/rlpToString';
// const l = logger('trie/scratch');
export default class ScratchReadStream extends Readable {
trie: Trie$Base;
next: null; // FIXME
// @ts-ignore FIXME, we need to properly check the full file
_started: boolean;
constructor (trie: Trie$Base) {
super({ objectMode: true });
this.trie = trie;
this.next = null;
}
_read () {
if (this._started) {
return;
}
const onDone = () => {
// l.debug(() => 'onDone');
// @ts-ignore FIXME, we need to properly check the full file
this.push(null);
};
// @ts-ignore FIXME, we need to properly check the full file
const onFound = (nodeRef, node, key, next) => {
// l.debug(() => ['onFound', u8aToHex(nodeRef), node.type, rlpToString(node.raw)]);
// @ts-ignore FIXME, we need to properly check the full file
this.push({
key: nodeRef,
value: node.serialize()
});
next();
};
// l.debug(() => 'Starting stream');
this._started = true;
this.trie._findDbNodes(onFound, onDone);
}
}
-59
View File
@@ -1,59 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { Trie$Base } from '../types';
// @ts-ignore FIXME, we need to properly check the full file
import { Readable } from 'readable-stream';
// import logger from '@polkadot/util/logger';
// import u8aToHex from '@polkadot/util/u8a/toHex';
import { nibblesToU8a } from '../nibbles';
// const l = logger('trie/read');
export default class TrieReadStream extends Readable {
trie: Trie$Base;
next: null; // FIXME
// @ts-ignore FIXME, we need to properly check the full file
_started: boolean;
constructor (trie: Trie$Base) {
super({ objectMode: true });
this.trie = trie;
this.next = null;
this._started = false;
}
_read () {
if (this._started) {
return;
}
const onDone = () => {
// l.debug(() => 'onDone');
// @ts-ignore FIXME, we need to properly check the full file
this.push(null);
};
// @ts-ignore FIXME, we need to properly check the full file
const onFound = (nodeRef, node, nibbles, next) => {
// l.debug(() => ['onFound', nibbles, u8aToHex(node.value)]);
// @ts-ignore FIXME, we need to properly check the full file
this.push({
key: nibblesToU8a(nibbles),
value: node.value
});
next();
};
// l.debug(() => 'Starting stream');
this._started = true;
this.trie._findValueNodes(onFound, onDone);
}
}
-37
View File
@@ -1,37 +0,0 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
export type Callback = (error?: Error | null, result?: any) => void;
// NOTE This is actually DecodedRlp = Array<Uint8Array | DecodedRlp>, but since the decoder is relatively simple, we get away with the definition below
export type DecodedRlp = Array<Uint8Array | Array<Uint8Array>>;
export type HashFn = (data: Uint8Array) => Uint8Array;
export type Trie$Node$Type = 'branch' | 'extention' | 'leaf';
export interface Trie$Node {
hash: () => Uint8Array,
serialize: () => Uint8Array,
raw: DecodedRlp,
type: Trie$Node$Type,
value: Uint8Array
}
type Trie$Find$OnResult = (nodeRef: Uint8Array, node: Trie$Node, nibbles: Array<number>, next: () => void) => void;
export type Trie$Base = {
_findDbNodes (onResult: Trie$Find$OnResult, onDone: Callback): void,
_findValueNodes (onResult: Trie$Find$OnResult, onDone: Callback): void
};
export type SemaphorePromise = (fn: () => Promise<any>) => Promise<any>;
type NodeFactory = {
fromBranch (rlp: DecodedRlp): Trie$Node
fromRaw (rlp: DecodedRlp): Trie$Node,
fromExtention (key: Uint8Array, value: Uint8Array): Trie$Node,
fromLeaf (key: Uint8Array, value: Uint8Array): Trie$Node
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
export enum NodeType {
EMPTY = 0,
LEAF = 1,
EXTENSION = 2,
BRANCH = 3
}
export type NodeEmpty = null;
export type NodeEncoded = Uint8Array;
export type NodeEncodedOrEmpty = NodeEncoded | NodeEmpty;
export type NodeBranch = [
NodeEncodedOrEmpty, NodeEncodedOrEmpty, NodeEncodedOrEmpty, NodeEncodedOrEmpty,
NodeEncodedOrEmpty, NodeEncodedOrEmpty, NodeEncodedOrEmpty, NodeEncodedOrEmpty,
NodeEncodedOrEmpty, NodeEncodedOrEmpty, NodeEncodedOrEmpty, NodeEncodedOrEmpty,
NodeEncodedOrEmpty, NodeEncodedOrEmpty, NodeEncodedOrEmpty, NodeEncodedOrEmpty,
NodeEncodedOrEmpty
];
export type EncodedPath = Uint8Array | null;
export type NodeKv = [EncodedPath, NodeEncodedOrEmpty];
export type NodeNotEmpty = NodeKv | NodeBranch;
export type Node = NodeEmpty | NodeNotEmpty;
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { Node, NodeBranch, NodeKv, NodeEmpty, NodeType } from '../types';
import isNull from '@polkadot/util/is/null';
import { getNodeType } from './node';
export function isEmptyNode (node: Node): node is NodeEmpty {
return isNull(node);
}
export function isKvNode (node: Node): node is NodeKv {
return !isEmptyNode(node) && node.length === 2;
}
export function isExtensionNode (node: Node): node is NodeKv {
return getNodeType(node) === NodeType.EXTENSION;
}
export function isLeafNode (node: Node): node is NodeKv {
return getNodeType(node) === NodeType.LEAF;
}
export function isBranchNode (node: Node): node is NodeBranch {
return !isEmptyNode(node) && node.length === 17;
}
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import isU8a from '@polkadot/util/is/u8a';
export default function isRawNode (node: Array<number> | Buffer | Uint8Array) {
return Array.isArray(node) && !isU8a(node);
}
+78
View File
@@ -0,0 +1,78 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import isNull from '@polkadot/util/is/null';
import { EncodedPath, Node } from '../types';
import { isKvNode } from './is';
import { addNibblesTerminator, decodeNibbles, encodeNibbles, removeNibblesTerminator } from './nibbles';
export function extractKey (node: Node): Uint8Array {
if (!isKvNode(node)) {
throw new Error('Can only extract keys from KV branches');
}
const [prefixedKey] = node;
return removeNibblesTerminator(
decodeNibbles(prefixedKey)
);
}
export function keyEquals (key: Uint8Array | null, test: Uint8Array | null): boolean {
if (isNull(key) && isNull(test)) {
return true;
} else if (isNull(key) || isNull(test) || (key.length !== test.length)) {
return false;
}
return keyStartsWith(key, test);
}
export function keyStartsWith (key: Uint8Array | null, partial: Uint8Array | null): boolean {
if (isNull(key) && isNull(partial)) {
return true;
} else if (isNull(key) || isNull(partial) || (key.length < partial.length)) {
return false;
}
for (let index = 0; index < partial.length; index++) {
if (key[index] !== partial[index]) {
return false;
}
}
return true;
}
export function computeExtensionKey (nibbles: Uint8Array): EncodedPath {
return encodeNibbles(nibbles);
}
export function computeLeafKey (nibbles: Uint8Array): EncodedPath {
return encodeNibbles(
addNibblesTerminator(nibbles)
);
}
export function getCommonPrefixLength (left: Uint8Array, right: Uint8Array): number {
for (let index = 0; index < left.length && index < right.length; index++) {
if (left[index] !== right[index]) {
return index;
}
}
return Math.min(left.length, right.length);
}
export function consumeCommonPrefix (left: Uint8Array, right: Uint8Array): [Uint8Array, Uint8Array, Uint8Array] {
const length = getCommonPrefixLength(left, right);
return [
left.subarray(0, length),
left.subarray(length),
right.subarray(length)
];
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { NodeEncodedOrEmpty, NodeEncoded } from '../types';
import fromNibbles from '@polkadot/trie-hash/util/fromNibbles';
import toNibbles from '@polkadot/trie-hash/util/asNibbles';
const HP_FLAG_2 = 2;
const HP_FLAG_0 = 0;
const NIBBLE_TERMINATOR = 16;
const NEEDS_TERMINATOR = [HP_FLAG_2, HP_FLAG_2 + 1];
const IS_ODD_LENGTH = [HP_FLAG_0 + 1, HP_FLAG_2 + 1];
export function isNibblesTerminated (nibbles: Uint8Array): boolean {
return nibbles[nibbles.length - 1] === NIBBLE_TERMINATOR;
}
export function addNibblesTerminator (nibbles: Uint8Array): Uint8Array {
if (isNibblesTerminated(nibbles)) {
return nibbles;
}
const terminated = new Uint8Array(nibbles.length + 1);
terminated.set(nibbles);
terminated[nibbles.length] = NIBBLE_TERMINATOR;
return terminated;
}
export function removeNibblesTerminator (nibbles: Uint8Array): Uint8Array {
if (isNibblesTerminated(nibbles)) {
return nibbles.subarray(0, nibbles.length - 1);
}
return nibbles;
}
export function decodeNibbles (value: NodeEncodedOrEmpty): Uint8Array {
const nibblesWithFlag = toNibbles(value);
const [flag] = nibblesWithFlag;
const rawNibbles = IS_ODD_LENGTH.includes(flag)
? nibblesWithFlag.subarray(1)
: nibblesWithFlag.subarray(2);
return NEEDS_TERMINATOR.includes(flag)
? addNibblesTerminator(rawNibbles)
: rawNibbles;
}
export function encodeNibbles (nibbles: Uint8Array): NodeEncoded {
const flag = isNibblesTerminated(nibbles)
? HP_FLAG_2
: HP_FLAG_0;
const rawNibbles = removeNibblesTerminator(nibbles);
const prefix = rawNibbles.length % 2
? [flag + 1]
: [flag, 0];
const prefixed = new Uint8Array(prefix.length + rawNibbles.length);
prefixed.set(prefix);
prefixed.set(rawNibbles, prefix.length);
return fromNibbles(prefixed);
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { Node, NodeType, NodeNotEmpty } from '../types';
import isNull from '@polkadot/util/is/null';
import nodeDecode from '@polkadot/util-rlp/decode';
import nodeEncode from '@polkadot/util-rlp/encode';
import { isBranchNode, isEmptyNode, isKvNode } from './is';
import { decodeNibbles, isNibblesTerminated } from './nibbles';
export function getNodeType (node: Node): NodeType {
if (isEmptyNode(node)) {
return NodeType.EMPTY;
} else if (isKvNode(node)) {
const [key] = node;
const nibbles = decodeNibbles(key);
return isNibblesTerminated(nibbles)
? NodeType.LEAF
: NodeType.EXTENSION;
} else if (isBranchNode(node)) {
return NodeType.BRANCH;
}
throw new Error(`Unable to determine node type`);
}
export function decodeNode (encoded: Uint8Array | Node): Node {
if (isNull(encoded) || encoded.length === 0) {
return null;
} else if (Array.isArray(encoded)) {
return encoded;
}
const decoded = (nodeDecode(encoded) as NodeNotEmpty)
.map((value) =>
value && value.length
? value
: null
);
return decoded as NodeNotEmpty;
}
export function encodeNode (node: Node): Uint8Array {
return nodeEncode(node);
}
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { DecodedRlp } from '../types';
import assert from '@polkadot/util/assert';
import isU8a from '@polkadot/util/is/u8a';
import u8aToHex from '@polkadot/util/u8a/toHex';
export default function rlpToString (rlp: Uint8Array | DecodedRlp): string {
if (Array.isArray(rlp)) {
return `[ ${rlp.map(rlpToString).join(', ')} ]`;
}
assert(isU8a(rlp), `Expected Array | Uint8Array inside rlp, found type '${typeof rlp}'`);
return u8aToHex(rlp);
}
-27
View File
@@ -1,27 +0,0 @@
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
import { SemaphorePromise } from '../types';
import semaphore from 'semaphore';
export default function semaphorePromise (max: number): SemaphorePromise {
const _semaphore = semaphore(max);
return (fn: () => Promise<any>): Promise<any> => {
return new Promise((resolve, reject) => {
_semaphore.take(async () => {
try {
const result = await fn();
_semaphore.leave();
resolve(result);
} catch (error) {
_semaphore.leave();
reject(error);
}
});
});
};
}
-53
View File
@@ -1,53 +0,0 @@
// Copyright 2015-2018 authors & contributors of https://github.com/ethereumjs/merkle-patricia-tree
// Copyright 2017-2018 @polkadot/trie-db authors & contributors
// This software may be modified and distributed under the terms
// of the MPL-2.0 license. See the LICENSE file for details.
type Task$Fn = (taskCb: (value: any) => any) => any;
type Task = {
fn: Task$Fn,
priority: number,
reject: (error: Error) => void,
resolve: (value: any) => void
};
// https://github.com/ethereumjs/merkle-patricia-tree/pull/38
export default function taskExecutor (maxPoolSize: number) {
let currentPoolSize = 0;
const queue: Array<Task> = [];
const execute = (task: Task | undefined) => {
if (task === undefined) {
return;
}
if (currentPoolSize < maxPoolSize) {
currentPoolSize++;
task.fn((value: any): void => {
currentPoolSize--;
task.resolve(value);
if (queue.length > 0) {
queue.sort((a, b) =>
b.priority - a.priority
);
execute(queue.shift());
}
});
return;
}
queue.push(task);
};
return (priority: number, fn: Task$Fn): Promise<any> => {
return new Promise((resolve, reject) => {
execute({ fn, reject, resolve, priority });
});
};
}