@feelyourprotocol/mpt 8141.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +448 -0
- package/dist/cjs/constructors.d.ts +12 -0
- package/dist/cjs/constructors.d.ts.map +1 -0
- package/dist/cjs/constructors.js +57 -0
- package/dist/cjs/constructors.js.map +1 -0
- package/dist/cjs/db/checkpointDB.d.ts +87 -0
- package/dist/cjs/db/checkpointDB.d.ts.map +1 -0
- package/dist/cjs/db/checkpointDB.js +258 -0
- package/dist/cjs/db/checkpointDB.js.map +1 -0
- package/dist/cjs/db/index.d.ts +2 -0
- package/dist/cjs/db/index.d.ts.map +1 -0
- package/dist/cjs/db/index.js +18 -0
- package/dist/cjs/db/index.js.map +1 -0
- package/dist/cjs/index.d.ts +8 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +24 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/mpt.d.ts +261 -0
- package/dist/cjs/mpt.d.ts.map +1 -0
- package/dist/cjs/mpt.js +900 -0
- package/dist/cjs/mpt.js.map +1 -0
- package/dist/cjs/node/branch.d.ts +14 -0
- package/dist/cjs/node/branch.d.ts.map +1 -0
- package/dist/cjs/node/branch.js +52 -0
- package/dist/cjs/node/branch.js.map +1 -0
- package/dist/cjs/node/extension.d.ts +7 -0
- package/dist/cjs/node/extension.d.ts.map +1 -0
- package/dist/cjs/node/extension.js +14 -0
- package/dist/cjs/node/extension.js.map +1 -0
- package/dist/cjs/node/extensionOrLeafNodeBase.d.ts +15 -0
- package/dist/cjs/node/extensionOrLeafNodeBase.d.ts.map +1 -0
- package/dist/cjs/node/extensionOrLeafNodeBase.js +42 -0
- package/dist/cjs/node/extensionOrLeafNodeBase.js.map +1 -0
- package/dist/cjs/node/index.d.ts +5 -0
- package/dist/cjs/node/index.d.ts.map +1 -0
- package/dist/cjs/node/index.js +21 -0
- package/dist/cjs/node/index.js.map +1 -0
- package/dist/cjs/node/leaf.d.ts +7 -0
- package/dist/cjs/node/leaf.d.ts.map +1 -0
- package/dist/cjs/node/leaf.js +14 -0
- package/dist/cjs/node/leaf.js.map +1 -0
- package/dist/cjs/node/util.d.ts +8 -0
- package/dist/cjs/node/util.d.ts.map +1 -0
- package/dist/cjs/node/util.js +38 -0
- package/dist/cjs/node/util.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/proof/index.d.ts +3 -0
- package/dist/cjs/proof/index.d.ts.map +1 -0
- package/dist/cjs/proof/index.js +19 -0
- package/dist/cjs/proof/index.js.map +1 -0
- package/dist/cjs/proof/proof.d.ts +41 -0
- package/dist/cjs/proof/proof.d.ts.map +1 -0
- package/dist/cjs/proof/proof.js +119 -0
- package/dist/cjs/proof/proof.js.map +1 -0
- package/dist/cjs/proof/range.d.ts +35 -0
- package/dist/cjs/proof/range.d.ts.map +1 -0
- package/dist/cjs/proof/range.js +456 -0
- package/dist/cjs/proof/range.js.map +1 -0
- package/dist/cjs/types.d.ts +110 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/util/asyncWalk.d.ts +20 -0
- package/dist/cjs/util/asyncWalk.d.ts.map +1 -0
- package/dist/cjs/util/asyncWalk.js +50 -0
- package/dist/cjs/util/asyncWalk.js.map +1 -0
- package/dist/cjs/util/encoding.d.ts +31 -0
- package/dist/cjs/util/encoding.d.ts.map +1 -0
- package/dist/cjs/util/encoding.js +200 -0
- package/dist/cjs/util/encoding.js.map +1 -0
- package/dist/cjs/util/genesisState.d.ts +6 -0
- package/dist/cjs/util/genesisState.d.ts.map +1 -0
- package/dist/cjs/util/genesisState.js +45 -0
- package/dist/cjs/util/genesisState.js.map +1 -0
- package/dist/cjs/util/hex.d.ts +20 -0
- package/dist/cjs/util/hex.d.ts.map +1 -0
- package/dist/cjs/util/hex.js +48 -0
- package/dist/cjs/util/hex.js.map +1 -0
- package/dist/cjs/util/index.d.ts +4 -0
- package/dist/cjs/util/index.d.ts.map +1 -0
- package/dist/cjs/util/index.js +20 -0
- package/dist/cjs/util/index.js.map +1 -0
- package/dist/cjs/util/nibbles.d.ts +30 -0
- package/dist/cjs/util/nibbles.d.ts.map +1 -0
- package/dist/cjs/util/nibbles.js +79 -0
- package/dist/cjs/util/nibbles.js.map +1 -0
- package/dist/cjs/util/walkController.d.ts +72 -0
- package/dist/cjs/util/walkController.d.ts.map +1 -0
- package/dist/cjs/util/walkController.js +138 -0
- package/dist/cjs/util/walkController.js.map +1 -0
- package/dist/esm/constructors.d.ts +12 -0
- package/dist/esm/constructors.d.ts.map +1 -0
- package/dist/esm/constructors.js +53 -0
- package/dist/esm/constructors.js.map +1 -0
- package/dist/esm/db/checkpointDB.d.ts +87 -0
- package/dist/esm/db/checkpointDB.d.ts.map +1 -0
- package/dist/esm/db/checkpointDB.js +254 -0
- package/dist/esm/db/checkpointDB.js.map +1 -0
- package/dist/esm/db/index.d.ts +2 -0
- package/dist/esm/db/index.d.ts.map +1 -0
- package/dist/esm/db/index.js +2 -0
- package/dist/esm/db/index.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mpt.d.ts +261 -0
- package/dist/esm/mpt.d.ts.map +1 -0
- package/dist/esm/mpt.js +897 -0
- package/dist/esm/mpt.js.map +1 -0
- package/dist/esm/node/branch.d.ts +14 -0
- package/dist/esm/node/branch.d.ts.map +1 -0
- package/dist/esm/node/branch.js +48 -0
- package/dist/esm/node/branch.js.map +1 -0
- package/dist/esm/node/extension.d.ts +7 -0
- package/dist/esm/node/extension.d.ts.map +1 -0
- package/dist/esm/node/extension.js +10 -0
- package/dist/esm/node/extension.js.map +1 -0
- package/dist/esm/node/extensionOrLeafNodeBase.d.ts +15 -0
- package/dist/esm/node/extensionOrLeafNodeBase.d.ts.map +1 -0
- package/dist/esm/node/extensionOrLeafNodeBase.js +38 -0
- package/dist/esm/node/extensionOrLeafNodeBase.js.map +1 -0
- package/dist/esm/node/index.d.ts +5 -0
- package/dist/esm/node/index.d.ts.map +1 -0
- package/dist/esm/node/index.js +5 -0
- package/dist/esm/node/index.js.map +1 -0
- package/dist/esm/node/leaf.d.ts +7 -0
- package/dist/esm/node/leaf.d.ts.map +1 -0
- package/dist/esm/node/leaf.js +10 -0
- package/dist/esm/node/leaf.js.map +1 -0
- package/dist/esm/node/util.d.ts +8 -0
- package/dist/esm/node/util.d.ts.map +1 -0
- package/dist/esm/node/util.js +33 -0
- package/dist/esm/node/util.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/proof/index.d.ts +3 -0
- package/dist/esm/proof/index.d.ts.map +1 -0
- package/dist/esm/proof/index.js +3 -0
- package/dist/esm/proof/index.js.map +1 -0
- package/dist/esm/proof/proof.d.ts +41 -0
- package/dist/esm/proof/proof.d.ts.map +1 -0
- package/dist/esm/proof/proof.js +113 -0
- package/dist/esm/proof/proof.js.map +1 -0
- package/dist/esm/proof/range.d.ts +35 -0
- package/dist/esm/proof/range.d.ts.map +1 -0
- package/dist/esm/proof/range.js +453 -0
- package/dist/esm/proof/range.js.map +1 -0
- package/dist/esm/types.d.ts +110 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +3 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/util/asyncWalk.d.ts +20 -0
- package/dist/esm/util/asyncWalk.d.ts.map +1 -0
- package/dist/esm/util/asyncWalk.js +47 -0
- package/dist/esm/util/asyncWalk.js.map +1 -0
- package/dist/esm/util/encoding.d.ts +31 -0
- package/dist/esm/util/encoding.d.ts.map +1 -0
- package/dist/esm/util/encoding.js +188 -0
- package/dist/esm/util/encoding.js.map +1 -0
- package/dist/esm/util/genesisState.d.ts +6 -0
- package/dist/esm/util/genesisState.d.ts.map +1 -0
- package/dist/esm/util/genesisState.js +42 -0
- package/dist/esm/util/genesisState.js.map +1 -0
- package/dist/esm/util/hex.d.ts +20 -0
- package/dist/esm/util/hex.d.ts.map +1 -0
- package/dist/esm/util/hex.js +43 -0
- package/dist/esm/util/hex.js.map +1 -0
- package/dist/esm/util/index.d.ts +4 -0
- package/dist/esm/util/index.d.ts.map +1 -0
- package/dist/esm/util/index.js +4 -0
- package/dist/esm/util/index.js.map +1 -0
- package/dist/esm/util/nibbles.d.ts +30 -0
- package/dist/esm/util/nibbles.d.ts.map +1 -0
- package/dist/esm/util/nibbles.js +73 -0
- package/dist/esm/util/nibbles.js.map +1 -0
- package/dist/esm/util/walkController.d.ts +72 -0
- package/dist/esm/util/walkController.d.ts.map +1 -0
- package/dist/esm/util/walkController.js +134 -0
- package/dist/esm/util/walkController.js.map +1 -0
- package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
- package/package.json +85 -0
- package/src/constructors.ts +71 -0
- package/src/db/checkpointDB.ts +298 -0
- package/src/db/index.ts +1 -0
- package/src/index.ts +7 -0
- package/src/mpt.ts +1090 -0
- package/src/node/branch.ts +60 -0
- package/src/node/extension.ts +13 -0
- package/src/node/extensionOrLeafNodeBase.ts +54 -0
- package/src/node/index.ts +4 -0
- package/src/node/leaf.ts +13 -0
- package/src/node/util.ts +35 -0
- package/src/proof/index.ts +2 -0
- package/src/proof/proof.ts +135 -0
- package/src/proof/range.ts +542 -0
- package/src/types.ts +151 -0
- package/src/util/asyncWalk.ts +60 -0
- package/src/util/encoding.ts +209 -0
- package/src/util/genesisState.ts +52 -0
- package/src/util/hex.ts +47 -0
- package/src/util/index.ts +3 -0
- package/src/util/nibbles.ts +80 -0
- package/src/util/walkController.ts +172 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { EthereumJSErrorWithoutCode, equalsBytes } from '@feelyourprotocol/util'
|
|
2
|
+
import { keccak_256 } from '@noble/hashes/sha3.js'
|
|
3
|
+
|
|
4
|
+
import { createMPTFromProof } from '../index.ts'
|
|
5
|
+
import { MerklePatriciaTrie } from '../mpt.ts'
|
|
6
|
+
import { BranchMPTNode, ExtensionMPTNode, LeafMPTNode } from '../node/index.ts'
|
|
7
|
+
import { bytesToNibbles, nibblesCompare, nibblesTypeToPackedBytes } from '../util/nibbles.ts'
|
|
8
|
+
|
|
9
|
+
import type { HashKeysFunction, MPTNode, Nibbles } from '../types.ts'
|
|
10
|
+
|
|
11
|
+
// reference: https://github.com/ethereum/go-ethereum/blob/20356e57b119b4e70ce47665a71964434e15200d/trie/proof.go
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* unset will remove all nodes to the left or right of the target key(decided by `removeLeft`).
|
|
15
|
+
* @param trie - trie object.
|
|
16
|
+
* @param parent - parent node, it can be `null`.
|
|
17
|
+
* @param child - child node.
|
|
18
|
+
* @param key - target nibbles.
|
|
19
|
+
* @param pos - key position.
|
|
20
|
+
* @param removeLeft - remove all nodes to the left or right of the target key.
|
|
21
|
+
* @param stack - a stack of modified nodes.
|
|
22
|
+
* @returns The end position of key.
|
|
23
|
+
*/
|
|
24
|
+
async function unset(
|
|
25
|
+
trie: MerklePatriciaTrie,
|
|
26
|
+
parent: MPTNode,
|
|
27
|
+
child: MPTNode | null,
|
|
28
|
+
key: Nibbles,
|
|
29
|
+
pos: number,
|
|
30
|
+
removeLeft: boolean,
|
|
31
|
+
stack: MPTNode[],
|
|
32
|
+
): Promise<number> {
|
|
33
|
+
if (child instanceof BranchMPTNode) {
|
|
34
|
+
/**
|
|
35
|
+
* This node is a branch node,
|
|
36
|
+
* remove all branches on the left or right
|
|
37
|
+
*/
|
|
38
|
+
if (removeLeft) {
|
|
39
|
+
for (let i = 0; i < key[pos]; i++) {
|
|
40
|
+
child.setBranch(i, null)
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
for (let i = key[pos] + 1; i < 16; i++) {
|
|
44
|
+
child.setBranch(i, null)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// record this node on the stack
|
|
49
|
+
stack.push(child)
|
|
50
|
+
|
|
51
|
+
// continue to the next node
|
|
52
|
+
const next = child.getBranch(key[pos])
|
|
53
|
+
const _child = next && (await trie.lookupNode(next))
|
|
54
|
+
return unset(trie, child, _child, key, pos + 1, removeLeft, stack)
|
|
55
|
+
} else if (child instanceof ExtensionMPTNode || child instanceof LeafMPTNode) {
|
|
56
|
+
/**
|
|
57
|
+
* This node is an extension node or lead node,
|
|
58
|
+
* if node._nibbles is less or greater than the target key,
|
|
59
|
+
* remove self from parent
|
|
60
|
+
*/
|
|
61
|
+
if (
|
|
62
|
+
key.length - pos < child.keyLength() ||
|
|
63
|
+
nibblesCompare(child._nibbles, key.slice(pos, pos + child.keyLength())) !== 0
|
|
64
|
+
) {
|
|
65
|
+
if (removeLeft) {
|
|
66
|
+
if (nibblesCompare(child._nibbles, key.slice(pos)) < 0) {
|
|
67
|
+
;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
if (nibblesCompare(child._nibbles, key.slice(pos)) > 0) {
|
|
71
|
+
;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return pos - 1
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (child instanceof LeafMPTNode) {
|
|
78
|
+
// This node is a leaf node, directly remove it from parent
|
|
79
|
+
;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
|
|
80
|
+
return pos - 1
|
|
81
|
+
} else {
|
|
82
|
+
const _child = await trie.lookupNode(child.value())
|
|
83
|
+
if (_child instanceof LeafMPTNode) {
|
|
84
|
+
// The child of this node is leaf node, remove it from parent too
|
|
85
|
+
;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
|
|
86
|
+
return pos - 1
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// record this node on the stack
|
|
90
|
+
stack.push(child)
|
|
91
|
+
|
|
92
|
+
// continue to the next node
|
|
93
|
+
return unset(trie, child, _child, key, pos + child.keyLength(), removeLeft, stack)
|
|
94
|
+
}
|
|
95
|
+
} else if (child === null) {
|
|
96
|
+
return pos - 1
|
|
97
|
+
} else {
|
|
98
|
+
throw EthereumJSErrorWithoutCode('invalid node')
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* unsetInternal will remove all nodes between `left` and `right` (including `left` and `right`)
|
|
104
|
+
* @param trie - trie object.
|
|
105
|
+
* @param left - left nibbles.
|
|
106
|
+
* @param right - right nibbles.
|
|
107
|
+
* @returns Is it an empty trie.
|
|
108
|
+
*/
|
|
109
|
+
async function unsetInternal(
|
|
110
|
+
trie: MerklePatriciaTrie,
|
|
111
|
+
left: Nibbles,
|
|
112
|
+
right: Nibbles,
|
|
113
|
+
): Promise<boolean> {
|
|
114
|
+
// Key position
|
|
115
|
+
let pos = 0
|
|
116
|
+
// Parent node
|
|
117
|
+
let parent: MPTNode | null = null
|
|
118
|
+
// Current node
|
|
119
|
+
let node: MPTNode | null = await trie.lookupNode(trie.root())
|
|
120
|
+
let shortForkLeft!: number
|
|
121
|
+
let shortForkRight!: number
|
|
122
|
+
// A stack of modified nodes.
|
|
123
|
+
const stack: MPTNode[] = []
|
|
124
|
+
|
|
125
|
+
// 1. Find the fork point of `left` and `right`
|
|
126
|
+
|
|
127
|
+
while (true) {
|
|
128
|
+
if (node instanceof ExtensionMPTNode || node instanceof LeafMPTNode) {
|
|
129
|
+
// record this node on the stack
|
|
130
|
+
stack.push(node)
|
|
131
|
+
|
|
132
|
+
if (left.length - pos < node.keyLength()) {
|
|
133
|
+
shortForkLeft = nibblesCompare(left.slice(pos), node._nibbles)
|
|
134
|
+
} else {
|
|
135
|
+
shortForkLeft = nibblesCompare(left.slice(pos, pos + node.keyLength()), node._nibbles)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (right.length - pos < node.keyLength()) {
|
|
139
|
+
shortForkRight = nibblesCompare(right.slice(pos), node._nibbles)
|
|
140
|
+
} else {
|
|
141
|
+
shortForkRight = nibblesCompare(right.slice(pos, pos + node.keyLength()), node._nibbles)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// If one of `left` and `right` is not equal to node._nibbles, it means we found the fork point
|
|
145
|
+
if (shortForkLeft !== 0 || shortForkRight !== 0) {
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (node instanceof LeafMPTNode) {
|
|
150
|
+
// it shouldn't happen
|
|
151
|
+
throw EthereumJSErrorWithoutCode('invalid node')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// continue to the next node
|
|
155
|
+
parent = node
|
|
156
|
+
pos += node.keyLength()
|
|
157
|
+
node = await trie.lookupNode(node.value())
|
|
158
|
+
} else if (node instanceof BranchMPTNode) {
|
|
159
|
+
// record this node on the stack
|
|
160
|
+
stack.push(node)
|
|
161
|
+
|
|
162
|
+
const leftNode = node.getBranch(left[pos])
|
|
163
|
+
const rightNode = node.getBranch(right[pos])
|
|
164
|
+
|
|
165
|
+
// One of `left` and `right` is `null`, stop searching
|
|
166
|
+
if (leftNode === null || rightNode === null) {
|
|
167
|
+
break
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Stop searching if `left` and `right` are not equal
|
|
171
|
+
if (!(leftNode instanceof Uint8Array)) {
|
|
172
|
+
if (rightNode instanceof Uint8Array) {
|
|
173
|
+
break
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (leftNode.length !== rightNode.length) {
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let abort = false
|
|
181
|
+
for (let i = 0; i < leftNode.length; i++) {
|
|
182
|
+
if (!equalsBytes(leftNode[i], rightNode[i])) {
|
|
183
|
+
abort = true
|
|
184
|
+
break
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (abort) {
|
|
188
|
+
break
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
if (!(rightNode instanceof Uint8Array)) {
|
|
192
|
+
break
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!equalsBytes(leftNode, rightNode)) {
|
|
196
|
+
break
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// continue to the next node
|
|
201
|
+
parent = node
|
|
202
|
+
node = await trie.lookupNode(leftNode)
|
|
203
|
+
pos += 1
|
|
204
|
+
} else {
|
|
205
|
+
throw EthereumJSErrorWithoutCode('invalid node')
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 2. Starting from the fork point, delete all nodes between `left` and `right`
|
|
210
|
+
|
|
211
|
+
const saveStack = (key: Nibbles, stack: MPTNode[]) => {
|
|
212
|
+
return trie.saveStack(key, stack, [])
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (node instanceof ExtensionMPTNode || node instanceof LeafMPTNode) {
|
|
216
|
+
/**
|
|
217
|
+
* There can have these five scenarios:
|
|
218
|
+
* - both proofs are less than the trie path => no valid range
|
|
219
|
+
* - both proofs are greater than the trie path => no valid range
|
|
220
|
+
* - left proof is less and right proof is greater => valid range, unset the entire trie
|
|
221
|
+
* - left proof points to the trie node, but right proof is greater => valid range, unset left node
|
|
222
|
+
* - right proof points to the trie node, but left proof is less => valid range, unset right node
|
|
223
|
+
*/
|
|
224
|
+
const removeSelfFromParentAndSaveStack = async (key: Nibbles) => {
|
|
225
|
+
if (parent === null) {
|
|
226
|
+
return true
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
stack.pop()
|
|
230
|
+
;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
|
|
231
|
+
await saveStack(key.slice(0, pos - 1), stack)
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (shortForkLeft === -1 && shortForkRight === -1) {
|
|
236
|
+
throw EthereumJSErrorWithoutCode('invalid range')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (shortForkLeft === 1 && shortForkRight === 1) {
|
|
240
|
+
throw EthereumJSErrorWithoutCode('invalid range')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (shortForkLeft !== 0 && shortForkRight !== 0) {
|
|
244
|
+
// Unset the entire trie
|
|
245
|
+
return removeSelfFromParentAndSaveStack(left)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Unset left node
|
|
249
|
+
if (shortForkRight !== 0) {
|
|
250
|
+
if (node instanceof LeafMPTNode) {
|
|
251
|
+
return removeSelfFromParentAndSaveStack(left)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const child = await trie.lookupNode(node._value)
|
|
255
|
+
if (child instanceof LeafMPTNode) {
|
|
256
|
+
return removeSelfFromParentAndSaveStack(left)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const endPos = await unset(trie, node, child, left.slice(pos), node.keyLength(), false, stack)
|
|
260
|
+
await saveStack(left.slice(0, pos + endPos), stack)
|
|
261
|
+
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Unset right node
|
|
266
|
+
if (shortForkLeft !== 0) {
|
|
267
|
+
if (node instanceof LeafMPTNode) {
|
|
268
|
+
return removeSelfFromParentAndSaveStack(right)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const child = await trie.lookupNode(node._value)
|
|
272
|
+
if (child instanceof LeafMPTNode) {
|
|
273
|
+
return removeSelfFromParentAndSaveStack(right)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const endPos = await unset(trie, node, child, right.slice(pos), node.keyLength(), true, stack)
|
|
277
|
+
await saveStack(right.slice(0, pos + endPos), stack)
|
|
278
|
+
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return false
|
|
283
|
+
} else if (node instanceof BranchMPTNode) {
|
|
284
|
+
// Unset all internal nodes in the forkPoint
|
|
285
|
+
for (let i = left[pos] + 1; i < right[pos]; i++) {
|
|
286
|
+
node.setBranch(i, null)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
{
|
|
290
|
+
/**
|
|
291
|
+
* `stack` records the path from root to fork point.
|
|
292
|
+
* Since we need to unset both left and right nodes once,
|
|
293
|
+
* we need to make a copy here.
|
|
294
|
+
*/
|
|
295
|
+
const _stack = [...stack]
|
|
296
|
+
const next = node.getBranch(left[pos])
|
|
297
|
+
const child = next && (await trie.lookupNode(next))
|
|
298
|
+
const endPos = await unset(trie, node, child, left.slice(pos), 1, false, _stack)
|
|
299
|
+
await saveStack(left.slice(0, pos + endPos), _stack)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
{
|
|
303
|
+
const _stack = [...stack]
|
|
304
|
+
const next = node.getBranch(right[pos])
|
|
305
|
+
const child = next && (await trie.lookupNode(next))
|
|
306
|
+
const endPos = await unset(trie, node, child, right.slice(pos), 1, true, _stack)
|
|
307
|
+
await saveStack(right.slice(0, pos + endPos), _stack)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return false
|
|
311
|
+
} else {
|
|
312
|
+
throw EthereumJSErrorWithoutCode('invalid node')
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Verifies a proof and return the verified trie.
|
|
318
|
+
* @param rootHash - root hash.
|
|
319
|
+
* @param key - target key.
|
|
320
|
+
* @param proof - proof node list.
|
|
321
|
+
* @throws If proof is found to be invalid.
|
|
322
|
+
* @returns The value from the key, or null if valid proof of non-existence.
|
|
323
|
+
*/
|
|
324
|
+
async function verifyMPTWithMerkleProof(
|
|
325
|
+
rootHash: Uint8Array,
|
|
326
|
+
key: Uint8Array,
|
|
327
|
+
proof: Uint8Array[],
|
|
328
|
+
useKeyHashingFunction: HashKeysFunction,
|
|
329
|
+
): Promise<{ value: Uint8Array | null; trie: MerklePatriciaTrie }> {
|
|
330
|
+
const proofTrie = await createMPTFromProof(proof, {
|
|
331
|
+
root: rootHash,
|
|
332
|
+
useKeyHashingFunction,
|
|
333
|
+
})
|
|
334
|
+
try {
|
|
335
|
+
const value = await proofTrie.get(key, true)
|
|
336
|
+
return {
|
|
337
|
+
trie: proofTrie,
|
|
338
|
+
value,
|
|
339
|
+
}
|
|
340
|
+
} catch (err: any) {
|
|
341
|
+
if (err.message === 'Missing node in DB') {
|
|
342
|
+
throw EthereumJSErrorWithoutCode('Invalid proof provided')
|
|
343
|
+
} else {
|
|
344
|
+
throw err
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* hasRightElement returns the indicator whether there exists more elements
|
|
351
|
+
* on the right side of the given path
|
|
352
|
+
* @param trie - trie object.
|
|
353
|
+
* @param key - given path.
|
|
354
|
+
*/
|
|
355
|
+
async function hasRightElement(trie: MerklePatriciaTrie, key: Nibbles): Promise<boolean> {
|
|
356
|
+
let pos = 0
|
|
357
|
+
let node: MPTNode | null = await trie.lookupNode(trie.root())
|
|
358
|
+
while (node !== null) {
|
|
359
|
+
if (node instanceof BranchMPTNode) {
|
|
360
|
+
for (let i = key[pos] + 1; i < 16; i++) {
|
|
361
|
+
if (node.getBranch(i) !== null) {
|
|
362
|
+
return true
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const next = node.getBranch(key[pos])
|
|
367
|
+
node = next && (await trie.lookupNode(next))
|
|
368
|
+
pos += 1
|
|
369
|
+
} else if (node instanceof ExtensionMPTNode) {
|
|
370
|
+
if (
|
|
371
|
+
key.length - pos < node.keyLength() ||
|
|
372
|
+
nibblesCompare(node._nibbles, key.slice(pos, pos + node.keyLength())) !== 0
|
|
373
|
+
) {
|
|
374
|
+
return nibblesCompare(node._nibbles, key.slice(pos)) > 0
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
pos += node.keyLength()
|
|
378
|
+
node = await trie.lookupNode(node._value)
|
|
379
|
+
} else if (node instanceof LeafMPTNode) {
|
|
380
|
+
return false
|
|
381
|
+
} else {
|
|
382
|
+
throw EthereumJSErrorWithoutCode('invalid node')
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return false
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Checks whether the given leaf nodes and edge proof can prove the given trie leaves range is matched with the specific root.
|
|
390
|
+
*
|
|
391
|
+
* A range proof is a proof that includes the encoded trie nodes from the root node to leaf node for one or more branches of a trie,
|
|
392
|
+
* allowing an entire range of leaf nodes to be validated. This is useful in applications such as snap sync where contiguous ranges
|
|
393
|
+
* of state trie data is received and validated for constructing world state, locally.
|
|
394
|
+
*
|
|
395
|
+
* There are four situations:
|
|
396
|
+
*
|
|
397
|
+
* - All elements proof. In this case the proof can be null, but the range should
|
|
398
|
+
* be all the leaves in the trie.
|
|
399
|
+
*
|
|
400
|
+
* - One element proof. In this case no matter the edge proof is a non-existent
|
|
401
|
+
* proof or not, we can always verify the correctness of the proof.
|
|
402
|
+
*
|
|
403
|
+
* - Zero element proof. In this case a single non-existent proof is enough to prove.
|
|
404
|
+
* Besides, if there are still some other leaves available on the right side, then
|
|
405
|
+
* an error will be returned.
|
|
406
|
+
*
|
|
407
|
+
* - Two edge elements proof. In this case two existent or non-existent proof(first and last) should be provided.
|
|
408
|
+
*
|
|
409
|
+
* NOTE: Currently only supports verification when the length of firstKey and lastKey are the same.
|
|
410
|
+
*
|
|
411
|
+
* @param rootHash - root hash of state trie this proof is being verified against.
|
|
412
|
+
* @param firstKey - first key of range being proven.
|
|
413
|
+
* @param lastKey - last key of range being proven.
|
|
414
|
+
* @param keys - key list of leaf data being proven.
|
|
415
|
+
* @param values - value list of leaf data being proven, one-to-one correspondence with keys.
|
|
416
|
+
* @param proof - proof node list, if all-elements-proof where no proof is needed, proof should be null, and both `firstKey` and `lastKey` must be null as well
|
|
417
|
+
* @param opts - optional, the opts may include a custom hashing function to use with the trie for proof verification
|
|
418
|
+
* @returns a flag to indicate whether there exists more trie node in the trie
|
|
419
|
+
*/
|
|
420
|
+
export async function verifyMerkleRangeProof(
|
|
421
|
+
rootHash: Uint8Array,
|
|
422
|
+
firstKeyRaw: Uint8Array | null,
|
|
423
|
+
lastKeyRaw: Uint8Array | null,
|
|
424
|
+
keysRaw: Uint8Array[],
|
|
425
|
+
values: Uint8Array[],
|
|
426
|
+
proof: Uint8Array[] | null,
|
|
427
|
+
useKeyHashingFunction: HashKeysFunction = keccak_256,
|
|
428
|
+
): Promise<boolean> {
|
|
429
|
+
// Convert Uint8Array keys to nibbles
|
|
430
|
+
const firstKey = firstKeyRaw !== null ? bytesToNibbles(firstKeyRaw) : null
|
|
431
|
+
const lastKey = lastKeyRaw !== null ? bytesToNibbles(lastKeyRaw) : null
|
|
432
|
+
const keys = keysRaw.map(bytesToNibbles)
|
|
433
|
+
|
|
434
|
+
if (keys.length !== values.length) {
|
|
435
|
+
throw EthereumJSErrorWithoutCode('invalid keys length or values length')
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Make sure the keys are in order
|
|
439
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
440
|
+
if (nibblesCompare(keys[i], keys[i + 1]) >= 0) {
|
|
441
|
+
throw EthereumJSErrorWithoutCode('invalid keys order')
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// Make sure all values are present
|
|
445
|
+
for (const value of values) {
|
|
446
|
+
if (value.length === 0) {
|
|
447
|
+
throw EthereumJSErrorWithoutCode('invalid values')
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// All elements proof
|
|
452
|
+
if (proof === null && firstKey === null && lastKey === null) {
|
|
453
|
+
const trie = new MerklePatriciaTrie({ useKeyHashingFunction })
|
|
454
|
+
for (let i = 0; i < keys.length; i++) {
|
|
455
|
+
await trie.put(nibblesTypeToPackedBytes(keys[i]), values[i])
|
|
456
|
+
}
|
|
457
|
+
if (!equalsBytes(rootHash, trie.root())) {
|
|
458
|
+
throw EthereumJSErrorWithoutCode('invalid all elements proof: root mismatch')
|
|
459
|
+
}
|
|
460
|
+
return false
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (proof !== null && firstKey !== null && lastKey === null) {
|
|
464
|
+
// Zero element proof
|
|
465
|
+
if (keys.length === 0) {
|
|
466
|
+
const { trie, value } = await verifyMPTWithMerkleProof(
|
|
467
|
+
rootHash,
|
|
468
|
+
nibblesTypeToPackedBytes(firstKey),
|
|
469
|
+
proof,
|
|
470
|
+
useKeyHashingFunction,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
if (value !== null || (await hasRightElement(trie, firstKey))) {
|
|
474
|
+
throw EthereumJSErrorWithoutCode('invalid zero element proof: value mismatch')
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return false
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (proof === null || firstKey === null || lastKey === null) {
|
|
482
|
+
throw EthereumJSErrorWithoutCode(
|
|
483
|
+
'invalid all elements proof: proof, firstKey, lastKey must be null at the same time',
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// One element proof
|
|
488
|
+
if (keys.length === 1 && nibblesCompare(firstKey, lastKey) === 0) {
|
|
489
|
+
const { trie, value } = await verifyMPTWithMerkleProof(
|
|
490
|
+
rootHash,
|
|
491
|
+
nibblesTypeToPackedBytes(firstKey),
|
|
492
|
+
proof,
|
|
493
|
+
useKeyHashingFunction,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
if (nibblesCompare(firstKey, keys[0]) !== 0) {
|
|
497
|
+
throw EthereumJSErrorWithoutCode(
|
|
498
|
+
'invalid one element proof: firstKey should be equal to keys[0]',
|
|
499
|
+
)
|
|
500
|
+
}
|
|
501
|
+
if (value === null || !equalsBytes(value, values[0])) {
|
|
502
|
+
throw EthereumJSErrorWithoutCode('invalid one element proof: value mismatch')
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return hasRightElement(trie, firstKey)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Two edge elements proof
|
|
509
|
+
if (nibblesCompare(firstKey, lastKey) >= 0) {
|
|
510
|
+
throw EthereumJSErrorWithoutCode(
|
|
511
|
+
'invalid two edge elements proof: firstKey should be less than lastKey',
|
|
512
|
+
)
|
|
513
|
+
}
|
|
514
|
+
if (firstKey.length !== lastKey.length) {
|
|
515
|
+
throw EthereumJSErrorWithoutCode(
|
|
516
|
+
'invalid two edge elements proof: the length of firstKey should be equal to the length of lastKey',
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const trie = await createMPTFromProof(proof, {
|
|
521
|
+
useKeyHashingFunction,
|
|
522
|
+
root: rootHash,
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
// Remove all nodes between two edge proofs
|
|
526
|
+
const empty = await unsetInternal(trie, firstKey, lastKey)
|
|
527
|
+
if (empty) {
|
|
528
|
+
trie.root(trie.EMPTY_TRIE_ROOT)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Put all elements to the trie
|
|
532
|
+
for (let i = 0; i < keys.length; i++) {
|
|
533
|
+
await trie.put(nibblesTypeToPackedBytes(keys[i]), values[i])
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Compare rootHash
|
|
537
|
+
if (!equalsBytes(trie.root(), rootHash)) {
|
|
538
|
+
throw EthereumJSErrorWithoutCode('invalid two edge elements proof: root mismatch')
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return hasRightElement(trie, keys[keys.length - 1])
|
|
542
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { utf8ToBytes } from '@feelyourprotocol/util'
|
|
2
|
+
|
|
3
|
+
import type { DB, ValueEncoding } from '@feelyourprotocol/util'
|
|
4
|
+
import type { BranchMPTNode, ExtensionMPTNode, LeafMPTNode } from './node/index.ts'
|
|
5
|
+
import type { WalkController } from './util/walkController.ts'
|
|
6
|
+
|
|
7
|
+
export type MPTNode = BranchMPTNode | ExtensionMPTNode | LeafMPTNode
|
|
8
|
+
|
|
9
|
+
export type Nibbles = number[]
|
|
10
|
+
|
|
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 RawExtensionMPTNode = [Uint8Array, Uint8Array]
|
|
16
|
+
export type RawLeafMPTNode = [Uint8Array, Uint8Array]
|
|
17
|
+
|
|
18
|
+
// Branch and extension nodes might store
|
|
19
|
+
// hash to next node, or a raw node if its length < 32
|
|
20
|
+
export type NodeReferenceOrRawMPTNode = Uint8Array | RawExtensionMPTNode | RawLeafMPTNode
|
|
21
|
+
|
|
22
|
+
export type BranchMPTNodeBranchValue = NodeReferenceOrRawMPTNode | null
|
|
23
|
+
|
|
24
|
+
export type Proof = Uint8Array[]
|
|
25
|
+
|
|
26
|
+
export interface CommonInterface {
|
|
27
|
+
customCrypto: {
|
|
28
|
+
keccak256?: (msg: Uint8Array) => Uint8Array
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Path {
|
|
33
|
+
node: MPTNode | null
|
|
34
|
+
remaining: Nibbles
|
|
35
|
+
stack: MPTNode[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type FoundNodeFunction = (
|
|
39
|
+
nodeRef: NodeReferenceOrRawMPTNode,
|
|
40
|
+
node: MPTNode | null,
|
|
41
|
+
key: Nibbles,
|
|
42
|
+
walkController: WalkController,
|
|
43
|
+
) => void
|
|
44
|
+
|
|
45
|
+
export type HashKeysFunction = (msg: Uint8Array) => Uint8Array
|
|
46
|
+
|
|
47
|
+
export interface MPTOpts {
|
|
48
|
+
/**
|
|
49
|
+
* A database instance.
|
|
50
|
+
*/
|
|
51
|
+
db?: DB<string, string | Uint8Array>
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A `Uint8Array` for the root of a previously stored trie
|
|
55
|
+
*/
|
|
56
|
+
root?: Uint8Array
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create as a secure MerklePatriciaTrie where the keys are automatically hashed using the
|
|
60
|
+
* **keccak_256** hash function or alternatively the custom hash function provided.
|
|
61
|
+
* Default: `false`
|
|
62
|
+
*
|
|
63
|
+
* This is the flavor of the MerklePatriciaTrie which is used in production Ethereum networks
|
|
64
|
+
* like Ethereum Mainnet.
|
|
65
|
+
*
|
|
66
|
+
* Note: This functionality has been refactored along the v5 release and was before
|
|
67
|
+
* provided as a separate inherited class `SecureTrie`. Just replace with `Trie`
|
|
68
|
+
* instantiation with `useKeyHashing` set to `true`.
|
|
69
|
+
*/
|
|
70
|
+
useKeyHashing?: boolean
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Hash function used for hashing trie node and securing key.
|
|
74
|
+
*/
|
|
75
|
+
useKeyHashingFunction?: HashKeysFunction
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Add a prefix to the trie node keys
|
|
79
|
+
*
|
|
80
|
+
* (potential performance benefits if multiple tries are stored within the same DB,
|
|
81
|
+
* e.g. all storage tries being stored in the outer account state DB)
|
|
82
|
+
*/
|
|
83
|
+
keyPrefix?: Uint8Array
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* ValueEncoding of the database (the values which are `put`/`get` in the db are of this type). Defaults to `string`
|
|
87
|
+
*/
|
|
88
|
+
valueEncoding?: ValueEncoding
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Store the root inside the database after every `write` operation
|
|
92
|
+
*/
|
|
93
|
+
useRootPersistence?: boolean
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Flag to prune the trie. When set to `true`, each time a value is overridden,
|
|
97
|
+
* unreachable nodes will be pruned (deleted) from the trie
|
|
98
|
+
*/
|
|
99
|
+
useNodePruning?: boolean
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* LRU cache for trie nodes to allow for faster node retrieval.
|
|
103
|
+
*
|
|
104
|
+
* Default: 0 (deactivated)
|
|
105
|
+
*/
|
|
106
|
+
cacheSize?: number
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @feelyourprotocol/common `Common` instance (an alternative to passing in a `customHashingFunction`)
|
|
110
|
+
*/
|
|
111
|
+
common?: CommonInterface
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type MPTOptsWithDefaults = MPTOpts & {
|
|
115
|
+
useKeyHashing: boolean
|
|
116
|
+
useKeyHashingFunction: HashKeysFunction
|
|
117
|
+
useRootPersistence: boolean
|
|
118
|
+
useNodePruning: boolean
|
|
119
|
+
cacheSize: number
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface TrieShallowCopyOpts {
|
|
123
|
+
keyPrefix?: Uint8Array
|
|
124
|
+
cacheSize?: number
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface CheckpointDBOpts {
|
|
128
|
+
/**
|
|
129
|
+
* A database instance.
|
|
130
|
+
*/
|
|
131
|
+
db: DB<string, string | Uint8Array>
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* ValueEncoding of the database (the values which are `put`/`get` in the db are of this type). Defaults to `string`
|
|
135
|
+
*/
|
|
136
|
+
valueEncoding?: ValueEncoding
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Cache size (default: 0)
|
|
140
|
+
*/
|
|
141
|
+
cacheSize?: number
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type Checkpoint = {
|
|
145
|
+
// We cannot use a Uint8Array => Uint8Array map directly. If you create two Uint8Arrays with the same internal value,
|
|
146
|
+
// then when setting a value on the Map, it actually creates two indices.
|
|
147
|
+
keyValueMap: Map<string, Uint8Array | undefined>
|
|
148
|
+
root: Uint8Array
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const ROOT_DB_KEY = utf8ToBytes('__root__')
|