@feelyourprotocol/statemanager 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 (169) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +331 -0
  3. package/dist/cjs/cache/account.d.ts +85 -0
  4. package/dist/cjs/cache/account.d.ts.map +1 -0
  5. package/dist/cjs/cache/account.js +252 -0
  6. package/dist/cjs/cache/account.js.map +1 -0
  7. package/dist/cjs/cache/cache.d.ts +23 -0
  8. package/dist/cjs/cache/cache.d.ts.map +1 -0
  9. package/dist/cjs/cache/cache.js +31 -0
  10. package/dist/cjs/cache/cache.js.map +1 -0
  11. package/dist/cjs/cache/caches.d.ts +19 -0
  12. package/dist/cjs/cache/caches.d.ts.map +1 -0
  13. package/dist/cjs/cache/caches.js +107 -0
  14. package/dist/cjs/cache/caches.js.map +1 -0
  15. package/dist/cjs/cache/code.d.ts +87 -0
  16. package/dist/cjs/cache/code.d.ts.map +1 -0
  17. package/dist/cjs/cache/code.js +258 -0
  18. package/dist/cjs/cache/code.js.map +1 -0
  19. package/dist/cjs/cache/index.d.ts +7 -0
  20. package/dist/cjs/cache/index.d.ts.map +1 -0
  21. package/dist/cjs/cache/index.js +23 -0
  22. package/dist/cjs/cache/index.js.map +1 -0
  23. package/dist/cjs/cache/originalStorageCache.d.ts +21 -0
  24. package/dist/cjs/cache/originalStorageCache.d.ts.map +1 -0
  25. package/dist/cjs/cache/originalStorageCache.js +52 -0
  26. package/dist/cjs/cache/originalStorageCache.js.map +1 -0
  27. package/dist/cjs/cache/storage.d.ts +101 -0
  28. package/dist/cjs/cache/storage.d.ts.map +1 -0
  29. package/dist/cjs/cache/storage.js +337 -0
  30. package/dist/cjs/cache/storage.js.map +1 -0
  31. package/dist/cjs/cache/types.d.ts +36 -0
  32. package/dist/cjs/cache/types.d.ts.map +1 -0
  33. package/dist/cjs/cache/types.js +8 -0
  34. package/dist/cjs/cache/types.js.map +1 -0
  35. package/dist/cjs/index.d.ts +8 -0
  36. package/dist/cjs/index.d.ts.map +1 -0
  37. package/dist/cjs/index.js +24 -0
  38. package/dist/cjs/index.js.map +1 -0
  39. package/dist/cjs/merkleStateManager.d.ts +260 -0
  40. package/dist/cjs/merkleStateManager.d.ts.map +1 -0
  41. package/dist/cjs/merkleStateManager.js +616 -0
  42. package/dist/cjs/merkleStateManager.js.map +1 -0
  43. package/dist/cjs/package.json +3 -0
  44. package/dist/cjs/proof/index.d.ts +3 -0
  45. package/dist/cjs/proof/index.d.ts.map +1 -0
  46. package/dist/cjs/proof/index.js +19 -0
  47. package/dist/cjs/proof/index.js.map +1 -0
  48. package/dist/cjs/proof/merkle.d.ts +40 -0
  49. package/dist/cjs/proof/merkle.d.ts.map +1 -0
  50. package/dist/cjs/proof/merkle.js +182 -0
  51. package/dist/cjs/proof/merkle.js.map +1 -0
  52. package/dist/cjs/proof/rpc.d.ts +10 -0
  53. package/dist/cjs/proof/rpc.d.ts.map +1 -0
  54. package/dist/cjs/proof/rpc.js +20 -0
  55. package/dist/cjs/proof/rpc.js.map +1 -0
  56. package/dist/cjs/rpcStateManager.d.ts +162 -0
  57. package/dist/cjs/rpcStateManager.d.ts.map +1 -0
  58. package/dist/cjs/rpcStateManager.js +313 -0
  59. package/dist/cjs/rpcStateManager.js.map +1 -0
  60. package/dist/cjs/simpleStateManager.d.ts +54 -0
  61. package/dist/cjs/simpleStateManager.d.ts.map +1 -0
  62. package/dist/cjs/simpleStateManager.js +125 -0
  63. package/dist/cjs/simpleStateManager.js.map +1 -0
  64. package/dist/cjs/statefulBinaryTreeStateManager.d.ts +69 -0
  65. package/dist/cjs/statefulBinaryTreeStateManager.d.ts.map +1 -0
  66. package/dist/cjs/statefulBinaryTreeStateManager.js +576 -0
  67. package/dist/cjs/statefulBinaryTreeStateManager.js.map +1 -0
  68. package/dist/cjs/types.d.ts +92 -0
  69. package/dist/cjs/types.d.ts.map +1 -0
  70. package/dist/cjs/types.js +3 -0
  71. package/dist/cjs/types.js.map +1 -0
  72. package/dist/cjs/util.d.ts +4 -0
  73. package/dist/cjs/util.d.ts.map +1 -0
  74. package/dist/cjs/util.js +21 -0
  75. package/dist/cjs/util.js.map +1 -0
  76. package/dist/esm/cache/account.d.ts +85 -0
  77. package/dist/esm/cache/account.d.ts.map +1 -0
  78. package/dist/esm/cache/account.js +248 -0
  79. package/dist/esm/cache/account.js.map +1 -0
  80. package/dist/esm/cache/cache.d.ts +23 -0
  81. package/dist/esm/cache/cache.d.ts.map +1 -0
  82. package/dist/esm/cache/cache.js +27 -0
  83. package/dist/esm/cache/cache.js.map +1 -0
  84. package/dist/esm/cache/caches.d.ts +19 -0
  85. package/dist/esm/cache/caches.d.ts.map +1 -0
  86. package/dist/esm/cache/caches.js +103 -0
  87. package/dist/esm/cache/caches.js.map +1 -0
  88. package/dist/esm/cache/code.d.ts +87 -0
  89. package/dist/esm/cache/code.d.ts.map +1 -0
  90. package/dist/esm/cache/code.js +254 -0
  91. package/dist/esm/cache/code.js.map +1 -0
  92. package/dist/esm/cache/index.d.ts +7 -0
  93. package/dist/esm/cache/index.d.ts.map +1 -0
  94. package/dist/esm/cache/index.js +7 -0
  95. package/dist/esm/cache/index.js.map +1 -0
  96. package/dist/esm/cache/originalStorageCache.d.ts +21 -0
  97. package/dist/esm/cache/originalStorageCache.d.ts.map +1 -0
  98. package/dist/esm/cache/originalStorageCache.js +48 -0
  99. package/dist/esm/cache/originalStorageCache.js.map +1 -0
  100. package/dist/esm/cache/storage.d.ts +101 -0
  101. package/dist/esm/cache/storage.d.ts.map +1 -0
  102. package/dist/esm/cache/storage.js +333 -0
  103. package/dist/esm/cache/storage.js.map +1 -0
  104. package/dist/esm/cache/types.d.ts +36 -0
  105. package/dist/esm/cache/types.d.ts.map +1 -0
  106. package/dist/esm/cache/types.js +5 -0
  107. package/dist/esm/cache/types.js.map +1 -0
  108. package/dist/esm/index.d.ts +8 -0
  109. package/dist/esm/index.d.ts.map +1 -0
  110. package/dist/esm/index.js +8 -0
  111. package/dist/esm/index.js.map +1 -0
  112. package/dist/esm/merkleStateManager.d.ts +260 -0
  113. package/dist/esm/merkleStateManager.d.ts.map +1 -0
  114. package/dist/esm/merkleStateManager.js +612 -0
  115. package/dist/esm/merkleStateManager.js.map +1 -0
  116. package/dist/esm/package.json +3 -0
  117. package/dist/esm/proof/index.d.ts +3 -0
  118. package/dist/esm/proof/index.d.ts.map +1 -0
  119. package/dist/esm/proof/index.js +3 -0
  120. package/dist/esm/proof/index.js.map +1 -0
  121. package/dist/esm/proof/merkle.d.ts +40 -0
  122. package/dist/esm/proof/merkle.d.ts.map +1 -0
  123. package/dist/esm/proof/merkle.js +175 -0
  124. package/dist/esm/proof/merkle.js.map +1 -0
  125. package/dist/esm/proof/rpc.d.ts +10 -0
  126. package/dist/esm/proof/rpc.d.ts.map +1 -0
  127. package/dist/esm/proof/rpc.js +17 -0
  128. package/dist/esm/proof/rpc.js.map +1 -0
  129. package/dist/esm/rpcStateManager.d.ts +162 -0
  130. package/dist/esm/rpcStateManager.d.ts.map +1 -0
  131. package/dist/esm/rpcStateManager.js +308 -0
  132. package/dist/esm/rpcStateManager.js.map +1 -0
  133. package/dist/esm/simpleStateManager.d.ts +54 -0
  134. package/dist/esm/simpleStateManager.d.ts.map +1 -0
  135. package/dist/esm/simpleStateManager.js +121 -0
  136. package/dist/esm/simpleStateManager.js.map +1 -0
  137. package/dist/esm/statefulBinaryTreeStateManager.d.ts +69 -0
  138. package/dist/esm/statefulBinaryTreeStateManager.d.ts.map +1 -0
  139. package/dist/esm/statefulBinaryTreeStateManager.js +572 -0
  140. package/dist/esm/statefulBinaryTreeStateManager.js.map +1 -0
  141. package/dist/esm/types.d.ts +92 -0
  142. package/dist/esm/types.d.ts.map +1 -0
  143. package/dist/esm/types.js +2 -0
  144. package/dist/esm/types.js.map +1 -0
  145. package/dist/esm/util.d.ts +4 -0
  146. package/dist/esm/util.d.ts.map +1 -0
  147. package/dist/esm/util.js +18 -0
  148. package/dist/esm/util.js.map +1 -0
  149. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
  150. package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
  151. package/package.json +74 -0
  152. package/src/cache/account.ts +277 -0
  153. package/src/cache/cache.ts +35 -0
  154. package/src/cache/caches.ts +125 -0
  155. package/src/cache/code.ts +277 -0
  156. package/src/cache/index.ts +6 -0
  157. package/src/cache/originalStorageCache.ts +57 -0
  158. package/src/cache/storage.ts +369 -0
  159. package/src/cache/types.ts +38 -0
  160. package/src/index.ts +7 -0
  161. package/src/merkleStateManager.ts +737 -0
  162. package/src/proof/index.ts +2 -0
  163. package/src/proof/merkle.ts +264 -0
  164. package/src/proof/rpc.ts +24 -0
  165. package/src/rpcStateManager.ts +381 -0
  166. package/src/simpleStateManager.ts +154 -0
  167. package/src/statefulBinaryTreeStateManager.ts +789 -0
  168. package/src/types.ts +103 -0
  169. package/src/util.ts +28 -0
