@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.
Files changed (204) hide show
  1. package/README.md +448 -0
  2. package/dist/cjs/constructors.d.ts +12 -0
  3. package/dist/cjs/constructors.d.ts.map +1 -0
  4. package/dist/cjs/constructors.js +57 -0
  5. package/dist/cjs/constructors.js.map +1 -0
  6. package/dist/cjs/db/checkpointDB.d.ts +87 -0
  7. package/dist/cjs/db/checkpointDB.d.ts.map +1 -0
  8. package/dist/cjs/db/checkpointDB.js +258 -0
  9. package/dist/cjs/db/checkpointDB.js.map +1 -0
  10. package/dist/cjs/db/index.d.ts +2 -0
  11. package/dist/cjs/db/index.d.ts.map +1 -0
  12. package/dist/cjs/db/index.js +18 -0
  13. package/dist/cjs/db/index.js.map +1 -0
  14. package/dist/cjs/index.d.ts +8 -0
  15. package/dist/cjs/index.d.ts.map +1 -0
  16. package/dist/cjs/index.js +24 -0
  17. package/dist/cjs/index.js.map +1 -0
  18. package/dist/cjs/mpt.d.ts +261 -0
  19. package/dist/cjs/mpt.d.ts.map +1 -0
  20. package/dist/cjs/mpt.js +900 -0
  21. package/dist/cjs/mpt.js.map +1 -0
  22. package/dist/cjs/node/branch.d.ts +14 -0
  23. package/dist/cjs/node/branch.d.ts.map +1 -0
  24. package/dist/cjs/node/branch.js +52 -0
  25. package/dist/cjs/node/branch.js.map +1 -0
  26. package/dist/cjs/node/extension.d.ts +7 -0
  27. package/dist/cjs/node/extension.d.ts.map +1 -0
  28. package/dist/cjs/node/extension.js +14 -0
  29. package/dist/cjs/node/extension.js.map +1 -0
  30. package/dist/cjs/node/extensionOrLeafNodeBase.d.ts +15 -0
  31. package/dist/cjs/node/extensionOrLeafNodeBase.d.ts.map +1 -0
  32. package/dist/cjs/node/extensionOrLeafNodeBase.js +42 -0
  33. package/dist/cjs/node/extensionOrLeafNodeBase.js.map +1 -0
  34. package/dist/cjs/node/index.d.ts +5 -0
  35. package/dist/cjs/node/index.d.ts.map +1 -0
  36. package/dist/cjs/node/index.js +21 -0
  37. package/dist/cjs/node/index.js.map +1 -0
  38. package/dist/cjs/node/leaf.d.ts +7 -0
  39. package/dist/cjs/node/leaf.d.ts.map +1 -0
  40. package/dist/cjs/node/leaf.js +14 -0
  41. package/dist/cjs/node/leaf.js.map +1 -0
  42. package/dist/cjs/node/util.d.ts +8 -0
  43. package/dist/cjs/node/util.d.ts.map +1 -0
  44. package/dist/cjs/node/util.js +38 -0
  45. package/dist/cjs/node/util.js.map +1 -0
  46. package/dist/cjs/package.json +3 -0
  47. package/dist/cjs/proof/index.d.ts +3 -0
  48. package/dist/cjs/proof/index.d.ts.map +1 -0
  49. package/dist/cjs/proof/index.js +19 -0
  50. package/dist/cjs/proof/index.js.map +1 -0
  51. package/dist/cjs/proof/proof.d.ts +41 -0
  52. package/dist/cjs/proof/proof.d.ts.map +1 -0
  53. package/dist/cjs/proof/proof.js +119 -0
  54. package/dist/cjs/proof/proof.js.map +1 -0
  55. package/dist/cjs/proof/range.d.ts +35 -0
  56. package/dist/cjs/proof/range.d.ts.map +1 -0
  57. package/dist/cjs/proof/range.js +456 -0
  58. package/dist/cjs/proof/range.js.map +1 -0
  59. package/dist/cjs/types.d.ts +110 -0
  60. package/dist/cjs/types.d.ts.map +1 -0
  61. package/dist/cjs/types.js +6 -0
  62. package/dist/cjs/types.js.map +1 -0
  63. package/dist/cjs/util/asyncWalk.d.ts +20 -0
  64. package/dist/cjs/util/asyncWalk.d.ts.map +1 -0
  65. package/dist/cjs/util/asyncWalk.js +50 -0
  66. package/dist/cjs/util/asyncWalk.js.map +1 -0
  67. package/dist/cjs/util/encoding.d.ts +31 -0
  68. package/dist/cjs/util/encoding.d.ts.map +1 -0
  69. package/dist/cjs/util/encoding.js +200 -0
  70. package/dist/cjs/util/encoding.js.map +1 -0
  71. package/dist/cjs/util/genesisState.d.ts +6 -0
  72. package/dist/cjs/util/genesisState.d.ts.map +1 -0
  73. package/dist/cjs/util/genesisState.js +45 -0
  74. package/dist/cjs/util/genesisState.js.map +1 -0
  75. package/dist/cjs/util/hex.d.ts +20 -0
  76. package/dist/cjs/util/hex.d.ts.map +1 -0
  77. package/dist/cjs/util/hex.js +48 -0
  78. package/dist/cjs/util/hex.js.map +1 -0
  79. package/dist/cjs/util/index.d.ts +4 -0
  80. package/dist/cjs/util/index.d.ts.map +1 -0
  81. package/dist/cjs/util/index.js +20 -0
  82. package/dist/cjs/util/index.js.map +1 -0
  83. package/dist/cjs/util/nibbles.d.ts +30 -0
  84. package/dist/cjs/util/nibbles.d.ts.map +1 -0
  85. package/dist/cjs/util/nibbles.js +79 -0
  86. package/dist/cjs/util/nibbles.js.map +1 -0
  87. package/dist/cjs/util/walkController.d.ts +72 -0
  88. package/dist/cjs/util/walkController.d.ts.map +1 -0
  89. package/dist/cjs/util/walkController.js +138 -0
  90. package/dist/cjs/util/walkController.js.map +1 -0
  91. package/dist/esm/constructors.d.ts +12 -0
  92. package/dist/esm/constructors.d.ts.map +1 -0
  93. package/dist/esm/constructors.js +53 -0
  94. package/dist/esm/constructors.js.map +1 -0
  95. package/dist/esm/db/checkpointDB.d.ts +87 -0
  96. package/dist/esm/db/checkpointDB.d.ts.map +1 -0
  97. package/dist/esm/db/checkpointDB.js +254 -0
  98. package/dist/esm/db/checkpointDB.js.map +1 -0
  99. package/dist/esm/db/index.d.ts +2 -0
  100. package/dist/esm/db/index.d.ts.map +1 -0
  101. package/dist/esm/db/index.js +2 -0
  102. package/dist/esm/db/index.js.map +1 -0
  103. package/dist/esm/index.d.ts +8 -0
  104. package/dist/esm/index.d.ts.map +1 -0
  105. package/dist/esm/index.js +8 -0
  106. package/dist/esm/index.js.map +1 -0
  107. package/dist/esm/mpt.d.ts +261 -0
  108. package/dist/esm/mpt.d.ts.map +1 -0
  109. package/dist/esm/mpt.js +897 -0
  110. package/dist/esm/mpt.js.map +1 -0
  111. package/dist/esm/node/branch.d.ts +14 -0
  112. package/dist/esm/node/branch.d.ts.map +1 -0
  113. package/dist/esm/node/branch.js +48 -0
  114. package/dist/esm/node/branch.js.map +1 -0
  115. package/dist/esm/node/extension.d.ts +7 -0
  116. package/dist/esm/node/extension.d.ts.map +1 -0
  117. package/dist/esm/node/extension.js +10 -0
  118. package/dist/esm/node/extension.js.map +1 -0
  119. package/dist/esm/node/extensionOrLeafNodeBase.d.ts +15 -0
  120. package/dist/esm/node/extensionOrLeafNodeBase.d.ts.map +1 -0
  121. package/dist/esm/node/extensionOrLeafNodeBase.js +38 -0
  122. package/dist/esm/node/extensionOrLeafNodeBase.js.map +1 -0
  123. package/dist/esm/node/index.d.ts +5 -0
  124. package/dist/esm/node/index.d.ts.map +1 -0
  125. package/dist/esm/node/index.js +5 -0
  126. package/dist/esm/node/index.js.map +1 -0
  127. package/dist/esm/node/leaf.d.ts +7 -0
  128. package/dist/esm/node/leaf.d.ts.map +1 -0
  129. package/dist/esm/node/leaf.js +10 -0
  130. package/dist/esm/node/leaf.js.map +1 -0
  131. package/dist/esm/node/util.d.ts +8 -0
  132. package/dist/esm/node/util.d.ts.map +1 -0
  133. package/dist/esm/node/util.js +33 -0
  134. package/dist/esm/node/util.js.map +1 -0
  135. package/dist/esm/package.json +3 -0
  136. package/dist/esm/proof/index.d.ts +3 -0
  137. package/dist/esm/proof/index.d.ts.map +1 -0
  138. package/dist/esm/proof/index.js +3 -0
  139. package/dist/esm/proof/index.js.map +1 -0
  140. package/dist/esm/proof/proof.d.ts +41 -0
  141. package/dist/esm/proof/proof.d.ts.map +1 -0
  142. package/dist/esm/proof/proof.js +113 -0
  143. package/dist/esm/proof/proof.js.map +1 -0
  144. package/dist/esm/proof/range.d.ts +35 -0
  145. package/dist/esm/proof/range.d.ts.map +1 -0
  146. package/dist/esm/proof/range.js +453 -0
  147. package/dist/esm/proof/range.js.map +1 -0
  148. package/dist/esm/types.d.ts +110 -0
  149. package/dist/esm/types.d.ts.map +1 -0
  150. package/dist/esm/types.js +3 -0
  151. package/dist/esm/types.js.map +1 -0
  152. package/dist/esm/util/asyncWalk.d.ts +20 -0
  153. package/dist/esm/util/asyncWalk.d.ts.map +1 -0
  154. package/dist/esm/util/asyncWalk.js +47 -0
  155. package/dist/esm/util/asyncWalk.js.map +1 -0
  156. package/dist/esm/util/encoding.d.ts +31 -0
  157. package/dist/esm/util/encoding.d.ts.map +1 -0
  158. package/dist/esm/util/encoding.js +188 -0
  159. package/dist/esm/util/encoding.js.map +1 -0
  160. package/dist/esm/util/genesisState.d.ts +6 -0
  161. package/dist/esm/util/genesisState.d.ts.map +1 -0
  162. package/dist/esm/util/genesisState.js +42 -0
  163. package/dist/esm/util/genesisState.js.map +1 -0
  164. package/dist/esm/util/hex.d.ts +20 -0
  165. package/dist/esm/util/hex.d.ts.map +1 -0
  166. package/dist/esm/util/hex.js +43 -0
  167. package/dist/esm/util/hex.js.map +1 -0
  168. package/dist/esm/util/index.d.ts +4 -0
  169. package/dist/esm/util/index.d.ts.map +1 -0
  170. package/dist/esm/util/index.js +4 -0
  171. package/dist/esm/util/index.js.map +1 -0
  172. package/dist/esm/util/nibbles.d.ts +30 -0
  173. package/dist/esm/util/nibbles.d.ts.map +1 -0
  174. package/dist/esm/util/nibbles.js +73 -0
  175. package/dist/esm/util/nibbles.js.map +1 -0
  176. package/dist/esm/util/walkController.d.ts +72 -0
  177. package/dist/esm/util/walkController.d.ts.map +1 -0
  178. package/dist/esm/util/walkController.js +134 -0
  179. package/dist/esm/util/walkController.js.map +1 -0
  180. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
  181. package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
  182. package/package.json +85 -0
  183. package/src/constructors.ts +71 -0
  184. package/src/db/checkpointDB.ts +298 -0
  185. package/src/db/index.ts +1 -0
  186. package/src/index.ts +7 -0
  187. package/src/mpt.ts +1090 -0
  188. package/src/node/branch.ts +60 -0
  189. package/src/node/extension.ts +13 -0
  190. package/src/node/extensionOrLeafNodeBase.ts +54 -0
  191. package/src/node/index.ts +4 -0
  192. package/src/node/leaf.ts +13 -0
  193. package/src/node/util.ts +35 -0
  194. package/src/proof/index.ts +2 -0
  195. package/src/proof/proof.ts +135 -0
  196. package/src/proof/range.ts +542 -0
  197. package/src/types.ts +151 -0
  198. package/src/util/asyncWalk.ts +60 -0
  199. package/src/util/encoding.ts +209 -0
  200. package/src/util/genesisState.ts +52 -0
  201. package/src/util/hex.ts +47 -0
  202. package/src/util/index.ts +3 -0
  203. package/src/util/nibbles.ts +80 -0
  204. package/src/util/walkController.ts +172 -0
