Skip to content

Commit f1fda27

Browse files
authored
trie: improve node typings and class architecture (#3708)
* trie: add RawNode types and replace EmbeddedNode type * trie: implement new types in BranchNode class * trie: improve extension and leaf node class architecture * trie: small improvements * trie: improve raw nodes comments * trie: improve comments for raw nodes
1 parent 657cdad commit f1fda27

File tree

7 files changed

+50
-56
lines changed

7 files changed

+50
-56
lines changed

packages/trie/src/node/branch.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { RLP } from '@ethereumjs/rlp'
22

3-
import type { EmbeddedNode } from '../types.js'
3+
import type { BranchNodeBranchValue, NodeReferenceOrRawNode } from '../types.js'
44

55
export class BranchNode {
6-
_branches: (EmbeddedNode | null)[]
6+
_branches: BranchNodeBranchValue[]
77
_value: Uint8Array | null
88

99
constructor() {
@@ -26,19 +26,19 @@ export class BranchNode {
2626
return this._value && this._value.length > 0 ? this._value : null
2727
}
2828

29-
setBranch(i: number, v: EmbeddedNode | null) {
29+
setBranch(i: number, v: BranchNodeBranchValue) {
3030
this._branches[i] = v
3131
}
3232

33-
raw(): (EmbeddedNode | null)[] {
33+
raw(): BranchNodeBranchValue[] {
3434
return [...this._branches, this._value]
3535
}
3636

3737
serialize(): Uint8Array {
38-
return RLP.encode(this.raw() as Uint8Array[])
38+
return RLP.encode(this.raw())
3939
}
4040

41-
getBranch(i: number) {
41+
getBranch(i: number): BranchNodeBranchValue {
4242
const b = this._branches[i]
4343
if (b !== null && b.length > 0) {
4444
return b
@@ -47,8 +47,8 @@ export class BranchNode {
4747
}
4848
}
4949

50-
getChildren(): [number, EmbeddedNode][] {
51-
const children: [number, EmbeddedNode][] = []
50+
getChildren(): [number, NodeReferenceOrRawNode][] {
51+
const children: [number, NodeReferenceOrRawNode][] = []
5252
for (let i = 0; i < 16; i++) {
5353
const b = this._branches[i]
5454
if (b !== null && b.length > 0) {
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { addHexPrefix } from '../util/hex.js'
1+
import { ExtensionOrLeafNodeBase } from './extensionOrLeafNodeBase.js'
22

3-
import { Node } from './node.js'
3+
import type { Nibbles, RawExtensionNode } from '../types.js'
44

5-
import type { Nibbles } from '../types.js'
6-
7-
export class ExtensionNode extends Node {
5+
export class ExtensionNode extends ExtensionOrLeafNodeBase {
86
constructor(nibbles: Nibbles, value: Uint8Array) {
97
super(nibbles, value, false)
108
}
119

12-
static encodeKey(key: Nibbles): Nibbles {
13-
return addHexPrefix(key, false)
10+
raw(): RawExtensionNode {
11+
return super.raw()
1412
}
1513
}

packages/trie/src/node/node.ts renamed to packages/trie/src/node/extensionOrLeafNodeBase.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,27 @@ import { RLP } from '@ethereumjs/rlp'
33
import { addHexPrefix, removeHexPrefix } from '../util/hex.js'
44
import { nibblesTypeToPackedBytes } from '../util/nibbles.js'
55

6-
import type { Nibbles } from '../types.js'
6+
import type { Nibbles, RawExtensionNode, RawLeafNode } from '../types.js'
77

8-
export class Node {
8+
export abstract class ExtensionOrLeafNodeBase {
99
_nibbles: Nibbles
1010
_value: Uint8Array
11-
_terminator: boolean
11+
_isLeaf: boolean
1212

13-
constructor(nibbles: Nibbles, value: Uint8Array, terminator: boolean) {
13+
constructor(nibbles: Nibbles, value: Uint8Array, isLeaf: boolean) {
1414
this._nibbles = nibbles
1515
this._value = value
16-
this._terminator = terminator
16+
this._isLeaf = isLeaf
1717
}
1818

1919
static decodeKey(key: Nibbles): Nibbles {
2020
return removeHexPrefix(key)
2121
}
2222

23+
encodedKey(): Nibbles {
24+
return addHexPrefix(this._nibbles.slice(0), this._isLeaf)
25+
}
26+
2327
key(k?: Nibbles): Nibbles {
2428
if (k !== undefined) {
2529
this._nibbles = k
@@ -40,11 +44,7 @@ export class Node {
4044
return this._value
4145
}
4246

43-
encodedKey(): Nibbles {
44-
return addHexPrefix(this._nibbles.slice(0), this._terminator)
45-
}
46-
47-
raw(): [Uint8Array, Uint8Array] {
47+
raw(): RawExtensionNode | RawLeafNode {
4848
return [nibblesTypeToPackedBytes(this.encodedKey()), this._value]
4949
}
5050

packages/trie/src/node/leaf.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { addHexPrefix } from '../util/hex.js'
1+
import { ExtensionOrLeafNodeBase } from './extensionOrLeafNodeBase.js'
22

3-
import { Node } from './node.js'
3+
import type { Nibbles, RawLeafNode } from '../types.js'
44

5-
import type { Nibbles } from '../types.js'
6-
7-
export class LeafNode extends Node {
5+
export class LeafNode extends ExtensionOrLeafNodeBase {
86
constructor(nibbles: Nibbles, value: Uint8Array) {
97
super(nibbles, value, true)
108
}
119

12-
static encodeKey(key: Nibbles): Nibbles {
13-
return addHexPrefix(key, true)
10+
raw(): RawLeafNode {
11+
return super.raw()
1412
}
1513
}

packages/trie/src/trie.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ import { bytesToNibbles, matchingNibbleLength, nibblesTypeToPackedBytes } from '
3434
import { WalkController } from './util/walkController.js'
3535

3636
import type {
37-
EmbeddedNode,
37+
BranchNodeBranchValue,
3838
FoundNodeFunction,
3939
Nibbles,
40+
NodeReferenceOrRawNode,
4041
Path,
4142
TrieNode,
4243
TrieOpts,
@@ -357,7 +358,7 @@ export class Trie {
357358
debugString +=
358359
branchNode instanceof Uint8Array
359360
? `NodeHash: ${bytesToHex(branchNode)}`
360-
: `Raw_Node: ${branchNode!.toString()}`
361+
: `Raw_Node: ${branchNode.toString()}`
361362
}
362363

363364
this.debug(debugString, ['find_path', 'branch_node'])
@@ -564,8 +565,8 @@ export class Trie {
564565
}
565566

566567
if (
567-
matchingNibbleLength(lastNode.key(), key.slice(l)) === lastNode.key().length &&
568-
keyRemainder.length === 0
568+
keyRemainder.length === 0 &&
569+
matchingNibbleLength(lastNode.key(), key.slice(l)) === lastNode.key().length
569570
) {
570571
matchLeaf = true
571572
}
@@ -574,7 +575,7 @@ export class Trie {
574575
if (matchLeaf) {
575576
// just updating a found value
576577
lastNode.value(value)
577-
stack.push(lastNode as TrieNode)
578+
stack.push(lastNode)
578579
} else if (lastNode instanceof BranchNode) {
579580
stack.push(lastNode)
580581
if (keyRemainder.length !== 0) {
@@ -609,8 +610,8 @@ export class Trie {
609610
if (lastKey.length !== 0 || lastNode instanceof LeafNode) {
610611
// shrinking extension or leaf
611612
lastNode.key(lastKey)
612-
const formattedNode = this._formatNode(lastNode, false, toSave)
613-
newBranchNode.setBranch(branchKey, formattedNode as EmbeddedNode)
613+
const formattedNode = this._formatNode(lastNode, false, toSave) as NodeReferenceOrRawNode
614+
newBranchNode.setBranch(branchKey, formattedNode)
614615
} else {
615616
// remove extension or attaching
616617
this._formatNode(lastNode, false, toSave, true)
@@ -703,7 +704,7 @@ export class Trie {
703704

704705
let key = bytesToNibbles(k)
705706

706-
if (!parentNode) {
707+
if (parentNode === undefined) {
707708
// the root here has to be a leaf.
708709
this.root(this.EMPTY_TRIE_ROOT)
709710
return
@@ -728,7 +729,7 @@ export class Trie {
728729

729730
// nodes on the branch
730731
// count the number of nodes on the branch
731-
const branchNodes: [number, EmbeddedNode][] = lastNode.getChildren()
732+
const branchNodes: [number, NodeReferenceOrRawNode][] = lastNode.getChildren()
732733

733734
// if there is only one branch node left, collapse the branch node
734735
if (branchNodes.length === 1) {
@@ -753,10 +754,8 @@ export class Trie {
753754

754755
// look up node
755756
const foundNode = await this.lookupNode(branchNode)
756-
// if (foundNode) {
757757
key = processBranchNode(key, branchNodeKey, foundNode, parentNode as TrieNode, stack)
758758
await this.saveStack(key, stack, opStack)
759-
// }
760759
} else {
761760
// simple removing a leaf and recalculation the stack
762761
if (parentNode) {
@@ -819,7 +818,7 @@ export class Trie {
819818
topLevel: boolean,
820819
opStack: BatchDBOp[],
821820
remove: boolean = false,
822-
): Uint8Array | (EmbeddedNode | null)[] {
821+
): Uint8Array | NodeReferenceOrRawNode | BranchNodeBranchValue[] {
823822
const encoded = node.serialize()
824823

825824
if (encoded.length >= 32 || topLevel) {

packages/trie/src/types.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,18 @@ export type TrieNode = BranchNode | ExtensionNode | LeafNode
88

99
export type Nibbles = number[]
1010

11+
// A raw node refers to the non-serialized, array form of the node
12+
// A raw extension node is a 2-item node, where the first item is the encoded path to the next node, and the second item is the reference to the next node
13+
// A raw leaf node is a 2-item node, where the first item is the remaining path to the leaf node, and the second item is the value
14+
// To learn more: https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/#optimization
15+
export type RawExtensionNode = [Uint8Array, Uint8Array]
16+
export type RawLeafNode = [Uint8Array, Uint8Array]
17+
1118
// Branch and extension nodes might store
12-
// hash to next node, or embed it if its len < 32
13-
export type EmbeddedNode = Uint8Array | Uint8Array[]
19+
// hash to next node, or a raw node if its length < 32
20+
export type NodeReferenceOrRawNode = Uint8Array | RawExtensionNode | RawLeafNode
21+
22+
export type BranchNodeBranchValue = NodeReferenceOrRawNode | null
1423

1524
export type Proof = Uint8Array[]
1625

packages/trie/src/util/nibbles.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,3 @@ export function matchingNibbleLength(nib1: Nibbles, nib2: Nibbles): number {
8181
}
8282
return i
8383
}
84-
85-
/**
86-
* Compare two nibble array keys.
87-
* @param keyA
88-
* @param keyB
89-
*/
90-
export function doKeysMatch(keyA: Nibbles, keyB: Nibbles): boolean {
91-
const length = matchingNibbleLength(keyA, keyB)
92-
return length === keyA.length && length === keyB.length
93-
}

0 commit comments

Comments
 (0)