@@ -0,0 +1,789 @@
1
+ import { BinaryTree } from '@feelyourprotocol/binarytree'
2
+ import { BinaryTreeAccessedStateType } from '@feelyourprotocol/common'
3
+ import { RLP } from '@feelyourprotocol/rlp'
4
+ import type { Address, BinaryTreeExecutionWitness, PrefixedHexString } from '@feelyourprotocol/util'
5
+ import {
6
+ Account,
7
+ BINARY_TREE_CODE_CHUNK_SIZE,
8
+ BINARY_TREE_CODE_OFFSET,
9
+ BINARY_TREE_NODE_WIDTH,
10
+ BinaryTreeLeafType,
11
+ EthereumJSErrorWithoutCode,
12
+ KECCAK256_NULL,
13
+ MapDB,
14
+ bigIntToBytes,
15
+ bytesToBigInt,
16
+ bytesToHex,
17
+ chunkifyBinaryTreeCode,
18
+ createAddressFromString,
19
+ createPartialAccount,
20
+ createPartialAccountFromRLP,
21
+ decodeBinaryTreeLeafBasicData,
22
+ encodeBinaryTreeLeafBasicData,
23
+ equalsBytes,
24
+ generateBinaryTreeChunkSuffixes,
25
+ generateBinaryTreeCodeStems,
26
+ getBinaryTreeKeyForStorageSlot,
27
+ getBinaryTreeStem,
28
+ hexToBigInt,
29
+ hexToBytes,
30
+ isDebugEnabled,
31
+ padToEven,
32
+ setLengthLeft,
33
+ setLengthRight,
34
+ short,
35
+ unprefixedHexToBytes,
36
+ } from '@feelyourprotocol/util'
37
+ import { blake3 } from '@noble/hashes/blake3.js'
38
+ import { keccak_256 } from '@noble/hashes/sha3.js'
39
+ import debugDefault from 'debug'
40
+
41
+ import { OriginalStorageCache } from './cache/originalStorageCache.ts'
42
+ import { modifyAccountFields } from './util.ts'
43
+
44
+ import type {
45
+ AccountFields,
46
+ BinaryTreeAccessWitnessInterface,
47
+ BinaryTreeAccessedStateWithAddress,
48
+ GenesisState,
49
+ StateManagerInterface,
50
+ StorageDump,
51
+ StoragePair,
52
+ StorageRange,
53
+ } from '@feelyourprotocol/common'
54
+ import type { Debugger } from 'debug'
55
+ import type { Caches } from './cache/caches.ts'
56
+ import type { BinaryTreeState, StatefulBinaryTreeStateManagerOpts } from './types.ts'
57
+
58
+ const ZEROVALUE = '0x0000000000000000000000000000000000000000000000000000000000000000'
59
+ export class StatefulBinaryTreeStateManager implements StateManagerInterface {
60
+ protected _debug: Debugger
61
+ protected _caches?: Caches
62
+
63
+ preStateRoot: Uint8Array
64
+ originalStorageCache: OriginalStorageCache
65
+ hashFunction: (input: Uint8Array) => Uint8Array
66
+
67
+ protected _tree: BinaryTree
68
+
69
+ protected _checkpointCount: number
70
+
71
+ // Post-state provided from the executionWitness.
72
+ // Should not update. Used for comparing our computed post-state with the canonical one.
73
+ private _postState: BinaryTreeState = {}
74
+
75
+ /**
76
+ * StateManager is run in DEBUG mode (default: false)
77
+ * Taken from DEBUG environment variable
78
+ *
79
+ * Safeguards on debug() calls are added for
80
+ * performance reasons to avoid string literal evaluation
81
+ * @hidden
82
+ */
83
+ protected readonly DEBUG: boolean = false
84
+
85
+ private keccakFunction: Function
86
+
87
+ constructor(opts: StatefulBinaryTreeStateManagerOpts) {
88
+ // Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables
89
+ this.DEBUG = isDebugEnabled('ethjs')
90
+
91
+ this._checkpointCount = 0
92
+
93
+ if (opts.common?.isActivatedEIP(7864) === false) {
94
+ throw EthereumJSErrorWithoutCode('EIP-7864 required for binary tree state management')
95
+ }
96
+
97
+ this.hashFunction = opts.hashFunction ?? blake3
98
+ this._tree =
99
+ opts.tree ??
100
+ new BinaryTree({
101
+ hashFunction: this.hashFunction,
102
+ db: new MapDB<string, string | Uint8Array>(),
103
+ useRootPersistence: false,
104
+ cacheSize: 0,
105
+ })
106
+ this._debug = debugDefault('statemanager:binarytree')
107
+ this.originalStorageCache = new OriginalStorageCache(this.getStorage.bind(this))
108
+ this._caches = opts.caches
109
+ this.keccakFunction = keccak_256
110
+ this.preStateRoot = new Uint8Array(32) // Initial state root is zeroes
111
+ }
112
+
113
+ /**
114
+ * Gets the account associated with `address` or `undefined` if account does not exist
115
+ * @param address - Address of the `account` to get
116
+ */
117
+ getAccount = async (address: Address): Promise<Account | undefined> => {
118
+ const elem = this._caches?.account?.get(address)
119
+ if (elem !== undefined) {
120
+ return elem.accountRLP !== undefined
121
+ ? createPartialAccountFromRLP(elem.accountRLP)
122
+ : undefined
123
+ }
124
+
125
+ const stem = getBinaryTreeStem(this.hashFunction, address, 0)
126
+
127
+ // First retrieve the account "header" values from the trie
128
+ const accountValues = await this._tree.get(stem, [
129
+ BinaryTreeLeafType.BasicData,
130
+ BinaryTreeLeafType.CodeHash,
131
+ ])
132
+
133
+ let account
134
+ if (accountValues[0] !== null && accountValues[0] !== undefined) {
135
+ const basicData = decodeBinaryTreeLeafBasicData(accountValues[0]!)
136
+ account = createPartialAccount({
137
+ version: basicData.version,
138
+ balance: basicData.balance,
139
+ nonce: basicData.nonce,
140
+ // Codehash is either untouched (i.e. null) or deleted (i.e. overwritten with zeros)
141
+ codeHash:
142
+ accountValues[1] === null ||
143
+ accountValues[1] === undefined ||
144
+ equalsBytes(accountValues[1], new Uint8Array(32))
145
+ ? KECCAK256_NULL
146
+ : accountValues[1],
147
+ codeSize: basicData.codeSize,
148
+ storageRoot: KECCAK256_NULL,
149
+ })
150
+ } else if (accountValues[1] === undefined || accountValues[1] === null) {
151
+ // account does not exist if both basic fields and codehash are undefined
152
+ if (this.DEBUG) {
153
+ this._debug(`getAccount address=${address.toString()} from DB (non-existent)`)
154
+ }
155
+ this._caches?.account?.put(address, account)
156
+ }
157
+
158
+ if (this.DEBUG) {
159
+ this._debug(`getAccount address=${address.toString()} stem=${short(stem)}`)
160
+ }
161
+ return account
162
+ }
163
+
164
+ public initBinaryTreeExecutionWitness(
165
+ _blockNum: bigint,
166
+ executionWitness?: BinaryTreeExecutionWitness | null,
167
+ ) {
168
+ if (executionWitness === null || executionWitness === undefined) {
169
+ const errorMsg = `Invalid executionWitness=${executionWitness} for initBinaryTreeExecutionWitness`
170
+ this._debug(errorMsg)
171
+ throw Error(errorMsg)
172
+ }
173
+
174
+ this.preStateRoot = hexToBytes(executionWitness.parentStateRoot) // set prestate root
175
+
176
+ // Populate the post-state from the executionWitness
177
+
178
+ const postStateRaw = executionWitness.stateDiff.flatMap(({ stem, suffixDiffs }) => {
179
+ const suffixDiffPairs = suffixDiffs.map(({ newValue, currentValue, suffix }) => {
180
+ const key = `${stem}${padToEven(Number(suffix).toString(16))}` as PrefixedHexString
181
+ // A postState value of null means there was no change from the preState.
182
+ // In this implementation, we therefore replace null with the preState.
183
+ const value = newValue ?? currentValue
184
+
185
+ return {
186
+ [key]: value,
187
+ }
188
+ })
189
+
190
+ return suffixDiffPairs
191
+ })
192
+
193
+ const postState = postStateRaw.reduce((prevValue, currentValue) => {
194
+ const acc = { ...prevValue, ...currentValue }
195
+ return acc
196
+ }, {})
197
+
198
+ this._postState = postState
199
+
200
+ this._debug(`initBinaryTreeExecutionWitness postState=${JSON.stringify(this._postState)}`)
201
+ }
202
+
203
+ /**
204
+ * Saves an account into state under the provided `address`.
205
+ * @param address - Address under which to store `account`
206
+ * @param account - The account to store or undefined if to be deleted
207
+ */
208
+ putAccount = async (address: Address, account?: Account): Promise<void> => {
209
+ if (this.DEBUG) {
210
+ this._debug(
211
+ `putAccount address=${address} nonce=${account?.nonce} balance=${
212
+ account?.balance
213
+ } contract=${account && account.isContract() ? 'yes' : 'no'} empty=${
214
+ account && account.isEmpty() ? 'yes' : 'no'
215
+ }`,
216
+ )
217
+ }
218
+ if (this._caches?.account === undefined) {
219
+ if (account !== undefined) {
220
+ const stem = getBinaryTreeStem(this.hashFunction, address, 0)
221
+ const basicDataBytes = encodeBinaryTreeLeafBasicData(account)
222
+ await this._tree.put(
223
+ stem,
224
+ [BinaryTreeLeafType.BasicData, BinaryTreeLeafType.CodeHash],
225
+ [basicDataBytes, account.codeHash],
226
+ )
227
+ } else {
228
+ // Delete account
229
+ await this.deleteAccount(address)
230
+ }
231
+ } else {
232
+ if (account !== undefined) {
233
+ this._caches?.account?.put(address, account, true)
234
+ } else {
235
+ this._caches?.account?.del(address)
236
+ }
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Deletes an account from state under the provided `address`.
242
+ * @param address - Address of the account which should be deleted
243
+ */
244
+ deleteAccount = async (address: Address): Promise<void> => {
245
+ if (this.DEBUG) {
246
+ this._debug(`Delete account ${address}`)
247
+ }
248
+
249
+ this._caches?.deleteAccount(address)
250
+
251
+ if (this._caches?.account === undefined) {
252
+ const stem = getBinaryTreeStem(this.hashFunction, address)
253
+ // Special instance where we delete the account and revert the trie value to untouched
254
+ await this._tree.put(
255
+ stem,
256
+ [BinaryTreeLeafType.BasicData, BinaryTreeLeafType.CodeHash],
257
+ [null, null],
258
+ )
259
+ }
260
+ }
261
+
262
+ modifyAccountFields = async (address: Address, accountFields: AccountFields): Promise<void> => {
263
+ await modifyAccountFields(this, address, accountFields)
264
+ }
265
+ putCode = async (address: Address, value: Uint8Array): Promise<void> => {
266
+ if (this.DEBUG) {
267
+ this._debug(`putCode address=${address.toString()} value=${short(value)}`)
268
+ }
269
+
270
+ this._caches?.code?.put(address, value)
271
+
272
+ const codeHash = keccak_256(value)
273
+ if (equalsBytes(codeHash, KECCAK256_NULL)) {
274
+ // If the code hash is the null hash, no code has to be stored
275
+ return
276
+ }
277
+
278
+ if ((await this.getAccount(address)) === undefined) {
279
+ await this.putAccount(address, new Account())
280
+ }
281
+ if (this.DEBUG) {
282
+ this._debug(`Update codeHash (-> ${short(codeHash)}) for account ${address}`)
283
+ }
284
+
285
+ const codeChunks = chunkifyBinaryTreeCode(value)
286
+ const chunkStems = generateBinaryTreeCodeStems(codeChunks.length, address, this.hashFunction)
287
+
288
+ const chunkSuffixes: number[] = generateBinaryTreeChunkSuffixes(codeChunks.length)
289
+ // Put the code chunks corresponding to the first stem (up to 128 chunks)
290
+ await this._tree.put(
291
+ chunkStems[0],
292
+ chunkSuffixes.slice(
293
+ 0,
294
+ chunkSuffixes.length <= BINARY_TREE_CODE_OFFSET
295
+ ? chunkSuffixes.length
296
+ : BINARY_TREE_CODE_OFFSET,
297
+ ),
298
+ codeChunks.slice(
299
+ 0,
300
+ codeChunks.length <= BINARY_TREE_CODE_OFFSET ? codeChunks.length : BINARY_TREE_CODE_OFFSET,
301
+ ),
302
+ )
303
+
304
+ // Put additional chunks under additional stems as applicable
305
+ for (let stem = 1; stem < chunkStems.length; stem++) {
306
+ const sliceStart = BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * (stem - 1)
307
+ const sliceEnd =
308
+ value.length <= BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * stem
309
+ ? value.length
310
+ : BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * stem
311
+ await this._tree.put(
312
+ chunkStems[stem],
313
+ chunkSuffixes.slice(sliceStart, sliceEnd),
314
+ codeChunks.slice(sliceStart, sliceEnd),
315
+ )
316
+ }
317
+ await this.modifyAccountFields(address, {
318
+ codeHash,
319
+ codeSize: value.length,
320
+ })
321
+ }
322
+
323
+ getCode = async (address: Address): Promise<Uint8Array> => {
324
+ if (this.DEBUG) {
325
+ this._debug(`getCode address=${address.toString()}`)
326
+ }
327
+
328
+ const elem = this._caches?.code?.get(address)
329
+ if (elem !== undefined) {
330
+ return elem.code ?? new Uint8Array(0)
331
+ }
332
+
333
+ const account = await this.getAccount(address)
334
+ if (!account) {
335
+ return new Uint8Array(0)
336
+ }
337
+ if (!account.isContract()) {
338
+ return new Uint8Array(0)
339
+ }
340
+
341
+ // allocate the code
342
+ const codeSize = account.codeSize
343
+
344
+ const stems = generateBinaryTreeCodeStems(
345
+ Math.ceil(codeSize / BINARY_TREE_CODE_CHUNK_SIZE),
346
+ address,
347
+ this.hashFunction,
348
+ )
349
+ const chunkSuffixes = generateBinaryTreeChunkSuffixes(
350
+ Math.ceil(codeSize / BINARY_TREE_CODE_CHUNK_SIZE),
351
+ )
352
+
353
+ const chunksByStem = new Array(stems.length)
354
+ // Retrieve the code chunks stored in the first leaf node
355
+ chunksByStem[0] = await this._tree.get(
356
+ stems[0],
357
+ chunkSuffixes.slice(
358
+ 0,
359
+ codeSize <= BINARY_TREE_CODE_OFFSET ? codeSize : BINARY_TREE_CODE_OFFSET,
360
+ ),
361
+ )
362
+
363
+ // Retrieve code chunks on any additional stems
364
+ for (let stem = 1; stem < stems.length; stem++) {
365
+ const sliceStart = BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * (stem - 1)
366
+ const sliceEnd =
367
+ codeSize <= BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * stem
368
+ ? codeSize
369
+ : BINARY_TREE_CODE_OFFSET + BINARY_TREE_NODE_WIDTH * stem
370
+ chunksByStem[stem] = await this._tree.get(
371
+ stems[stem],
372
+ chunkSuffixes.slice(sliceStart, sliceEnd),
373
+ )
374
+ }
375
+ const chunks = chunksByStem.flat()
376
+ const code = new Uint8Array(codeSize)
377
+ // Insert code chunks into final array (skipping PUSHDATA overflow indicator byte)
378
+ for (let x = 0; x < chunks.length; x++) {
379
+ if (chunks[x] === undefined)
380
+ throw EthereumJSErrorWithoutCode(`expected code chunk at index ${x}, got undefined`)
381
+
382
+ let lastChunkByteIndex = BINARY_TREE_CODE_CHUNK_SIZE
383
+ // Determine code ending byte (if we're on the last chunk)
384
+ if (x === chunks.length - 1) {
385
+ // On the last chunk, the slice either ends on a partial chunk (if codeSize doesn't exactly fit in full chunks), or a full chunk
386
+ lastChunkByteIndex = codeSize % BINARY_TREE_CODE_CHUNK_SIZE || BINARY_TREE_CODE_CHUNK_SIZE
387
+ }
388
+ code.set(
389
+ chunks[x]!.slice(1, lastChunkByteIndex + 1),
390
+ code.byteOffset + x * BINARY_TREE_CODE_CHUNK_SIZE,
391
+ )
392
+ }
393
+ this._caches?.code?.put(address, code)
394
+
395
+ return code
396
+ }
397
+
398
+ getCodeSize = async (address: Address): Promise<number> => {
399
+ const accountBytes = (
400
+ await this._tree.get(getBinaryTreeStem(this.hashFunction, address), [
401
+ BinaryTreeLeafType.BasicData,
402
+ ])
403
+ )[0]
404
+ if (accountBytes === null) return 0
405
+ return decodeBinaryTreeLeafBasicData(accountBytes).codeSize
406
+ }
407
+ getStorage = async (address: Address, key: Uint8Array): Promise<Uint8Array> => {
408
+ if (key.length !== 32) {
409
+ throw EthereumJSErrorWithoutCode('Storage key must be 32 bytes long')
410
+ }
411
+ const cachedValue = this._caches?.storage?.get(address, key)
412
+ if (cachedValue !== undefined) {
413
+ const decoded = RLP.decode(cachedValue ?? new Uint8Array(0)) as Uint8Array
414
+ return decoded
415
+ }
416
+
417
+ const account = await this.getAccount(address)
418
+ if (!account) {
419
+ return new Uint8Array()
420
+ }
421
+ const storageKey = getBinaryTreeKeyForStorageSlot(
422
+ address,
423
+ bytesToBigInt(key),
424
+ this.hashFunction,
425
+ )
426
+ const value = await this._tree.get(storageKey.slice(0, 31), [storageKey[31]])
427
+
428
+ this._caches?.storage?.put(address, key, value[0] ?? hexToBytes('0x80'))
429
+ const decoded = (value[0] ?? new Uint8Array(0)) as Uint8Array
430
+ return setLengthLeft(decoded, 32)
431
+ }
432
+
433
+ putStorage = async (address: Address, key: Uint8Array, value: Uint8Array): Promise<void> => {
434
+ this._caches?.storage?.put(address, key, RLP.encode(value))
435
+ if (this._caches?.storage === undefined) {
436
+ const storageKey = getBinaryTreeKeyForStorageSlot(
437
+ address,
438
+ bytesToBigInt(key),
439
+ this.hashFunction,
440
+ )
441
+ await this._tree.put(storageKey.slice(0, 31), [storageKey[31]], [setLengthLeft(value, 32)])
442
+ }
443
+ }
444
+
445
+ clearStorage = async (address: Address): Promise<void> => {
446
+ this._caches?.storage?.clearStorage(address)
447
+ }
448
+
449
+ checkpoint = async (): Promise<void> => {
450
+ this._tree.checkpoint()
451
+ this._caches?.checkpoint()
452
+ this._checkpointCount++
453
+ }
454
+ commit = async (): Promise<void> => {
455
+ await this._tree.commit()
456
+ this._caches?.commit()
457
+ this._checkpointCount--
458
+
459
+ if (this._checkpointCount === 0) {
460
+ await this.flush()
461
+ this.originalStorageCache.clear()
462
+ }
463
+
464
+ if (this.DEBUG) {
465
+ this._debug(`state checkpoint committed`)
466
+ }
467
+ }
468
+ revert = async (): Promise<void> => {
469
+ await this._tree.revert()
470
+ this._caches?.revert()
471
+
472
+ this._checkpointCount--
473
+
474
+ if (this._checkpointCount === 0) {
475
+ await this.flush()
476
+ this.originalStorageCache.clear()
477
+ }
478
+ }
479
+
480
+ flush = async (): Promise<void> => {
481
+ const codeItems = this._caches?.code?.flush() ?? []
482
+ for (const item of codeItems) {
483
+ const addr = createAddressFromString(`0x${item[0]}`)
484
+
485
+ const code = item[1].code
486
+ if (code === undefined) {
487
+ continue
488
+ }
489
+
490
+ await this.putCode(addr, code)
491
+ }
492
+
493
+ const storageItems = this._caches?.storage?.flush() ?? []
494
+ for (const item of storageItems) {
495
+ const address = createAddressFromString(`0x${item[0]}`)
496
+ const keyHex = item[1]
497
+ const keyBytes = unprefixedHexToBytes(keyHex)
498
+ const value = item[2]
499
+
500
+ const decoded = RLP.decode(value ?? new Uint8Array(0)) as Uint8Array
501
+ const account = await this.getAccount(address)
502
+ if (account) {
503
+ await this.putStorage(address, keyBytes, decoded)
504
+ }
505
+ }
506
+
507
+ const accountItems = this._caches?.account?.flush() ?? []
508
+ for (const item of accountItems) {
509
+ const address = createAddressFromString(`0x${item[0]}`)
510
+ const elem = item[1]
511
+ if (elem.accountRLP === undefined) {
512
+ await this.deleteAccount(address)
513
+ } else {
514
+ const account = createPartialAccountFromRLP(elem.accountRLP)
515
+ await this.putAccount(address, account)
516
+ }
517
+ }
518
+ }
519
+
520
+ async getComputedValue(
521
+ accessedState: BinaryTreeAccessedStateWithAddress,
522
+ ): Promise<PrefixedHexString | null> {
523
+ const { address, type } = accessedState
524
+
525
+ switch (type) {
526
+ case BinaryTreeAccessedStateType.BasicData: {
527
+ if (this._caches === undefined) {
528
+ const accountData = await this.getAccount(address)
529
+ if (accountData === undefined) {
530
+ return null
531
+ }
532
+ const basicDataBytes = encodeBinaryTreeLeafBasicData(accountData)
533
+ return bytesToHex(basicDataBytes)
534
+ } else {
535
+ const encodedAccount = this._caches?.account?.get(address)?.accountRLP
536
+ if (encodedAccount === undefined) {
537
+ return null
538
+ }
539
+ const basicDataBytes = encodeBinaryTreeLeafBasicData(
540
+ createPartialAccountFromRLP(encodedAccount),
541
+ )
542
+ return bytesToHex(basicDataBytes)
543
+ }
544
+ }
545
+
546
+ case BinaryTreeAccessedStateType.CodeHash: {
547
+ if (this._caches === undefined) {
548
+ const accountData = await this.getAccount(address)
549
+ if (accountData === undefined) {
550
+ return null
551
+ }
552
+
553
+ return bytesToHex(accountData.codeHash)
554
+ } else {
555
+ const encodedAccount = this._caches?.account?.get(address)?.accountRLP
556
+ if (encodedAccount === undefined) {
557
+ return null
558
+ }
559
+ return bytesToHex(createPartialAccountFromRLP(encodedAccount).codeHash)
560
+ }
561
+ }
562
+
563
+ case BinaryTreeAccessedStateType.Code: {
564
+ const { codeOffset } = accessedState
565
+ let code: Uint8Array | undefined | null = null
566
+ if (this._caches === undefined) {
567
+ code = await this.getCode(address)
568
+ if (code === undefined) {
569
+ return null
570
+ }
571
+ } else {
572
+ code = this._caches?.code?.get(address)?.code
573
+ if (code === undefined) {
574
+ return null
575
+ }
576
+ }
577
+
578
+ // we can only compare the actual code because to compare the first byte would
579
+ // be very tricky and impossible in certain scenarios like when the previous code chunk
580
+ // was not accessed and hence not even provided in the witness
581
+ // We are left-padding with two zeroes to get a 32-byte length, but these bytes should not be considered reliable
582
+ return bytesToHex(
583
+ setLengthLeft(
584
+ setLengthRight(
585
+ code.slice(codeOffset, codeOffset + BINARY_TREE_CODE_CHUNK_SIZE),
586
+ BINARY_TREE_CODE_CHUNK_SIZE,
587
+ ),
588
+ BINARY_TREE_CODE_CHUNK_SIZE + 1,
589
+ ),
590
+ )
591
+ }
592
+
593
+ case BinaryTreeAccessedStateType.Storage: {
594
+ const { slot } = accessedState
595
+ const key = setLengthLeft(bigIntToBytes(slot), 32)
596
+ let storage: Uint8Array | undefined | null = null
597
+ if (this._caches === undefined) {
598
+ storage = await this.getStorage(address, key)
599
+ if (storage === undefined) {
600
+ return null
601
+ }
602
+ } else {
603
+ storage = this._caches?.storage?.get(address, key)
604
+ }
605
+ if (storage === undefined) {
606
+ return null
607
+ }
608
+ return bytesToHex(setLengthLeft(storage, 32))
609
+ }
610
+ }
611
+ }
612
+
613
+ // Verifies that the witness post-state matches the computed post-state
614
+ async verifyBinaryTreePostState(
615
+ accessWitness: BinaryTreeAccessWitnessInterface,
616
+ ): Promise<boolean> {
617
+ // track what all chunks were accessed so as to compare in the end if any chunks were missed
618
+ // in access while comparing against the provided poststate in the execution witness
619
+ const accessedChunks = new Map<string, boolean>()
620
+ // switch to false if postVerify fails
621
+ let postFailures = 0
622
+
623
+ for (const accessedState of accessWitness?.accesses() ?? []) {
624
+ const { address, type } = accessedState
625
+ let extraMeta = ''
626
+ if (accessedState.type === BinaryTreeAccessedStateType.Code) {
627
+ extraMeta = `codeOffset=${accessedState.codeOffset}`
628
+ } else if (accessedState.type === BinaryTreeAccessedStateType.Storage) {
629
+ extraMeta = `slot=${accessedState.slot}`
630
+ }
631
+
632
+ const { chunkKey } = accessedState
633
+ accessedChunks.set(chunkKey, true)
634
+ let computedValue: PrefixedHexString | null | undefined =
635
+ await this.getComputedValue(accessedState)
636
+ if (computedValue === undefined) {
637
+ this.DEBUG &&
638
+ this._debug(
639
+ `Missing computed value for address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`,
640
+ )
641
+ postFailures++
642
+ continue
643
+ }
644
+
645
+ let canonicalValue: PrefixedHexString | null | undefined = this._postState[chunkKey]
646
+
647
+ if (canonicalValue === undefined) {
648
+ this.DEBUG &&
649
+ this._debug(
650
+ `Block accesses missing from postState for address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`,
651
+ )
652
+ postFailures++
653
+ continue
654
+ }
655
+
656
+ // if the access type is code, then we can't match the first byte because since the computed value
657
+ // doesn't has the first byte for push data since previous chunk code itself might not be available
658
+ if (accessedState.type === BinaryTreeAccessedStateType.Code) {
659
+ computedValue = computedValue !== null ? `0x${computedValue.slice(4)}` : null
660
+ canonicalValue = canonicalValue !== null ? `0x${canonicalValue.slice(4)}` : null
661
+ } else if (
662
+ accessedState.type === BinaryTreeAccessedStateType.Storage &&
663
+ canonicalValue === null &&
664
+ computedValue === ZEROVALUE
665
+ ) {
666
+ canonicalValue = ZEROVALUE
667
+ }
668
+
669
+ this._debug(`computed ${computedValue} canonical ${canonicalValue}`)
670
+ if (computedValue !== canonicalValue) {
671
+ if (type === BinaryTreeAccessedStateType.BasicData) {
672
+ this.DEBUG &&
673
+ this._debug(
674
+ `canonical value: `,
675
+ canonicalValue === null
676
+ ? null
677
+ : decodeBinaryTreeLeafBasicData(hexToBytes(canonicalValue)),
678
+ )
679
+ this.DEBUG &&
680
+ this._debug(
681
+ `computed value: `,
682
+ computedValue === null
683
+ ? null
684
+ : decodeBinaryTreeLeafBasicData(hexToBytes(computedValue)),
685
+ )
686
+ }
687
+ this.DEBUG &&
688
+ this._debug(
689
+ `Block accesses mismatch address=${address} type=${type} ${extraMeta} chunkKey=${chunkKey}`,
690
+ )
691
+ this.DEBUG && this._debug(`expected=${canonicalValue}`)
692
+ this.DEBUG && this._debug(`computed=${computedValue}`)
693
+ postFailures++
694
+ }
695
+ }
696
+
697
+ for (const canChunkKey of Object.keys(this._postState)) {
698
+ if (accessedChunks.get(canChunkKey) === undefined) {
699
+ this.DEBUG && this._debug(`Missing chunk access for canChunkKey=${canChunkKey}`)
700
+ postFailures++
701
+ }
702
+ }
703
+
704
+ const verifyPassed = postFailures === 0
705
+ this.DEBUG &&
706
+ this._debug(
707
+ `verifyBinaryTreePostState verifyPassed=${verifyPassed} postFailures=${postFailures}`,
708
+ )
709
+
710
+ return verifyPassed
711
+ }
712
+
713
+ getStateRoot(): Promise<Uint8Array> {
714
+ return Promise.resolve(this._tree.root())
715
+ }
716
+
717
+ setStateRoot(stateRoot: Uint8Array, clearCache?: boolean): Promise<void> {
718
+ this._tree.root(stateRoot)
719
+ clearCache === true && this.clearCaches()
720
+ return Promise.resolve()
721
+ }
722
+ hasStateRoot(root: Uint8Array): Promise<boolean> {
723
+ return this._tree.checkRoot(root)
724
+ }
725
+ dumpStorage?(_address: Address): Promise<StorageDump> {
726
+ throw EthereumJSErrorWithoutCode('Method not implemented.')
727
+ }
728
+ dumpStorageRange?(_address: Address, _startKey: bigint, _limit: number): Promise<StorageRange> {
729
+ throw EthereumJSErrorWithoutCode('Method not implemented.')
730
+ }
731
+ clearCaches(): void {
732
+ this._caches?.clear()
733
+ }
734
+ shallowCopy(_downlevelCaches?: boolean): StateManagerInterface {
735
+ throw EthereumJSErrorWithoutCode('Method not implemented.')
736
+ }
737
+ async checkChunkWitnessPresent(_address: Address, _codeOffset: number): Promise<boolean> {
738
+ throw EthereumJSErrorWithoutCode('Method not implemented.')
739
+ }
740
+ async generateCanonicalGenesis(genesisState: GenesisState) {
741
+ await this._tree.createRootNode()
742
+ await this.checkpoint()
743
+ for (const addressStr of Object.keys(genesisState) as PrefixedHexString[]) {
744
+ const addrState = genesisState[addressStr]
745
+ let nonce: PrefixedHexString | undefined
746
+ let balance: PrefixedHexString | bigint
747
+ let code: PrefixedHexString | undefined
748
+ let storage: StoragePair[] | undefined = []
749
+ if (Array.isArray(addrState)) {
750
+ ;[balance, code, storage, nonce] = addrState
751
+ } else {
752
+ balance = hexToBigInt(addrState)
753
+ nonce = '0x1'
754
+ code = '0x'
755
+ }
756
+ const address = createAddressFromString(addressStr)
757
+ await this.putAccount(address, new Account())
758
+ const codeBuf = hexToBytes(code ?? '0x')
759
+
760
+ const codeHash = this.keccakFunction(codeBuf)
761
+
762
+ // Set contract storage
763
+ if (storage !== undefined) {
764
+ for (const [storageKey, valHex] of storage) {
765
+ const val = hexToBytes(valHex)
766
+ if (['0x', '0x00'].includes(bytesToHex(val))) {
767
+ continue
768
+ }
769
+ const key = setLengthLeft(hexToBytes(storageKey), 32)
770
+ await this.putStorage(address, key, val)
771
+ }
772
+ }
773
+ // Put contract code
774
+ await this.putCode(address, codeBuf)
775
+
776
+ // Put account data
777
+ const account = createPartialAccount({
778
+ nonce,
779
+ balance,
780
+ codeHash,
781
+ codeSize: codeBuf.byteLength,
782
+ })
783
+
784
+ await this.putAccount(address, account)
785
+ }
786
+ await this.commit()
787
+ await this.flush()
788
+ }
789
+ }