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