Skip to content

Commit

Permalink
feat(tx-builder): support incomplete MPTrees
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Dec 22, 2022
1 parent ae6b15a commit 115bcf5
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 6 deletions.
28 changes: 24 additions & 4 deletions src/utils/mptree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
MissingNodeInTreeError,
UnknownPathNibbleError,
UnknownNodeLengthError,
ArgumentError,
InternalError,
} from './errors';

enum NodeType {
Expand All @@ -37,6 +39,12 @@ export type MPTreeBinary = [Buffer, Array<[Buffer, Buffer[]]>];
export default class MPTree {
readonly #rootHash: string;

#isComplete = true;

get isComplete(): boolean {
return this.#isComplete;
}

readonly #nodes: { [key: string]: Buffer[] };

static #nodeHash(node: Input): string {
Expand All @@ -54,7 +62,13 @@ export default class MPTree {
binary[1].map((node) => [node[0].toString('hex'), node[1]]),
);

if (this.#nodes[this.#rootHash] == null) throw new MissingNodeInTreeError('Can\'t find a node by root hash');
if (this.#nodes[this.#rootHash] == null) {
if (Object.keys(this.#nodes).length !== 0) {
throw new MissingNodeInTreeError('Can\'t find a node by root hash');
}
this.#isComplete = false;
return;
}
Object.entries(this.#nodes).forEach(([key, node]) => {
if (MPTree.#nodeHash(node) !== key) throw new MerkleTreeHashMismatchError();
const { type, payload } = MPTree.#parseNode(node);
Expand All @@ -64,9 +78,10 @@ export default class MPTree {
.slice(0, 16)
.filter((n) => n.length)
.forEach((n) => {
if (this.#nodes[n.toString('hex')] == null) {
throw new MissingNodeInTreeError('Can\'t find a node by hash in branch node');
if (n.length !== 32) {
throw new ArgumentError('MPTree branch item length', 32, n.length);
}
if (this.#nodes[n.toString('hex')] == null) this.#isComplete = false;
});
break;
case NodeType.Extension:
Expand Down Expand Up @@ -124,7 +139,12 @@ export default class MPTree {
let searchFrom = this.#rootHash;
let key = _key;
while (true) { // eslint-disable-line no-constant-condition
const { type, payload, path } = MPTree.#parseNode(this.#nodes[searchFrom]);
const node = this.#nodes[searchFrom];
if (node == null) {
if (!this.isComplete) return undefined;
throw new InternalError('Can\'t find node in complete tree');
}
const { type, payload, path } = MPTree.#parseNode(node);
switch (type) {
case NodeType.Branch:
if (key.length === 0) return payload[16];
Expand Down
5 changes: 3 additions & 2 deletions test/unit/mptree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ describe('Merkle Patricia Tree', () => {
};

it('can deserialize', () => {
expect(() => new MPTree(binary)).to.not.throw();
const tree = new MPTree(binary);
expect(tree.isComplete).to.be.equal(true);
});

it('can serialize', () => {
Expand Down Expand Up @@ -64,6 +65,6 @@ describe('Merkle Patricia Tree', () => {
const wrongBranchNodeLength = hexToTreeBinary('f9013fa0f6322076ba7e911690cf61563126879df81851ae11cd1c7423931ae77672c8dbf9011bf850a0056232c6f764553f472dacd7bba764e4d630adce971e4437dcf07421e20d6cf3eea03e2e29b62366a6b1e363ebf174fce8e4d9ad61abdc2dde65e3f74923dcd629c48ccb0a010087038d7ea4c67ffcf850a065657db43209ef7d57acb7aaf2e2c38f8828f9d425e4bec0d7de5bfa26496c61eea03269a8e17fffe495df7b47bf0ffb94897e1060baf3192e99978d91010325b62d8ccb0a010087038d7ea4c68004f875a0f6322076ba7e911690cf61563126879df81851ae11cd1c7423931ae77672c8dbf85280a065657db43209ef7d57acb7aaf2e2c38f8828f9d425e4bec0d7de5bfa26496c618080a0056232c6f764553f472dacd7bba764e4d630adce971e4437dcf07421e20d6cf380808080808080808080808080');
expect(() => new MPTree(wrongBranchNodeLength)).to.throw(UnknownNodeLengthError, 'Unknown node length: 18');
const wrongBranchNodeHash = hexToTreeBinary('f9013ea02275f012f78197935cca6322773642620a62f9a4af9cc8ff3245e3755245fcfcf9011af850a0056232c6f764553f472dacd7bba764e4d630adce971e4437dcf07421e20d6cf3eea03e2e29b62366a6b1e363ebf174fce8e4d9ad61abdc2dde65e3f74923dcd629c48ccb0a010087038d7ea4c67ffcf850a065657db43209ef7d57acb7aaf2e2c38f8828f9d425e4bec0d7de5bfa26496c61eea03269a8e17fffe495df7b47bf0ffb94897e1060baf3192e99978d91010325b62d8ccb0a010087038d7ea4c68004f874a02275f012f78197935cca6322773642620a62f9a4af9cc8ff3245e3755245fcfcf85180a065657db432ffef7d57acb7aaf2e2c38f8828f9d425e4bec0d7de5bfa26496c618080a0056232c6f764553f472dacd7bba764e4d630adce971e4437dcf07421e20d6cf3808080808080808080808080');
expect(() => new MPTree(wrongBranchNodeHash)).to.throw(MissingNodeInTreeError, 'Can\'t find a node by hash in branch node');
expect(new MPTree(wrongBranchNodeHash).isComplete).to.equal(false);
});
});

0 comments on commit 115bcf5

Please sign in to comment.