@@ -0,0 +1,60 @@
1
+ import { RLP } from '@feelyourprotocol/rlp'
2
+ import { bytesToHex, equalsBytes } from '@feelyourprotocol/util'
3
+
4
+ import { BranchMPTNode } from '../node/branch.ts'
5
+ import { ExtensionMPTNode } from '../node/extension.ts'
6
+
7
+ import type { MerklePatriciaTrie } from '../mpt.ts'
8
+ import type { MPTNode } from '../types.ts'
9
+
10
+ export type NodeFilter = (node: MPTNode, key: number[]) => Promise<boolean>
11
+ export type OnFound = (node: MPTNode, key: number[]) => Promise<any>
12
+
13
+ /**
14
+ * Walk MerklePatriciaTrie via async generator
15
+ * @param nodeHash - The root key to walk on.
16
+ * @param currentKey - The current (partial) key.
17
+ * @param onFound - Called on every node found (before filter)
18
+ * @param filter - Filter nodes yielded by the generator.
19
+ * @param visited - Set of visited nodes
20
+ * @returns AsyncIterable<{ node: MPTNode; currentKey: number[] }>
21
+ * Iterate through nodes with
22
+ * `for await (const { node, currentKey } of trie._walkTrie(root)) { ... }`
23
+ */
24
+ export async function* _walkTrie(
25
+ this: MerklePatriciaTrie,
26
+ nodeHash: Uint8Array,
27
+ currentKey: number[] = [],
28
+ onFound: OnFound = async (_trieNode: MPTNode, _key: number[]) => {},
29
+ filter: NodeFilter = async (_trieNode: MPTNode, _key: number[]) => true,
30
+ visited: Set<string> = new Set<string>(),
31
+ ): AsyncIterable<{ node: MPTNode; currentKey: number[] }> {
32
+ if (equalsBytes(nodeHash, this.EMPTY_TRIE_ROOT)) {
33
+ return
34
+ }
35
+ try {
36
+ const node = await this.lookupNode(nodeHash)
37
+ if (node === undefined || visited.has(bytesToHex(this.hash(node!.serialize())))) {
38
+ return
39
+ }
40
+ visited.add(bytesToHex(this.hash(node!.serialize())))
41
+ await onFound(node!, currentKey)
42
+ if (await filter(node!, currentKey)) {
43
+ yield { node: node!, currentKey }
44
+ }
45
+ if (node instanceof BranchMPTNode) {
46
+ for (const [nibble, childNode] of node._branches.entries()) {
47
+ const nextKey = [...currentKey, nibble]
48
+ const _childNode: Uint8Array =
49
+ childNode instanceof Uint8Array ? childNode : this.hash(RLP.encode(childNode))
50
+ yield* _walkTrie.bind(this)(_childNode, nextKey, onFound, filter, visited)
51
+ }
52
+ } else if (node instanceof ExtensionMPTNode) {
53
+ const childNode = node.value()
54
+ const nextKey = [...currentKey, ...node._nibbles]
55
+ yield* _walkTrie.bind(this)(childNode, nextKey, onFound, filter, visited)
56
+ }
57
+ } catch {
58
+ return
59
+ }
60
+ }
@@ -0,0 +1,209 @@
1
+ import { concatBytes, hexToBytes, unprefixedHexToBytes } from '@feelyourprotocol/util'
2
+
3
+ import { nibblesTypeToPackedBytes } from './nibbles.ts'
4
+
5
+ import type { Nibbles } from '../types.ts'
6
+
7
+ // Reference: https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/
8
+ //
9
+ // MerklePatriciaTrie keys are dealt with in three distinct encodings:
10
+ //
11
+ // KEYBYTES encoding contains the actual key and nothing else. This encoding is the
12
+ // input to most API functions.
13
+ //
14
+ // HEX encoding contains one byte for each nibble of the key and an optional trailing
15
+ // 'terminator' byte of value 0x10 which indicates whether or not the node at the key
16
+ // contains a value. Hex key encoding is used for nodes loaded in memory because it's
17
+ // convenient to access.
18
+ //
19
+ // COMPACT encoding is defined by the Ethereum Yellow Paper (it's called "hex prefix
20
+ // encoding" there) and contains the bytes of the key and a flag. The high nibble of the
21
+ // first byte contains the flag; the lowest bit encoding the oddness of the length and
22
+ // the second-lowest encoding whether the node at the key is a value node. The low nibble
23
+ // of the first byte is zero in the case of an even number of nibbles and the first nibble
24
+ // in the case of an odd number. All remaining nibbles (now an even number) fit properly
25
+ // into the remaining bytes. Compact encoding is used for nodes stored on disk.
26
+
27
+ /**
28
+ *
29
+ * @param nibbles byte sequence
30
+ * @returns boolean indicating if input hex nibble sequence has terminator indicating leaf-node
31
+ * terminator is represented with 16 because a nibble ranges from 0 - 15(f)
32
+ */
33
+ const hasTerminator = (nibbles: Uint8Array) => {
34
+ return nibbles.length > 0 && nibbles[nibbles.length - 1] === 16
35
+ }
36
+
37
+ export const nibblesToBytes = (nibbles: Uint8Array) => {
38
+ const bytes = new Uint8Array(nibbles.length / 2)
39
+ for (let bi = 0, ni = 0; ni < nibbles.length; bi += 1, ni += 2) {
40
+ bytes[bi] = (nibbles[ni] << 4) | nibbles[ni + 1]
41
+ }
42
+
43
+ return bytes
44
+ }
45
+
46
+ export const hexToKeybytes = (hex: Uint8Array) => {
47
+ if (hasTerminator(hex)) {
48
+ hex = hex.subarray(0, hex.length - 1)
49
+ }
50
+ if (hex.length % 2 === 1) {
51
+ throw Error("Can't convert hex key of odd length")
52
+ }
53
+
54
+ return nibblesToBytes(hex)
55
+ }
56
+
57
+ // hex to compact
58
+ export const nibblesToCompactBytes = (nibbles: Uint8Array) => {
59
+ let terminator = 0
60
+ if (hasTerminator(nibbles)) {
61
+ terminator = 1
62
+ // Remove the terminator from the sequence
63
+ nibbles = nibbles.subarray(0, nibbles.length - 1)
64
+ }
65
+ const buf = new Uint8Array(nibbles.length / 2 + 1)
66
+ // Shift the terminator info into the first nibble of buf[0]
67
+ buf[0] = terminator << 5
68
+ // If odd length, then add that flag into the first nibble and put the odd nibble to
69
+ // second part of buf[0] which otherwise will be left padded with a 0
70
+ if ((nibbles.length & 1) === 1) {
71
+ buf[0] |= 1 << 4
72
+ buf[0] |= nibbles[0]
73
+ nibbles = nibbles.subarray(1)
74
+ }
75
+
76
+ // create bytes out of the rest even nibbles
77
+ return concatBytes(buf.subarray(0, 1), nibblesToBytes(nibbles))
78
+ }
79
+
80
+ export const bytesToNibbles = (str: Uint8Array) => {
81
+ const l = str.length * 2 + 1
82
+ const nibbles = new Uint8Array(l)
83
+ for (let i = 0; i < str.length; i++) {
84
+ const b = str[i]
85
+ nibbles[i * 2] = b / 16
86
+ nibbles[i * 2 + 1] = b % 16
87
+ }
88
+ // This will get removed from calling function if the first nibble
89
+ // indicates that terminator is not present
90
+ nibbles[l - 1] = 16
91
+ return nibbles
92
+ }
93
+
94
+ export const compactBytesToNibbles = (compact: Uint8Array) => {
95
+ if (compact.length === 0) {
96
+ return compact
97
+ }
98
+ let base = bytesToNibbles(compact)
99
+ // delete terminator flag if terminator flag was not in first nibble
100
+ if (base[0] < 2) {
101
+ base = base.subarray(0, base.length - 1)
102
+ }
103
+ // chop the terminator nibble and the even padding (if there is one)
104
+ // i.e. chop 2 left nibbles when even else 1 when odd
105
+ const chop = 2 - (base[0] & 1)
106
+ return base.subarray(chop)
107
+ }
108
+
109
+ /**
110
+ * Converts each nibble into a single byte
111
+ *
112
+ * @param arr Nibble typed nibble array
113
+ * @returns Uint8Array typed byte array
114
+ */
115
+ export const nibbleTypeToByteType = (arr: Nibbles): Uint8Array => {
116
+ const l = arr.length
117
+ const buf = new Uint8Array(l)
118
+ for (let i = 0; i < buf.length; i++) {
119
+ buf[i] = arr[i]
120
+ }
121
+
122
+ return buf
123
+ }
124
+
125
+ /**
126
+ * Turns each byte into a single nibble, only extracting the lower nibble of each byte
127
+ *
128
+ * @param key Uint8Array typed byte array
129
+ * @returns Nibble typed nibble array
130
+ */
131
+ export const byteTypeToNibbleType = (key: Uint8Array): Nibbles => {
132
+ const nibbles = [] as Nibbles
133
+
134
+ for (let i = 0; i < key.length; i++) {
135
+ const q = i
136
+ nibbles[q] = key[i] % 16
137
+ }
138
+
139
+ return nibbles
140
+ }
141
+
142
+ /**
143
+ * Takes a string path and extends it by the given extension nibbles
144
+ *
145
+ * @param path String node path
146
+ * @param extension nibbles to extend by
147
+ * @param retType string indicating whether to return the key in "keybyte" or "hex" encoding
148
+ * @returns hex-encoded key
149
+ */
150
+ export const pathToHexKey = (path: string, extension: Nibbles, retType: string): Uint8Array => {
151
+ const b = hexToBytes(`0x${path}`)
152
+ const n = byteTypeToNibbleType(b)
153
+ if (retType === 'hex') {
154
+ return nibbleTypeToByteType(n.concat(extension))
155
+ } else if (retType === 'keybyte') {
156
+ return nibblesTypeToPackedBytes(n.concat(extension))
157
+ }
158
+ throw Error('retType must be either "keybyte" or "hex"')
159
+ }
160
+
161
+ export const mergeAndFormatKeyPaths = (pathStrings: string[]) => {
162
+ const ret: string[][] = []
163
+ let paths: string[] = []
164
+ let i = 0
165
+ while (i < pathStrings.length) {
166
+ const outerPathString = pathStrings[i]!.split('/')
167
+ const outerAccountPath = outerPathString[0]
168
+ const outerStoragePath = outerPathString[1]
169
+
170
+ paths.push(outerAccountPath)
171
+ if (outerStoragePath !== undefined) {
172
+ paths.push(outerStoragePath)
173
+ }
174
+
175
+ let j = ++i
176
+ while (j < pathStrings.length) {
177
+ const innerPathString = pathStrings[j]!.split('/')
178
+ const innerAccountPath = innerPathString[0]
179
+ const innerStoragePath = innerPathString[1]
180
+
181
+ if (innerAccountPath === outerAccountPath) {
182
+ paths.push(innerStoragePath)
183
+ } else {
184
+ ret.push(paths)
185
+ paths = []
186
+ i = j
187
+ break
188
+ }
189
+ j++
190
+ }
191
+ if (paths.length > 0) {
192
+ ret.push(paths)
193
+ paths = []
194
+ }
195
+ }
196
+ if (paths.length > 0) ret.push(paths)
197
+
198
+ return ret.map((pathStrings) =>
199
+ pathStrings.map((s) => {
200
+ if (s.length < 64) {
201
+ // partial path is compact encoded
202
+ return nibblesToCompactBytes(unprefixedHexToBytes(s))
203
+ } else {
204
+ // full path is keybyte encoded
205
+ return hexToKeybytes(unprefixedHexToBytes(s))
206
+ }
207
+ }),
208
+ )
209
+ }
@@ -0,0 +1,52 @@
1
+ import { RLP } from '@feelyourprotocol/rlp'
2
+ import {
3
+ Account,
4
+ hexToBytes,
5
+ isHexString,
6
+ unpadBytes,
7
+ unprefixedHexToBytes,
8
+ } from '@feelyourprotocol/util'
9
+ import { keccak_256 } from '@noble/hashes/sha3.js'
10
+
11
+ import { MerklePatriciaTrie } from '../mpt.ts'
12
+
13
+ import type { AccountState, GenesisState } from '@feelyourprotocol/common'
14
+
15
+ /**
16
+ * Derives the stateRoot of the genesis block based on genesis allocations
17
+ */
18
+ export async function genesisMPTStateRoot(genesisState: GenesisState) {
19
+ const trie = new MerklePatriciaTrie({ useKeyHashing: true })
20
+ for (const [key, value] of Object.entries(genesisState)) {
21
+ const address = isHexString(key) ? hexToBytes(key) : unprefixedHexToBytes(key)
22
+ const account = new Account()
23
+ if (typeof value === 'string') {
24
+ account.balance = BigInt(value)
25
+ } else {
26
+ const [balance, code, storage, nonce] = value as Partial<AccountState>
27
+ if (balance !== undefined) {
28
+ account.balance = BigInt(balance)
29
+ }
30
+ if (code !== undefined) {
31
+ const codeBytes = isHexString(code) ? hexToBytes(code) : unprefixedHexToBytes(code)
32
+ account.codeHash = keccak_256(codeBytes)
33
+ }
34
+ if (storage !== undefined) {
35
+ const storageTrie = new MerklePatriciaTrie({ useKeyHashing: true })
36
+ for (const [k, val] of storage) {
37
+ const storageKey = isHexString(k) ? hexToBytes(k) : unprefixedHexToBytes(k)
38
+ const storageVal = RLP.encode(
39
+ unpadBytes(isHexString(val) ? hexToBytes(val) : unprefixedHexToBytes(val)),
40
+ )
41
+ await storageTrie.put(storageKey, storageVal)
42
+ }
43
+ account.storageRoot = storageTrie.root()
44
+ }
45
+ if (nonce !== undefined) {
46
+ account.nonce = BigInt(nonce)
47
+ }
48
+ }
49
+ await trie.put(address, account.serialize())
50
+ }
51
+ return trie.root()
52
+ }
@@ -0,0 +1,47 @@
1
+ import type { Nibbles } from '../types.ts'
2
+
3
+ /**
4
+ * Prepends hex prefix to an array of nibbles.
5
+ * @param key - Array of nibbles
6
+ * @returns returns buffer of encoded data
7
+ **/
8
+ export function addHexPrefix(key: Nibbles, terminator: boolean): Nibbles {
9
+ // odd
10
+ if (key.length % 2) {
11
+ key.unshift(1)
12
+ } else {
13
+ // even
14
+ key.unshift(0)
15
+ key.unshift(0)
16
+ }
17
+
18
+ if (terminator) {
19
+ key[0] += 2
20
+ }
21
+
22
+ return key
23
+ }
24
+
25
+ /**
26
+ * Removes hex prefix of an array of nibbles.
27
+ * @param val - Array of nibbles
28
+ * @private
29
+ */
30
+ export function removeHexPrefix(val: Nibbles): Nibbles {
31
+ if (val[0] % 2) {
32
+ val = val.slice(1)
33
+ } else {
34
+ val = val.slice(2)
35
+ }
36
+
37
+ return val
38
+ }
39
+
40
+ /**
41
+ * Returns true if hex-prefixed path is for a terminating (leaf) node.
42
+ * @param key - a hex-prefixed array of nibbles
43
+ * @private
44
+ */
45
+ export function isTerminator(key: Nibbles): boolean {
46
+ return key[0] > 1
47
+ }
@@ -0,0 +1,3 @@
1
+ export * from './encoding.ts'
2
+ export * from './genesisState.ts'
3
+ export * from './walkController.ts'
@@ -0,0 +1,80 @@
1
+ import type { Nibbles } from '../types.ts'
2
+
3
+ /**
4
+ * Converts a bytes to a nibble array.
5
+ * @private
6
+ * @param key
7
+ */
8
+ export function bytesToNibbles(key: Uint8Array): Nibbles {
9
+ const nibbles = [] as Nibbles
10
+
11
+ for (let i = 0; i < key.length; i++) {
12
+ let q = i * 2
13
+ nibbles[q] = key[i] >> 4
14
+ ++q
15
+ nibbles[q] = key[i] % 16
16
+ }
17
+
18
+ return nibbles
19
+ }
20
+
21
+ /**
22
+ * Converts a nibble array into bytes.
23
+ * @private
24
+ * @param arr - Nibble array
25
+ */
26
+ export function nibblesTypeToPackedBytes(arr: Nibbles): Uint8Array {
27
+ const buf = new Uint8Array(arr.length / 2)
28
+ for (let i = 0; i < buf.length; i++) {
29
+ let q = i * 2
30
+ buf[i] = (arr[q] << 4) + arr[++q]
31
+ }
32
+ return buf
33
+ }
34
+
35
+ /**
36
+ * Compare two nibble array.
37
+ * * `0` is returned if `n2` === `n1`.
38
+ * * `1` is returned if `n2` > `n1`.
39
+ * * `-1` is returned if `n2` < `n1`.
40
+ * @param n1 - Nibble array
41
+ * @param n2 - Nibble array
42
+ */
43
+ export function nibblesCompare(n1: Nibbles, n2: Nibbles) {
44
+ const cmpLength = Math.min(n1.length, n2.length)
45
+
46
+ let res = 0
47
+ for (let i = 0; i < cmpLength; i++) {
48
+ if (n1[i] < n2[i]) {
49
+ res = -1
50
+ break
51
+ } else if (n1[i] > n2[i]) {
52
+ res = 1
53
+ break
54
+ }
55
+ }
56
+
57
+ if (res === 0) {
58
+ if (n1.length < n2.length) {
59
+ res = -1
60
+ } else if (n1.length > n2.length) {
61
+ res = 1
62
+ }
63
+ }
64
+
65
+ return res
66
+ }
67
+
68
+ /**
69
+ * Returns the number of in order matching nibbles of two give nibble arrays.
70
+ * @private
71
+ * @param nib1
72
+ * @param nib2
73
+ */
74
+ export function matchingNibbleLength(nib1: Nibbles, nib2: Nibbles): number {
75
+ let i = 0
76
+ while (nib1[i] === nib2[i] && nib1.length > i) {
77
+ i++
78
+ }
79
+ return i
80
+ }
@@ -0,0 +1,172 @@
1
+ import { EthereumJSErrorWithoutCode, PrioritizedTaskExecutor } from '@feelyourprotocol/util'
2
+
3
+ import { BranchMPTNode, ExtensionMPTNode, LeafMPTNode } from '../node/index.ts'
4
+
5
+ import type { MerklePatriciaTrie } from '../mpt.ts'
6
+ import type { FoundNodeFunction, MPTNode, Nibbles, NodeReferenceOrRawMPTNode } from '../types.ts'
7
+
8
+ /**
9
+ * Interface to control how the trie is being traversed. Schedules node visits via a
10
+ * prioritized task queue and invokes the provided callback for each node.
11
+ *
12
+ * Used by {@link MerklePatriciaTrie.findPath}, {@link MerklePatriciaTrie.walkTrie}, etc.
13
+ */
14
+ export class WalkController {
15
+ /** The {@link FoundNodeFunction} to call when a node is found. */
16
+ readonly onNode: FoundNodeFunction
17
+
18
+ /** Task executor that prioritizes node visits (shorter paths first). */
19
+ readonly taskExecutor: PrioritizedTaskExecutor
20
+
21
+ /** The trie being walked. */
22
+ readonly trie: MerklePatriciaTrie
23
+
24
+ private _resolvePromise: () => void
25
+ private _rejectPromise: (error: unknown) => void
26
+
27
+ /**
28
+ * @param onNode - The {@link FoundNodeFunction} to call when a node is found.
29
+ * @param trie - The trie to walk on.
30
+ * @param poolSize - The size of the task queue.
31
+ */
32
+ private constructor(onNode: FoundNodeFunction, trie: MerklePatriciaTrie, poolSize: number) {
33
+ this.onNode = onNode
34
+ this.taskExecutor = new PrioritizedTaskExecutor(poolSize)
35
+ this.trie = trie
36
+ this._resolvePromise = () => {}
37
+ this._rejectPromise = () => {}
38
+ }
39
+
40
+ /**
41
+ * Creates and starts an async walk over a trie from the given root.
42
+ * Resolves when all reachable nodes have been visited and no new tasks were scheduled.
43
+ *
44
+ * @param onNode - The {@link FoundNodeFunction} to call when a node is found.
45
+ * @param trie - The trie to walk on.
46
+ * @param rootHash - The root hash (32-byte keccak) to start walking from.
47
+ * @param poolSize - Task execution pool size to prevent OOM errors. Defaults to 500.
48
+ * @returns A Promise that resolves when the walk is finished.
49
+ */
50
+ static async newWalk(
51
+ onNode: FoundNodeFunction,
52
+ trie: MerklePatriciaTrie,
53
+ rootHash: Uint8Array,
54
+ poolSize?: number,
55
+ ): Promise<void> {
56
+ const controller = new WalkController(onNode, trie, poolSize ?? 500)
57
+ await controller._startWalk(rootHash)
58
+ }
59
+
60
+ private async _startWalk(rootHash: Uint8Array): Promise<void> {
61
+ return new Promise((resolve, reject) => {
62
+ this._resolvePromise = resolve
63
+ this._rejectPromise = reject
64
+ this.trie
65
+ .lookupNode(rootHash)
66
+ .then((rootNode) => {
67
+ this._processNode(rootHash, rootNode, [])
68
+ })
69
+ .catch((error: unknown) => {
70
+ this._rejectPromise(error)
71
+ })
72
+ })
73
+ }
74
+
75
+ /**
76
+ * Runs all children of a node. Priority of these nodes is the key length of the children.
77
+ * Used when walking an Extension or when exploring all branches of a Branch node.
78
+ *
79
+ * @param node - Node to get all children of and call onNode on.
80
+ * @param currentKeyNibbles - The current key (nibbles) which would yield the `node` when
81
+ * trying to get this node with a `get` operation. Defaults to `[]`.
82
+ * @returns `void`
83
+ */
84
+ allChildren(node: MPTNode, currentKeyNibbles: Nibbles = []): void {
85
+ if (node instanceof LeafMPTNode) {
86
+ return
87
+ }
88
+ let childEntries: [Nibbles, NodeReferenceOrRawMPTNode][]
89
+ if (node instanceof ExtensionMPTNode) {
90
+ childEntries = [[node.key(), node.value()]]
91
+ } else if (node instanceof BranchMPTNode) {
92
+ childEntries = node
93
+ .getChildren()
94
+ .map(([branchIndex, branchRef]) => [[branchIndex], branchRef])
95
+ } else {
96
+ return
97
+ }
98
+ for (const [keyExtension, childRef] of childEntries) {
99
+ const childKeyNibbles = currentKeyNibbles.concat(keyExtension)
100
+ const taskPriority = childKeyNibbles.length
101
+ this.pushNodeToQueue(childRef, childKeyNibbles, taskPriority)
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Pushes a node to the queue. If the queue has capacity, the node is executed immediately,
107
+ * otherwise it is queued for later execution.
108
+ *
109
+ * @param nodeRef - A node reference (32-byte keccak hash or raw encoding) to enqueue.
110
+ * @param currentKeyNibbles - The current key (nibbles) corresponding to this node. Defaults to `[]`.
111
+ * @param priority - Optional priority. Defaults to key length.
112
+ * @returns `void`
113
+ */
114
+ pushNodeToQueue(
115
+ nodeRef: NodeReferenceOrRawMPTNode,
116
+ currentKeyNibbles: Nibbles = [],
117
+ priority?: number,
118
+ ): void {
119
+ const taskPriority = priority ?? currentKeyNibbles.length
120
+ this.taskExecutor.executeOrQueue(taskPriority, (taskFinishedCallback: () => void) => {
121
+ this.trie
122
+ .lookupNode(nodeRef)
123
+ .then((decodedNode) => {
124
+ taskFinishedCallback()
125
+ this._processNode(nodeRef, decodedNode, currentKeyNibbles)
126
+ })
127
+ .catch((error: unknown) => {
128
+ this._rejectPromise(error)
129
+ })
130
+ })
131
+ }
132
+
133
+ /**
134
+ * Pushes a branch of a certain BranchMPTNode to the event queue.
135
+ * Used by findPath when following a specific key (only one child index is traversed).
136
+ *
137
+ * @param node - The BranchMPTNode to select a branch on.
138
+ * @param currentKeyNibbles - The current key which leads to the corresponding node. Defaults to `[]`.
139
+ * @param childIndex - The child index (0–15) to add to the event queue.
140
+ * @param priority - Optional priority of the event. Defaults to the total key length.
141
+ * @returns `void`
142
+ * @throws If `node` is not a BranchMPTNode or if the branch at `childIndex` is empty.
143
+ */
144
+ onlyBranchIndex(
145
+ node: BranchMPTNode,
146
+ currentKeyNibbles: Nibbles = [],
147
+ childIndex: number,
148
+ priority?: number,
149
+ ): void {
150
+ if (!(node instanceof BranchMPTNode)) {
151
+ throw EthereumJSErrorWithoutCode('Expected branch node')
152
+ }
153
+ const childRef = node.getBranch(childIndex)
154
+ if (!childRef) {
155
+ throw EthereumJSErrorWithoutCode('Could not get branch of childIndex')
156
+ }
157
+ const childKeyNibbles = currentKeyNibbles.concat(childIndex)
158
+ const taskPriority = priority ?? childKeyNibbles.length
159
+ this.pushNodeToQueue(childRef, childKeyNibbles, taskPriority)
160
+ }
161
+
162
+ private _processNode(
163
+ nodeRef: NodeReferenceOrRawMPTNode,
164
+ node: MPTNode | null,
165
+ currentKeyNibbles: Nibbles,
166
+ ): void {
167
+ this.onNode(nodeRef, node, currentKeyNibbles, this)
168
+ if (this.taskExecutor.finished()) {
169
+ this._resolvePromise()
170
+ }
171
+ }
172
+ }