@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,897 @@
1
+ // Some more secure presets when using e.g. JS `call`
2
+ 'use strict';
3
+ import { RLP } from '@feelyourprotocol/rlp';
4
+ import { BIGINT_0, EthereumJSErrorWithoutCode, KeyEncoding, Lock, MapDB, RLP_EMPTY_STRING, ValueEncoding, bytesToBigInt, bytesToHex, bytesToUnprefixedHex, bytesToUtf8, concatBytes, equalsBytes, isDebugEnabled, } from '@feelyourprotocol/util';
5
+ import { keccak_256 } from '@noble/hashes/sha3.js';
6
+ import debug from 'debug';
7
+ import { CheckpointDB } from "./db/checkpointDB.js";
8
+ import { BranchMPTNode, ExtensionMPTNode, LeafMPTNode, decodeMPTNode, decodeRawMPTNode, isRawMPTNode, } from "./node/index.js";
9
+ import { ROOT_DB_KEY } from "./types.js";
10
+ import { _walkTrie } from "./util/asyncWalk.js";
11
+ import { bytesToNibbles, matchingNibbleLength, nibblesTypeToPackedBytes } from "./util/nibbles.js";
12
+ import { WalkController } from "./util/walkController.js";
13
+ /**
14
+ * The basic trie interface, use with `import { MerklePatriciaTrie } from '@feelyourprotocol/mpt'`.
15
+ *
16
+ * A MerklePatriciaTrie object can be created with the constructor method:
17
+ *
18
+ * - {@link createMPT}
19
+ *
20
+ * A sparse MerklePatriciaTrie object can be created from a merkle proof:
21
+ *
22
+ * - {@link createMPTFromProof}
23
+ */
24
+ /**
25
+ * Merkle Patricia Trie - a space-optimized trie where each node with only one child
26
+ * is merged with its parent. Used for Ethereum state and storage.
27
+ *
28
+ * Node types:
29
+ * - Branch: 16-way branch + optional value (for keys ending at this node)
30
+ * - Extension: short path (nibbles) → child node
31
+ * - Leaf: remaining path (nibbles) → value
32
+ */
33
+ export class MerklePatriciaTrie {
34
+ /**
35
+ * Creates a new trie.
36
+ * @param opts Options for instantiating the trie
37
+ *
38
+ * Note: in most cases, {@link createMPT} constructor should be used. It uses the same API but provides sensible defaults
39
+ */
40
+ constructor(opts) {
41
+ /** Options with defaults applied */
42
+ this._opts = {
43
+ useKeyHashing: false,
44
+ useKeyHashingFunction: keccak_256,
45
+ keyPrefix: undefined,
46
+ useRootPersistence: false,
47
+ useNodePruning: false,
48
+ cacheSize: 0,
49
+ valueEncoding: ValueEncoding.String,
50
+ };
51
+ this._lock = new Lock();
52
+ this._debug = debug('mpt:#');
53
+ this.walkTrieIterable = _walkTrie.bind(this);
54
+ if (opts?.valueEncoding !== undefined && opts.db === undefined) {
55
+ throw EthereumJSErrorWithoutCode('`valueEncoding` can only be set if a `db` is provided');
56
+ }
57
+ if (opts !== undefined) {
58
+ this._opts = { ...this._opts, ...opts };
59
+ this._opts.useKeyHashingFunction =
60
+ opts.common?.customCrypto.keccak256 ?? opts.useKeyHashingFunction ?? keccak_256;
61
+ }
62
+ const valueEncoding = opts?.db !== undefined ? (opts.valueEncoding ?? ValueEncoding.String) : ValueEncoding.Bytes;
63
+ this.DEBUG = isDebugEnabled('ethjs');
64
+ this.debug = this.DEBUG
65
+ ? (message, namespaces = []) => {
66
+ let logger = this._debug;
67
+ for (const namespace of namespaces) {
68
+ logger = logger.extend(namespace);
69
+ }
70
+ logger(message);
71
+ }
72
+ : (..._args) => { };
73
+ this.database(opts?.db ?? new MapDB(), valueEncoding);
74
+ this.EMPTY_TRIE_ROOT = this.hash(RLP_EMPTY_STRING);
75
+ this._hashLen = this.EMPTY_TRIE_ROOT.length;
76
+ this._root = this.EMPTY_TRIE_ROOT;
77
+ if (opts?.root) {
78
+ this.root(opts.root);
79
+ }
80
+ this.DEBUG &&
81
+ this.debug(`Trie created:
82
+ || Root: ${bytesToHex(this.root())}
83
+ || Secure: ${this._opts.useKeyHashing}
84
+ || Persistent: ${this._opts.useRootPersistence}
85
+ || Pruning: ${this._opts.useNodePruning}
86
+ || CacheSize: ${this._opts.cacheSize}
87
+ || ----------------`);
88
+ }
89
+ database(db, valueEncoding) {
90
+ if (db !== undefined) {
91
+ if (db instanceof CheckpointDB) {
92
+ throw EthereumJSErrorWithoutCode('Cannot pass in an instance of CheckpointDB');
93
+ }
94
+ this._db = new CheckpointDB({ db, cacheSize: this._opts.cacheSize, valueEncoding });
95
+ }
96
+ return this._db;
97
+ }
98
+ /**
99
+ * Gets and/or Sets the current root of the `trie`
100
+ */
101
+ root(value) {
102
+ if (value !== undefined) {
103
+ if (value === null) {
104
+ value = this.EMPTY_TRIE_ROOT;
105
+ }
106
+ this.DEBUG && this.debug(`Setting root to ${bytesToHex(value)}`);
107
+ if (value.length !== this._hashLen) {
108
+ throw EthereumJSErrorWithoutCode(`Invalid root length. Roots are ${this._hashLen} bytes, got ${value.length} bytes`);
109
+ }
110
+ this._root = value;
111
+ }
112
+ return this._root;
113
+ }
114
+ /**
115
+ * Checks if a given root exists.
116
+ */
117
+ async checkRoot(root) {
118
+ try {
119
+ const value = await this.lookupNode(root);
120
+ return value !== null;
121
+ }
122
+ catch (error) {
123
+ if (error.message === 'Missing node in DB') {
124
+ return equalsBytes(root, this.EMPTY_TRIE_ROOT);
125
+ }
126
+ else {
127
+ throw error;
128
+ }
129
+ }
130
+ }
131
+ /**
132
+ * Gets a value given a `key`
133
+ * @param key - the key to search for
134
+ * @param throwIfMissing - if true, throws if any nodes are missing. Used for verifying proofs. (default: false)
135
+ * @returns A Promise that resolves to `Uint8Array` if a value was found or `null` if no value was found.
136
+ */
137
+ async get(key, throwIfMissing = false) {
138
+ this.DEBUG && this.debug(`Key: ${bytesToHex(key)}`, ['get']);
139
+ const { node, remaining } = await this.findPath(this.appliedKey(key), throwIfMissing);
140
+ let value = null;
141
+ if (node && remaining.length === 0) {
142
+ value = node.value();
143
+ }
144
+ this.DEBUG && this.debug(`Value: ${value === null ? 'null' : bytesToHex(value)}`, ['get']);
145
+ return value;
146
+ }
147
+ /**
148
+ * Stores a given `value` at the given `key` or do a delete if `value` is empty
149
+ * (delete operations are only executed on DB with `deleteFromDB` set to `true`)
150
+ * @param key
151
+ * @param value
152
+ * @returns A Promise that resolves once value is stored.
153
+ */
154
+ async put(key, value, skipKeyTransform = false) {
155
+ this.DEBUG && this.debug(`Key: ${bytesToHex(key)}`, ['put']);
156
+ this.DEBUG && this.debug(`Value: ${value === null ? 'null' : bytesToHex(value)}`, ['put']);
157
+ if (this._opts.useRootPersistence && equalsBytes(key, ROOT_DB_KEY)) {
158
+ throw EthereumJSErrorWithoutCode(`Attempted to set '${bytesToUtf8(ROOT_DB_KEY)}' key but it is not allowed.`);
159
+ }
160
+ // If value is empty, delete
161
+ if (value === null || value.length === 0) {
162
+ return this.del(key);
163
+ }
164
+ await this._lock.acquire();
165
+ const appliedKey = skipKeyTransform ? key : this.appliedKey(key);
166
+ if (equalsBytes(this.root(), this.EMPTY_TRIE_ROOT)) {
167
+ await this._createInitialNode(appliedKey, value);
168
+ }
169
+ else {
170
+ const { remaining, stack } = await this.findPath(appliedKey);
171
+ let ops = [];
172
+ if (this._opts.useNodePruning) {
173
+ const val = await this.get(key);
174
+ // Only delete keys if it either does not exist, or if it gets updated
175
+ // (The update will update the hash of the node, thus we can delete the original leaf node)
176
+ if (val === null || !equalsBytes(val, value)) {
177
+ ops = this._createPruneDeleteOps(stack);
178
+ }
179
+ }
180
+ // then update
181
+ await this._updateNode(appliedKey, value, remaining, stack);
182
+ if (this._opts.useNodePruning) {
183
+ // Only after updating the node we can delete the keyHashes
184
+ await this._db.batch(ops);
185
+ }
186
+ }
187
+ await this.persistRoot();
188
+ this._lock.release();
189
+ }
190
+ /**
191
+ * Deletes a value given a `key` from the trie
192
+ * (delete operations are only executed on DB with `deleteFromDB` set to `true`)
193
+ * @param key
194
+ * @returns A Promise that resolves once value is deleted.
195
+ */
196
+ async del(key, skipKeyTransform = false) {
197
+ this.DEBUG && this.debug(`Key: ${bytesToHex(key)}`, ['del']);
198
+ await this._lock.acquire();
199
+ const appliedKey = skipKeyTransform ? key : this.appliedKey(key);
200
+ const { node, stack } = await this.findPath(appliedKey);
201
+ let ops = [];
202
+ // Only delete if the `key` currently has any value
203
+ if (this._opts.useNodePruning && node !== null) {
204
+ ops = this._createPruneDeleteOps(stack);
205
+ }
206
+ if (node) {
207
+ await this._deleteNode(appliedKey, stack);
208
+ }
209
+ if (this._opts.useNodePruning) {
210
+ // Only after deleting the node it is possible to delete the keyHashes
211
+ await this._db.batch(ops);
212
+ }
213
+ await this.persistRoot();
214
+ this._lock.release();
215
+ }
216
+ // ─── Path finding ───────────────────────────────────────────────────────────
217
+ /**
218
+ * Finds the path from root to the node for the given key.
219
+ * Walks the trie, matching nibbles at each level. Returns the target node (if found)
220
+ * and the stack of nodes along the path (needed for updates/deletes).
221
+ *
222
+ * @param key - the search key (bytes)
223
+ * @param throwIfMissing - if true, throws when nodes are missing (e.g. proof verification)
224
+ * @param partialPath - optional pre-loaded stack for resuming from a mid-path node
225
+ */
226
+ async findPath(key, throwIfMissing = false, partialPath = {
227
+ stack: [],
228
+ }) {
229
+ const targetKey = bytesToNibbles(key);
230
+ const keyLen = targetKey.length;
231
+ const stack = Array.from({ length: keyLen });
232
+ // Pre-fill stack from partialPath when resuming a previous walk
233
+ let pathProgress = 0;
234
+ for (let stackIndex = 0; stackIndex < partialPath.stack.length - 1; stackIndex++) {
235
+ stack[stackIndex] = partialPath.stack[stackIndex];
236
+ pathProgress +=
237
+ stack[stackIndex] instanceof BranchMPTNode
238
+ ? 1
239
+ : stack[stackIndex].keyLength();
240
+ }
241
+ this.DEBUG && this.debug(`Target (${targetKey.length}): [${targetKey}]`, ['find_path']);
242
+ let result = null;
243
+ const onFound = async (_nodeRef, node, currentKeyNibbles, walkController) => {
244
+ stack[pathProgress] = node;
245
+ if (node instanceof BranchMPTNode) {
246
+ if (pathProgress === keyLen) {
247
+ result = { node, remaining: [], stack };
248
+ }
249
+ else {
250
+ const branchIndex = targetKey[pathProgress];
251
+ const branchNode = node.getBranch(branchIndex);
252
+ this.DEBUG &&
253
+ this.debug(branchNode === null
254
+ ? 'NULL'
255
+ : `Branch ${branchIndex}: ${branchNode instanceof Uint8Array ? bytesToHex(branchNode) : 'raw'}`, ['find_path', 'branch_node']);
256
+ if (!branchNode) {
257
+ result = { node: null, remaining: targetKey.slice(pathProgress), stack };
258
+ }
259
+ else {
260
+ pathProgress++;
261
+ walkController.onlyBranchIndex(node, currentKeyNibbles, branchIndex);
262
+ }
263
+ }
264
+ }
265
+ else if (node instanceof LeafMPTNode) {
266
+ const leafStartProgress = pathProgress;
267
+ if (keyLen - pathProgress > node.key().length) {
268
+ result = { node: null, remaining: targetKey.slice(leafStartProgress), stack };
269
+ return;
270
+ }
271
+ for (const nibble of node.key()) {
272
+ if (nibble !== targetKey[pathProgress]) {
273
+ result = { node: null, remaining: targetKey.slice(leafStartProgress), stack };
274
+ return;
275
+ }
276
+ pathProgress++;
277
+ }
278
+ result = { node, remaining: [], stack };
279
+ }
280
+ else if (node instanceof ExtensionMPTNode) {
281
+ const extensionStartProgress = pathProgress;
282
+ this.DEBUG &&
283
+ this.debug(`Extension key: [${node.key()}] vs expected [${targetKey.slice(pathProgress, pathProgress + node.key().length)}]`, ['find_path', 'extension_node']);
284
+ for (const nibble of node.key()) {
285
+ if (nibble !== targetKey[pathProgress]) {
286
+ result = { node: null, remaining: targetKey.slice(extensionStartProgress), stack };
287
+ return;
288
+ }
289
+ pathProgress++;
290
+ }
291
+ walkController.allChildren(node, currentKeyNibbles);
292
+ }
293
+ };
294
+ const startingNode = partialPath.stack[partialPath.stack.length - 1];
295
+ const start = startingNode !== undefined ? this.hash(startingNode.serialize()) : this.root();
296
+ try {
297
+ this.DEBUG &&
298
+ this.debug(`Walking trie from ${startingNode === undefined ? 'ROOT' : 'NODE'}: ${bytesToHex(start)}`, ['find_path']);
299
+ await this.walkTrie(start, onFound);
300
+ }
301
+ catch (error) {
302
+ if (error.message !== 'Missing node in DB' || throwIfMissing) {
303
+ throw error;
304
+ }
305
+ }
306
+ if (result === null) {
307
+ result = { node: null, remaining: [], stack };
308
+ }
309
+ this.DEBUG &&
310
+ this.debug(result.node !== null
311
+ ? `Target Node FOUND for ${bytesToNibbles(key)}`
312
+ : `Target Node NOT FOUND`, ['find_path']);
313
+ result.stack = result.stack.filter((stackEntry) => stackEntry !== undefined);
314
+ this.DEBUG &&
315
+ this.debug(`Result:
316
+ || Node: ${result.node === null ? 'null' : result.node.constructor.name}
317
+ || Remaining: [${result.remaining}]\n|| Stack: ${result.stack
318
+ .map((stackEntry) => stackEntry.constructor.name)
319
+ .join(', ')}`, ['find_path']);
320
+ return result;
321
+ }
322
+ /**
323
+ * Walks a trie until finished.
324
+ * @param root
325
+ * @param onFound - callback to call when a node is found. This schedules new tasks. If no tasks are available, the Promise resolves.
326
+ * @returns Resolves when finished walking trie.
327
+ */
328
+ async walkTrie(root, onFound) {
329
+ await WalkController.newWalk(onFound, this, root);
330
+ }
331
+ /**
332
+ * Executes a callback for each node in the trie.
333
+ * @param onFound - callback to call when a node is found.
334
+ * @returns Resolves when finished walking trie.
335
+ */
336
+ async walkAllNodes(onFound) {
337
+ for await (const { node, currentKey } of this.walkTrieIterable(this.root())) {
338
+ await onFound(node, currentKey);
339
+ }
340
+ }
341
+ /**
342
+ * Executes a callback for each value node in the trie.
343
+ * @param onFound - callback to call when a node is found.
344
+ * @returns Resolves when finished walking trie.
345
+ */
346
+ async walkAllValueNodes(onFound) {
347
+ for await (const { node, currentKey } of this.walkTrieIterable(this.root(), [], undefined, async (node) => {
348
+ return (node instanceof LeafMPTNode || (node instanceof BranchMPTNode && node.value() !== null));
349
+ })) {
350
+ await onFound(node, currentKey);
351
+ }
352
+ }
353
+ // ─── Node persistence (internal) ─────────────────────────────────────────────
354
+ /**
355
+ * Creates the initial leaf node when inserting into an empty trie.
356
+ * @private
357
+ */
358
+ async _createInitialNode(key, value) {
359
+ const newNode = new LeafMPTNode(bytesToNibbles(key), value);
360
+ const encoded = newNode.serialize();
361
+ this.root(this.hash(encoded));
362
+ await this._db.put(this._getDbKey(this.root()), encoded);
363
+ await this.persistRoot();
364
+ }
365
+ /**
366
+ * Retrieves a node from db by hash.
367
+ */
368
+ async lookupNode(node) {
369
+ if (isRawMPTNode(node)) {
370
+ const decoded = decodeRawMPTNode(node);
371
+ this.DEBUG && this.debug(`${decoded.constructor.name}`, ['lookup_node', 'raw_node']);
372
+ return decoded;
373
+ }
374
+ this.DEBUG && this.debug(`${bytesToHex(node)}`, ['lookup_node', 'by_hash']);
375
+ const value = (await this._db.get(this._getDbKey(node))) ?? null;
376
+ if (value === null) {
377
+ // Dev note: this error message text is used for error checking in `checkRoot`, `verifyMPTWithMerkleProof`, and `findPath`
378
+ throw EthereumJSErrorWithoutCode('Missing node in DB');
379
+ }
380
+ const decoded = decodeMPTNode(value);
381
+ this.DEBUG && this.debug(`${decoded.constructor.name} found in DB`, ['lookup_node', 'by_hash']);
382
+ return decoded;
383
+ }
384
+ /**
385
+ * True when we're updating an existing leaf value (key already exists, no structural change).
386
+ * @private
387
+ */
388
+ _isMatchingLeafUpdate(lastNode, stack, fullKeyNibbles, keyRemainder) {
389
+ if (!(lastNode instanceof LeafMPTNode) || keyRemainder.length !== 0) {
390
+ return false;
391
+ }
392
+ let keyOffset = 0;
393
+ for (const stackNode of stack) {
394
+ keyOffset += stackNode instanceof BranchMPTNode ? 1 : stackNode.key().length;
395
+ }
396
+ return (matchingNibbleLength(lastNode.key(), fullKeyNibbles.slice(keyOffset)) ===
397
+ lastNode.key().length);
398
+ }
399
+ /**
400
+ * Applies a value update given the path from findPath. Modifies the stack in-place
401
+ * to represent the new structure, then calls saveStack to persist.
402
+ *
403
+ * Three cases:
404
+ * 1. Match leaf: key exists, just update value (no structure change)
405
+ * 2. Branch: add new leaf to branch, or set branch value
406
+ * 3. Extension/Leaf with diverging path: create new branch at divergence, re-hang old + new leaf
407
+ *
408
+ * @private
409
+ */
410
+ async _updateNode(keyBytes, value, keyRemainder, stack) {
411
+ const opStack = [];
412
+ const lastNode = stack.pop();
413
+ if (!lastNode) {
414
+ throw EthereumJSErrorWithoutCode('Stack underflow');
415
+ }
416
+ const fullKeyNibbles = bytesToNibbles(keyBytes);
417
+ const matchLeaf = this._isMatchingLeafUpdate(lastNode, stack, fullKeyNibbles, keyRemainder);
418
+ if (matchLeaf) {
419
+ // Case 1: Key already exists at this leaf – update value in place
420
+ lastNode.value(value);
421
+ stack.push(lastNode);
422
+ }
423
+ else if (lastNode instanceof BranchMPTNode) {
424
+ // Case 2: Insert into branch – either new leaf on empty slot or set branch value
425
+ stack.push(lastNode);
426
+ if (keyRemainder.length !== 0) {
427
+ keyRemainder.shift();
428
+ stack.push(new LeafMPTNode(keyRemainder, value));
429
+ }
430
+ else {
431
+ lastNode.value(value);
432
+ }
433
+ }
434
+ else {
435
+ // Case 3: Last node is Extension or Leaf – path diverges. Create branch at divergence point
436
+ const lastKey = lastNode.key();
437
+ const matchingLength = matchingNibbleLength(lastKey, keyRemainder);
438
+ const newBranchMPTNode = new BranchMPTNode();
439
+ if (matchingLength !== 0) {
440
+ const newExtNode = new ExtensionMPTNode(lastNode.key().slice(0, matchingLength), value);
441
+ stack.push(newExtNode);
442
+ lastKey.splice(0, matchingLength);
443
+ keyRemainder.splice(0, matchingLength);
444
+ }
445
+ stack.push(newBranchMPTNode);
446
+ if (lastKey.length !== 0) {
447
+ const branchKey = lastKey.shift();
448
+ if (lastKey.length !== 0 || lastNode instanceof LeafMPTNode) {
449
+ lastNode.key(lastKey);
450
+ const formattedNode = this._formatNode(lastNode, false, opStack);
451
+ newBranchMPTNode.setBranch(branchKey, formattedNode);
452
+ }
453
+ else {
454
+ this._formatNode(lastNode, false, opStack, true);
455
+ newBranchMPTNode.setBranch(branchKey, lastNode.value());
456
+ }
457
+ }
458
+ else {
459
+ newBranchMPTNode.value(lastNode.value());
460
+ }
461
+ if (keyRemainder.length !== 0) {
462
+ keyRemainder.shift();
463
+ stack.push(new LeafMPTNode(keyRemainder, value));
464
+ }
465
+ else {
466
+ newBranchMPTNode.value(value);
467
+ }
468
+ }
469
+ await this.saveStack(fullKeyNibbles, stack, opStack);
470
+ }
471
+ /**
472
+ * Removes a key from the trie. Handles two main cases:
473
+ * - Deleting from a leaf: remove leaf, possibly collapse parent branch
474
+ * - Deleting from a branch value: clear value, possibly collapse if branch has single child
475
+ *
476
+ * When a branch ends up with only one child after deletion, we collapse it into
477
+ * an extension (or merge with parent extension) to keep the trie minimal.
478
+ *
479
+ * @private
480
+ */
481
+ async _deleteNode(keyBytes, stack) {
482
+ /**
483
+ * Collapses a branch with one child into a simpler structure.
484
+ * Parent can be: Branch (branch→branch), Extension (ext→branch), or null (branch is root).
485
+ */
486
+ const collapseBranchWithOneChild = (pathNibbles, branchIndex, childNode, parentNode, nodeStack) => {
487
+ const parentIsBranchOrRoot = parentNode === null || parentNode === undefined || parentNode instanceof BranchMPTNode;
488
+ if (parentIsBranchOrRoot) {
489
+ if (parentNode instanceof BranchMPTNode)
490
+ nodeStack.push(parentNode);
491
+ if (childNode instanceof BranchMPTNode) {
492
+ const extensionNode = new ExtensionMPTNode([branchIndex], new Uint8Array());
493
+ nodeStack.push(extensionNode);
494
+ pathNibbles.push(branchIndex);
495
+ }
496
+ else {
497
+ const childNodeKey = childNode.key();
498
+ childNodeKey.unshift(branchIndex);
499
+ childNode.key(childNodeKey.slice(0));
500
+ nodeStack.push(childNode);
501
+ return pathNibbles.concat(childNodeKey);
502
+ }
503
+ nodeStack.push(childNode);
504
+ return pathNibbles;
505
+ }
506
+ if (!(parentNode instanceof ExtensionMPTNode)) {
507
+ throw EthereumJSErrorWithoutCode('Expected extension node');
508
+ }
509
+ const parentKey = parentNode.key();
510
+ if (childNode instanceof BranchMPTNode) {
511
+ parentKey.push(branchIndex);
512
+ pathNibbles.push(branchIndex);
513
+ parentNode.key(parentKey);
514
+ nodeStack.push(parentNode);
515
+ }
516
+ else {
517
+ const childNodeKey = childNode.key();
518
+ childNodeKey.unshift(branchIndex);
519
+ const fullPath = parentKey.concat(childNodeKey);
520
+ childNode.key(fullPath);
521
+ nodeStack.push(childNode);
522
+ return pathNibbles.concat(childNodeKey);
523
+ }
524
+ nodeStack.push(childNode);
525
+ return pathNibbles;
526
+ };
527
+ let lastNode = stack.pop();
528
+ if (lastNode === undefined)
529
+ throw EthereumJSErrorWithoutCode('missing last node');
530
+ let parentNode = stack.pop();
531
+ const opStack = [];
532
+ let pathNibbles = bytesToNibbles(keyBytes);
533
+ if (parentNode === undefined) {
534
+ this.root(this.EMPTY_TRIE_ROOT);
535
+ return;
536
+ }
537
+ if (lastNode instanceof BranchMPTNode) {
538
+ lastNode.value(null);
539
+ }
540
+ else {
541
+ // Deleting a leaf: remove it from parent branch, then consider collapsing
542
+ if (!(parentNode instanceof BranchMPTNode)) {
543
+ throw EthereumJSErrorWithoutCode('Expected branch node');
544
+ }
545
+ const lastNodeKey = lastNode.key();
546
+ pathNibbles.splice(pathNibbles.length - lastNodeKey.length);
547
+ this._formatNode(lastNode, false, opStack, true);
548
+ parentNode.setBranch(pathNibbles.pop(), null);
549
+ lastNode = parentNode;
550
+ parentNode = stack.pop();
551
+ }
552
+ const branchNodes = lastNode.getChildren();
553
+ if (branchNodes.length === 1) {
554
+ // add the one remaining branch node to node above it
555
+ const branchNode = branchNodes[0][1];
556
+ const branchNodeKey = branchNodes[0][0];
557
+ // Special case where one needs to delete an extra node:
558
+ // In this case, after updating the branch, the branch node has just one branch left
559
+ // However, this violates the trie spec; this should be converted in either an ExtensionMPTNode
560
+ // Or a LeafMPTNode
561
+ // Since this branch is deleted, one can thus also delete this branch from the DB
562
+ // So add this to the `opStack` and mark the keyHash to be deleted
563
+ if (this._opts.useNodePruning) {
564
+ // If the branchNode has length < 32, it will be a RawNode (Uint8Array[]) instead of a Uint8Array
565
+ // In that case, we need to serialize and hash it into a Uint8Array, otherwise the operation will throw
566
+ const hashToDelete = isRawMPTNode(branchNode)
567
+ ? this.hash(RLP.encode(branchNode))
568
+ : branchNode;
569
+ opStack.push({ type: 'del', key: this._getDbKey(hashToDelete) });
570
+ }
571
+ // look up node
572
+ const foundNode = await this.lookupNode(branchNode);
573
+ pathNibbles = collapseBranchWithOneChild(pathNibbles, branchNodeKey, foundNode, parentNode, stack);
574
+ await this.saveStack(pathNibbles, stack, opStack);
575
+ }
576
+ else {
577
+ // Branch has multiple children: just persist the updated branch
578
+ if (parentNode) {
579
+ stack.push(parentNode);
580
+ }
581
+ stack.push(lastNode);
582
+ await this.saveStack(pathNibbles, stack, opStack);
583
+ }
584
+ }
585
+ /**
586
+ * Persists the modified node stack to the DB. Processes nodes from leaf toward root,
587
+ * wiring each node's references (extension value, branch slot) to its child's hash.
588
+ *
589
+ * @param key - nibble path that corresponds to the stack
590
+ * @param stack - nodes from findPath/update, bottom (leaf) to top (root)
591
+ * @param opStack - put/del operations accumulated by _formatNode
592
+ */
593
+ async saveStack(pathNibbles, stack, opStack) {
594
+ let childHash;
595
+ while (stack.length > 0) {
596
+ const node = stack.pop();
597
+ if (node === undefined) {
598
+ throw EthereumJSErrorWithoutCode('saveStack: missing node');
599
+ }
600
+ if (node instanceof LeafMPTNode || node instanceof ExtensionMPTNode) {
601
+ pathNibbles.splice(pathNibbles.length - node.key().length);
602
+ }
603
+ if (node instanceof ExtensionMPTNode && childHash !== undefined) {
604
+ node.value(childHash);
605
+ }
606
+ if (node instanceof BranchMPTNode && childHash !== undefined) {
607
+ const branchIndex = pathNibbles.pop();
608
+ node.setBranch(branchIndex, childHash);
609
+ }
610
+ childHash = this._formatNode(node, stack.length === 0, opStack);
611
+ }
612
+ if (childHash !== undefined) {
613
+ this.root(childHash);
614
+ }
615
+ await this._db.batch(opStack);
616
+ await this.persistRoot();
617
+ }
618
+ /**
619
+ * Serializes a node and either stores it (put) or schedules removal (del).
620
+ * Nodes ≥32 bytes (or top-level) are hashed and stored; smaller nodes are inlined as raw.
621
+ *
622
+ * @param node - the node to persist
623
+ * @param topLevel - if true, always store (root must be in DB)
624
+ * @param opStack - accumulates put/del operations for batch commit
625
+ * @param remove - if true, schedule del (used when pruning)
626
+ * @returns hash (for references) or raw encoding (for inline)
627
+ */
628
+ _formatNode(node, topLevel, opStack, remove = false) {
629
+ const encoded = node.serialize();
630
+ if (encoded.length >= 32 || topLevel) {
631
+ const nodeHash = this.hash(encoded);
632
+ const dbKey = this._getDbKey(nodeHash);
633
+ if (remove) {
634
+ if (this._opts.useNodePruning) {
635
+ opStack.push({ type: 'del', key: dbKey });
636
+ }
637
+ }
638
+ else {
639
+ opStack.push({ type: 'put', key: dbKey, value: encoded });
640
+ }
641
+ return nodeHash;
642
+ }
643
+ return node.raw();
644
+ }
645
+ /**
646
+ * The given hash of operations (key additions or deletions) are executed on the trie
647
+ * (delete operations are only executed on DB with `deleteFromDB` set to `true`)
648
+ * @example
649
+ * const ops = [
650
+ * { type: 'del', key: Uint8Array.from('father') }
651
+ * , { type: 'put', key: Uint8Array.from('name'), value: Uint8Array.from('Yuri Irsenovich Kim') } // cspell:disable-line
652
+ * , { type: 'put', key: Uint8Array.from('dob'), value: Uint8Array.from('16 February 1941') }
653
+ * , { type: 'put', key: Uint8Array.from('spouse'), value: Uint8Array.from('Kim Young-sook') } // cspell:disable-line
654
+ * , { type: 'put', key: Uint8Array.from('occupation'), value: Uint8Array.from('Clown') }
655
+ * ]
656
+ * await trie.batch(ops)
657
+ * @param ops
658
+ */
659
+ async batch(ops, skipKeyTransform) {
660
+ for (const op of ops) {
661
+ if (op.type === 'put') {
662
+ if (op.value === null || op.value === undefined) {
663
+ throw EthereumJSErrorWithoutCode('Invalid batch db operation');
664
+ }
665
+ await this.put(op.key, op.value, skipKeyTransform);
666
+ }
667
+ else if (op.type === 'del') {
668
+ await this.del(op.key, skipKeyTransform);
669
+ }
670
+ }
671
+ await this.persistRoot();
672
+ }
673
+ /**
674
+ * Verifies that every key in the DB is reachable from the root. Used to ensure
675
+ * pruning is correct – unreachable keys indicate a bug or corrupt state.
676
+ */
677
+ async verifyPrunedIntegrity() {
678
+ // Using deprecated bytesToUnprefixedHex for performance: used for string comparisons with database keys.
679
+ const roots = [
680
+ bytesToUnprefixedHex(this.root()),
681
+ bytesToUnprefixedHex(this.appliedKey(ROOT_DB_KEY)),
682
+ ];
683
+ for (const dbKeyHex of this._db.db._database.keys()) {
684
+ if (roots.includes(dbKeyHex)) {
685
+ // The root key can never be found from the trie, otherwise this would
686
+ // convert the tree from a directed acyclic graph to a directed cycling graph
687
+ continue;
688
+ }
689
+ // Track if key is found
690
+ let found = false;
691
+ try {
692
+ await this.walkTrie(this.root(), async (_, node, currentKeyNibbles, walkController) => {
693
+ if (found)
694
+ return;
695
+ if (node instanceof BranchMPTNode) {
696
+ for (const branchRef of node._branches) {
697
+ if (branchRef !== null &&
698
+ bytesToUnprefixedHex(isRawMPTNode(branchRef)
699
+ ? walkController.trie.appliedKey(RLP.encode(branchRef))
700
+ : branchRef) === dbKeyHex) {
701
+ found = true;
702
+ return;
703
+ }
704
+ }
705
+ walkController.allChildren(node, currentKeyNibbles);
706
+ }
707
+ if (node instanceof ExtensionMPTNode) {
708
+ if (bytesToUnprefixedHex(node.value()) === dbKeyHex) {
709
+ found = true;
710
+ return;
711
+ }
712
+ walkController.allChildren(node, currentKeyNibbles);
713
+ }
714
+ });
715
+ }
716
+ catch {
717
+ return false;
718
+ }
719
+ if (!found) {
720
+ return false;
721
+ }
722
+ }
723
+ return true;
724
+ }
725
+ /**
726
+ * Returns a copy of the underlying trie.
727
+ *
728
+ * Note on db: the copy will create a reference to the
729
+ * same underlying database.
730
+ *
731
+ * Note on cache: for memory reasons a copy will by default
732
+ * not recreate a new LRU cache but initialize with cache
733
+ * being deactivated. This behavior can be overwritten by
734
+ * explicitly setting `cacheSize` as an option on the method.
735
+ *
736
+ * @param includeCheckpoints - If true and during a checkpoint, the copy will contain the checkpointing metadata and will use the same scratch as underlying db.
737
+ */
738
+ shallowCopy(includeCheckpoints = true, opts) {
739
+ const trie = new MerklePatriciaTrie({
740
+ ...this._opts,
741
+ db: this._db.db.shallowCopy(),
742
+ root: this.root(),
743
+ cacheSize: 0,
744
+ ...(opts ?? {}),
745
+ });
746
+ if (includeCheckpoints && this.hasCheckpoints()) {
747
+ trie._db.setCheckpoints(this._db.checkpoints);
748
+ }
749
+ return trie;
750
+ }
751
+ /**
752
+ * Persists the root hash in the underlying database
753
+ */
754
+ async persistRoot() {
755
+ if (this._opts.useRootPersistence) {
756
+ this.DEBUG &&
757
+ this.debug(`Persisting root: \n|| RootHash: ${bytesToHex(this.root())}\n|| RootKey: ${bytesToHex(this.appliedKey(ROOT_DB_KEY))}`, ['persist_root']);
758
+ await this._db.put(this._getDbKey(this.appliedKey(ROOT_DB_KEY)), this.root());
759
+ }
760
+ }
761
+ /**
762
+ * Finds all nodes that are stored directly in the db
763
+ * (some nodes are stored raw inside other nodes)
764
+ * called by {@link ScratchReadStream}
765
+ * @private
766
+ */
767
+ async _findDbNodes(onFound) {
768
+ const outerOnFound = async (nodeRef, node, key, walkController) => {
769
+ if (isRawMPTNode(nodeRef)) {
770
+ if (node !== null) {
771
+ walkController.allChildren(node, key);
772
+ }
773
+ }
774
+ else {
775
+ onFound(nodeRef, node, key, walkController);
776
+ }
777
+ };
778
+ await this.walkTrie(this.root(), outerOnFound);
779
+ }
780
+ // ─── DB helpers ─────────────────────────────────────────────────────────────
781
+ /** Applies keyPrefix to a hash when multiple tries share a DB. */
782
+ _getDbKey(hash) {
783
+ return this._opts.keyPrefix ? concatBytes(this._opts.keyPrefix, hash) : hash;
784
+ }
785
+ /** Builds del ops for nodes that will be replaced (pruning). */
786
+ _createPruneDeleteOps(stack) {
787
+ return stack.map((node) => {
788
+ const deletedHash = this.hash(node.serialize());
789
+ return {
790
+ type: 'del',
791
+ key: this._getDbKey(deletedHash),
792
+ opts: { keyEncoding: KeyEncoding.Bytes },
793
+ };
794
+ });
795
+ }
796
+ /** Applies key hashing (keccak) when useKeyHashing is enabled (Ethereum-style). */
797
+ appliedKey(key) {
798
+ if (this._opts.useKeyHashing) {
799
+ return this.hash(key);
800
+ }
801
+ return key;
802
+ }
803
+ hash(inputBytes) {
804
+ return Uint8Array.from(this._opts.useKeyHashingFunction.call(undefined, inputBytes));
805
+ }
806
+ /**
807
+ * Is the trie during a checkpoint phase?
808
+ */
809
+ hasCheckpoints() {
810
+ return this._db.hasCheckpoints();
811
+ }
812
+ /**
813
+ * Creates a checkpoint that can later be reverted to or committed.
814
+ * After this is called, all changes can be reverted until `commit` is called.
815
+ */
816
+ checkpoint() {
817
+ this.DEBUG && this.debug(`${bytesToHex(this.root())}`, ['checkpoint']);
818
+ this._db.checkpoint(this.root());
819
+ }
820
+ /**
821
+ * Commits a checkpoint to disk, if current checkpoint is not nested.
822
+ * If nested, only sets the parent checkpoint as current checkpoint.
823
+ * @throws If not during a checkpoint phase
824
+ */
825
+ async commit() {
826
+ if (!this.hasCheckpoints()) {
827
+ throw EthereumJSErrorWithoutCode('trying to commit when not checkpointed');
828
+ }
829
+ this.DEBUG && this.debug(`${bytesToHex(this.root())}`, ['commit']);
830
+ await this._lock.acquire();
831
+ await this._db.commit();
832
+ await this.persistRoot();
833
+ this._lock.release();
834
+ }
835
+ /**
836
+ * Reverts the trie to the state it was at when `checkpoint` was first called.
837
+ * If during a nested checkpoint, sets root to most recent checkpoint, and sets
838
+ * parent checkpoint as current.
839
+ */
840
+ async revert() {
841
+ if (!this.hasCheckpoints()) {
842
+ throw EthereumJSErrorWithoutCode('trying to revert when not checkpointed');
843
+ }
844
+ this.DEBUG && this.debug(`${bytesToHex(this.root())}`, ['revert', 'before']);
845
+ await this._lock.acquire();
846
+ this.root(await this._db.revert());
847
+ await this.persistRoot();
848
+ this._lock.release();
849
+ this.DEBUG && this.debug(`${bytesToHex(this.root())}`, ['revert', 'after']);
850
+ }
851
+ /**
852
+ * Flushes all checkpoints, restoring the initial checkpoint state.
853
+ */
854
+ flushCheckpoints() {
855
+ this.DEBUG &&
856
+ this.debug(`Deleting ${this._db.checkpoints.length} checkpoints.`, ['flush_checkpoints']);
857
+ this._db.checkpoints = [];
858
+ }
859
+ /**
860
+ * Returns a list of values stored in the trie
861
+ * @param startKey first unhashed key in the range to be returned (defaults to 0). Note, all keys must be of the same length or undefined behavior will result
862
+ * @param limit - the number of keys to be returned (undefined means all keys)
863
+ * @returns an object with two properties (a map of all key/value pairs in the trie - or in the specified range) and then a `nextKey` reference if a range is specified
864
+ */
865
+ async getValueMap(startKey = BIGINT_0, limit) {
866
+ let inRange = limit !== undefined ? false : true;
867
+ let valueCount = 0;
868
+ const values = {};
869
+ let nextKey = null;
870
+ await this.walkAllValueNodes(async (node, currentKey) => {
871
+ if (node instanceof LeafMPTNode) {
872
+ const keyBytes = nibblesTypeToPackedBytes(currentKey.concat(node.key()));
873
+ if (!inRange) {
874
+ // Check if the key is already in the correct range.
875
+ if (bytesToBigInt(keyBytes) >= startKey) {
876
+ inRange = true;
877
+ }
878
+ else {
879
+ return;
880
+ }
881
+ }
882
+ if (limit === undefined || valueCount < limit) {
883
+ values[bytesToHex(keyBytes)] = bytesToHex(node._value);
884
+ valueCount++;
885
+ }
886
+ else if (valueCount === limit) {
887
+ nextKey = bytesToHex(keyBytes);
888
+ }
889
+ }
890
+ });
891
+ return {
892
+ values,
893
+ nextKey,
894
+ };
895
+ }
896
+ }
897
+ //# sourceMappingURL=mpt.js.map