@feelyourprotocol/blockchain 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 (132) hide show
  1. package/README.md +269 -0
  2. package/dist/cjs/blockchain.d.ts +372 -0
  3. package/dist/cjs/blockchain.d.ts.map +1 -0
  4. package/dist/cjs/blockchain.js +1105 -0
  5. package/dist/cjs/blockchain.js.map +1 -0
  6. package/dist/cjs/consensus/casper.d.ts +16 -0
  7. package/dist/cjs/consensus/casper.d.ts.map +1 -0
  8. package/dist/cjs/consensus/casper.js +28 -0
  9. package/dist/cjs/consensus/casper.js.map +1 -0
  10. package/dist/cjs/consensus/clique.d.ts +164 -0
  11. package/dist/cjs/consensus/clique.d.ts.map +1 -0
  12. package/dist/cjs/consensus/clique.js +520 -0
  13. package/dist/cjs/consensus/clique.js.map +1 -0
  14. package/dist/cjs/consensus/ethash.d.ts +29 -0
  15. package/dist/cjs/consensus/ethash.d.ts.map +1 -0
  16. package/dist/cjs/consensus/ethash.js +48 -0
  17. package/dist/cjs/consensus/ethash.js.map +1 -0
  18. package/dist/cjs/consensus/index.d.ts +5 -0
  19. package/dist/cjs/consensus/index.d.ts.map +1 -0
  20. package/dist/cjs/consensus/index.js +10 -0
  21. package/dist/cjs/consensus/index.js.map +1 -0
  22. package/dist/cjs/constructors.d.ts +13 -0
  23. package/dist/cjs/constructors.d.ts.map +1 -0
  24. package/dist/cjs/constructors.js +80 -0
  25. package/dist/cjs/constructors.js.map +1 -0
  26. package/dist/cjs/db/cache.d.ts +17 -0
  27. package/dist/cjs/db/cache.d.ts.map +1 -0
  28. package/dist/cjs/db/cache.js +38 -0
  29. package/dist/cjs/db/cache.js.map +1 -0
  30. package/dist/cjs/db/constants.d.ts +23 -0
  31. package/dist/cjs/db/constants.d.ts.map +1 -0
  32. package/dist/cjs/db/constants.js +54 -0
  33. package/dist/cjs/db/constants.js.map +1 -0
  34. package/dist/cjs/db/helpers.d.ts +9 -0
  35. package/dist/cjs/db/helpers.d.ts.map +1 -0
  36. package/dist/cjs/db/helpers.js +68 -0
  37. package/dist/cjs/db/helpers.js.map +1 -0
  38. package/dist/cjs/db/manager.d.ts +78 -0
  39. package/dist/cjs/db/manager.d.ts.map +1 -0
  40. package/dist/cjs/db/manager.js +203 -0
  41. package/dist/cjs/db/manager.js.map +1 -0
  42. package/dist/cjs/db/operation.d.ts +45 -0
  43. package/dist/cjs/db/operation.d.ts.map +1 -0
  44. package/dist/cjs/db/operation.js +110 -0
  45. package/dist/cjs/db/operation.js.map +1 -0
  46. package/dist/cjs/helpers.d.ts +19 -0
  47. package/dist/cjs/helpers.d.ts.map +1 -0
  48. package/dist/cjs/helpers.js +34 -0
  49. package/dist/cjs/helpers.js.map +1 -0
  50. package/dist/cjs/index.d.ts +7 -0
  51. package/dist/cjs/index.d.ts.map +1 -0
  52. package/dist/cjs/index.js +33 -0
  53. package/dist/cjs/index.js.map +1 -0
  54. package/dist/cjs/package.json +3 -0
  55. package/dist/cjs/types.d.ts +219 -0
  56. package/dist/cjs/types.d.ts.map +1 -0
  57. package/dist/cjs/types.js +3 -0
  58. package/dist/cjs/types.js.map +1 -0
  59. package/dist/esm/blockchain.d.ts +372 -0
  60. package/dist/esm/blockchain.d.ts.map +1 -0
  61. package/dist/esm/blockchain.js +1101 -0
  62. package/dist/esm/blockchain.js.map +1 -0
  63. package/dist/esm/consensus/casper.d.ts +16 -0
  64. package/dist/esm/consensus/casper.d.ts.map +1 -0
  65. package/dist/esm/consensus/casper.js +24 -0
  66. package/dist/esm/consensus/casper.js.map +1 -0
  67. package/dist/esm/consensus/clique.d.ts +164 -0
  68. package/dist/esm/consensus/clique.d.ts.map +1 -0
  69. package/dist/esm/consensus/clique.js +516 -0
  70. package/dist/esm/consensus/clique.js.map +1 -0
  71. package/dist/esm/consensus/ethash.d.ts +29 -0
  72. package/dist/esm/consensus/ethash.d.ts.map +1 -0
  73. package/dist/esm/consensus/ethash.js +44 -0
  74. package/dist/esm/consensus/ethash.js.map +1 -0
  75. package/dist/esm/consensus/index.d.ts +5 -0
  76. package/dist/esm/consensus/index.d.ts.map +1 -0
  77. package/dist/esm/consensus/index.js +5 -0
  78. package/dist/esm/consensus/index.js.map +1 -0
  79. package/dist/esm/constructors.d.ts +13 -0
  80. package/dist/esm/constructors.d.ts.map +1 -0
  81. package/dist/esm/constructors.js +76 -0
  82. package/dist/esm/constructors.js.map +1 -0
  83. package/dist/esm/db/cache.d.ts +17 -0
  84. package/dist/esm/db/cache.d.ts.map +1 -0
  85. package/dist/esm/db/cache.js +34 -0
  86. package/dist/esm/db/cache.js.map +1 -0
  87. package/dist/esm/db/constants.d.ts +23 -0
  88. package/dist/esm/db/constants.d.ts.map +1 -0
  89. package/dist/esm/db/constants.js +46 -0
  90. package/dist/esm/db/constants.js.map +1 -0
  91. package/dist/esm/db/helpers.d.ts +9 -0
  92. package/dist/esm/db/helpers.d.ts.map +1 -0
  93. package/dist/esm/db/helpers.js +61 -0
  94. package/dist/esm/db/helpers.js.map +1 -0
  95. package/dist/esm/db/manager.d.ts +78 -0
  96. package/dist/esm/db/manager.d.ts.map +1 -0
  97. package/dist/esm/db/manager.js +199 -0
  98. package/dist/esm/db/manager.js.map +1 -0
  99. package/dist/esm/db/operation.d.ts +45 -0
  100. package/dist/esm/db/operation.d.ts.map +1 -0
  101. package/dist/esm/db/operation.js +106 -0
  102. package/dist/esm/db/operation.js.map +1 -0
  103. package/dist/esm/helpers.d.ts +19 -0
  104. package/dist/esm/helpers.d.ts.map +1 -0
  105. package/dist/esm/helpers.js +30 -0
  106. package/dist/esm/helpers.js.map +1 -0
  107. package/dist/esm/index.d.ts +7 -0
  108. package/dist/esm/index.d.ts.map +1 -0
  109. package/dist/esm/index.js +7 -0
  110. package/dist/esm/index.js.map +1 -0
  111. package/dist/esm/package.json +3 -0
  112. package/dist/esm/types.d.ts +219 -0
  113. package/dist/esm/types.d.ts.map +1 -0
  114. package/dist/esm/types.js +2 -0
  115. package/dist/esm/types.js.map +1 -0
  116. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
  117. package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
  118. package/package.json +75 -0
  119. package/src/blockchain.ts +1353 -0
  120. package/src/consensus/casper.ts +33 -0
  121. package/src/consensus/clique.ts +633 -0
  122. package/src/consensus/ethash.ts +69 -0
  123. package/src/consensus/index.ts +5 -0
  124. package/src/constructors.ts +119 -0
  125. package/src/db/cache.ts +39 -0
  126. package/src/db/constants.ts +73 -0
  127. package/src/db/helpers.ts +81 -0
  128. package/src/db/manager.ts +243 -0
  129. package/src/db/operation.ts +152 -0
  130. package/src/helpers.ts +37 -0
  131. package/src/index.ts +12 -0
  132. package/src/types.ts +256 -0
@@ -0,0 +1,1353 @@
1
+ import { Block, BlockHeader, createBlock } from '@feelyourprotocol/block'
2
+ import { Common, ConsensusAlgorithm, ConsensusType, Hardfork, Mainnet } from '@feelyourprotocol/common'
3
+ import {
4
+ BIGINT_0,
5
+ BIGINT_1,
6
+ BIGINT_8,
7
+ EthereumJSErrorWithoutCode,
8
+ KECCAK256_RLP,
9
+ Lock,
10
+ MapDB,
11
+ SHA256_NULL,
12
+ bigIntToHex,
13
+ bytesToHex,
14
+ bytesToUnprefixedHex,
15
+ concatBytes,
16
+ equalsBytes,
17
+ isDebugEnabled,
18
+ } from '@feelyourprotocol/util'
19
+ import debugDefault from 'debug'
20
+ import { EventEmitter } from 'eventemitter3'
21
+
22
+ import { CasperConsensus } from './consensus/casper.ts'
23
+ import {
24
+ DBOp,
25
+ DBSaveLookups,
26
+ DBSetBlockOrHeader,
27
+ DBSetHashToNumber,
28
+ DBSetTD,
29
+ } from './db/helpers.ts'
30
+ import { DBManager } from './db/manager.ts'
31
+ import { DBTarget } from './db/operation.ts'
32
+
33
+ import type { HeaderData } from '@feelyourprotocol/block'
34
+ import type { CliqueConfig, GenesisState } from '@feelyourprotocol/common'
35
+ import type { BigIntLike, DB, DBObject } from '@feelyourprotocol/util'
36
+ import type { Debugger } from 'debug'
37
+ import type {
38
+ BlockchainEvent,
39
+ BlockchainInterface,
40
+ BlockchainOptions,
41
+ Consensus,
42
+ ConsensusDict,
43
+ OnBlock,
44
+ } from './types.ts'
45
+
46
+ /**
47
+ * Blockchain implementation to create and maintain a valid canonical chain
48
+ * of block headers or blocks with support for reorgs and the ability to provide
49
+ * custom DB backends.
50
+ *
51
+ * By default consensus validation is not provided since with the switch to
52
+ * Proof-of-Stake consensus is validated by the Ethereum consensus layer.
53
+ * If consensus validation is desired for Ethash or Clique blockchains the
54
+ * optional `consensusDict` option can be used to pass in validation objects.
55
+ *
56
+ * A Blockchain object can be created with the constructor method:
57
+ *
58
+ * - {@link createBlockchain}
59
+ */
60
+ export class Blockchain implements BlockchainInterface {
61
+ db: DB<Uint8Array | string, Uint8Array | string | DBObject>
62
+ dbManager: DBManager
63
+ events: EventEmitter<BlockchainEvent>
64
+
65
+ private _genesisBlock?: Block /** The genesis block of this blockchain */
66
+ private _customGenesisState?: GenesisState /** Custom genesis state */
67
+
68
+ /**
69
+ * The following two heads and the heads stored within the `_heads` always point
70
+ * to a hash in the canonical chain and never to a stale hash.
71
+ * With the exception of `_headHeaderHash` this does not necessarily need to be
72
+ * the hash with the highest total difficulty.
73
+ */
74
+ /** The hash of the current head block */
75
+ private _headBlockHash?: Uint8Array
76
+ /** The hash of the current head header */
77
+ private _headHeaderHash?: Uint8Array
78
+
79
+ /**
80
+ * A Map which stores the head of each key (for instance the "vm" key) which is
81
+ * updated along a {@link Blockchain.iterator} method run and can be used to (re)run
82
+ * non-verified blocks (for instance in the VM).
83
+ */
84
+ private _heads: { [key: string]: Uint8Array }
85
+
86
+ private _lock: Lock
87
+
88
+ public readonly common: Common
89
+ private _hardforkByHeadBlockNumber: boolean
90
+ private readonly _validateBlocks: boolean
91
+ private readonly _validateConsensus: boolean
92
+ private _consensusDict: ConsensusDict
93
+
94
+ /**
95
+ * This is used to track which canonical blocks are deleted. After a method calls
96
+ * `_deleteCanonicalChainReferences`, if this array has any items, the
97
+ * `deletedCanonicalBlocks` event is emitted with the array as argument.
98
+ */
99
+ private _deletedBlocks: Block[] = []
100
+
101
+ private DEBUG: boolean // Guard for debug logs
102
+ private _debug: Debugger
103
+
104
+ /**
105
+ * Creates new Blockchain object.
106
+ *
107
+ * @deprecated The direct usage of this constructor is discouraged since
108
+ * non-finalized async initialization might lead to side effects. Please
109
+ * use the async {@link createBlockchain} constructor instead (same API).
110
+ *
111
+ * @param opts An object with the options that this constructor takes. See
112
+ * {@link BlockchainOptions}.
113
+ */
114
+ constructor(opts: BlockchainOptions = {}) {
115
+ this.DEBUG = isDebugEnabled('ethjs')
116
+ this._debug = debugDefault('blockchain:#')
117
+
118
+ if (opts.common) {
119
+ this.common = opts.common
120
+ } else {
121
+ const DEFAULT_CHAIN = Mainnet
122
+ const DEFAULT_HARDFORK = Hardfork.Chainstart
123
+ this.common = new Common({
124
+ chain: DEFAULT_CHAIN,
125
+ hardfork: DEFAULT_HARDFORK,
126
+ })
127
+ }
128
+
129
+ this._hardforkByHeadBlockNumber = opts.hardforkByHeadBlockNumber ?? false
130
+ this._validateBlocks = opts.validateBlocks ?? true
131
+ this._validateConsensus = opts.validateConsensus ?? false
132
+ this._customGenesisState = opts.genesisState
133
+
134
+ this.db = opts.db ?? new MapDB()
135
+
136
+ this.dbManager = new DBManager(this.db, this.common)
137
+
138
+ this.events = new EventEmitter<BlockchainEvent>()
139
+
140
+ this._consensusDict = {}
141
+ this._consensusDict[ConsensusAlgorithm.Casper] = new CasperConsensus()
142
+
143
+ if (opts.consensusDict !== undefined) {
144
+ this._consensusDict = { ...this._consensusDict, ...opts.consensusDict }
145
+ }
146
+ this._consensusCheck()
147
+
148
+ this._heads = {}
149
+
150
+ this._lock = new Lock()
151
+
152
+ if (opts.genesisBlock && !opts.genesisBlock.isGenesis()) {
153
+ throw 'supplied block is not a genesis block'
154
+ }
155
+ }
156
+
157
+ private _consensusCheck() {
158
+ if (this._validateConsensus && this.consensus === undefined) {
159
+ throw EthereumJSErrorWithoutCode(
160
+ `Consensus object for ${this.common.consensusAlgorithm()} must be passed (see consensusDict option) if consensus validation is activated`,
161
+ )
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Returns an eventual consensus object matching the current consensus algorithm from Common
167
+ * or undefined if non available
168
+ */
169
+ get consensus(): Consensus | undefined {
170
+ return this._consensusDict[this.common.consensusAlgorithm()]
171
+ }
172
+
173
+ /**
174
+ * Returns a deep copy of this {@link Blockchain} instance.
175
+ *
176
+ * Note: this does not make a copy of the underlying db
177
+ * since it is unknown if the source is on disk or in memory.
178
+ * This should not be a significant issue in most usage since
179
+ * the queries will only reflect the instance's known data.
180
+ * If you would like this copied blockchain to use another db
181
+ * set the {@link db} of this returned instance to a copy of
182
+ * the original.
183
+ */
184
+ shallowCopy(): Blockchain {
185
+ const copiedBlockchain = Object.create(
186
+ Object.getPrototypeOf(this),
187
+ Object.getOwnPropertyDescriptors(this),
188
+ )
189
+ copiedBlockchain.common = this.common.copy()
190
+ return copiedBlockchain
191
+ }
192
+
193
+ /**
194
+ * Run a function after acquiring a lock. It is implied that we have already
195
+ * initialized the module (or we are calling this from the init function, like
196
+ * `_setCanonicalGenesisBlock`)
197
+ * @param action - function to run after acquiring a lock
198
+ * @hidden
199
+ */
200
+ private async runWithLock<T>(action: () => Promise<T>): Promise<T> {
201
+ try {
202
+ await this._lock.acquire()
203
+ const value = await action()
204
+ return value
205
+ } finally {
206
+ this._lock.release()
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Returns the specified iterator head.
212
+ *
213
+ * This function replaces the old Blockchain.getHead() method. Note that
214
+ * the function deviates from the old behavior and returns the
215
+ * genesis hash instead of the current head block if an iterator
216
+ * has not been run. This matches the behavior of {@link Blockchain.iterator}.
217
+ *
218
+ * @param name - Optional name of the iterator head (default: 'vm')
219
+ */
220
+ async getIteratorHead(name = 'vm'): Promise<Block> {
221
+ return this.runWithLock<Block>(async () => {
222
+ return (await this.getHead(name, false))!
223
+ })
224
+ }
225
+
226
+ /**
227
+ * This method differs from `getIteratorHead`. If the head is not found, it returns `undefined`.
228
+ * @param name - Optional name of the iterator head (default: 'vm')
229
+ * @returns
230
+ */
231
+ async getIteratorHeadSafe(name = 'vm'): Promise<Block | undefined> {
232
+ return this.runWithLock<Block | undefined>(async () => {
233
+ return this.getHead(name, true)
234
+ })
235
+ }
236
+
237
+ private async getHead(name: string, returnUndefinedIfNotSet: boolean = false) {
238
+ const headHash = this._heads[name]
239
+ if (headHash === undefined && returnUndefinedIfNotSet) {
240
+ return undefined
241
+ }
242
+ const hash = this._heads[name] ?? this.genesisBlock.hash()
243
+ const block = await this.getBlock(hash)
244
+ return block
245
+ }
246
+
247
+ /**
248
+ * Returns the latest header in the canonical chain.
249
+ */
250
+ async getCanonicalHeadHeader(): Promise<BlockHeader> {
251
+ return this.runWithLock<BlockHeader>(async () => {
252
+ if (!this._headHeaderHash) throw EthereumJSErrorWithoutCode('No head header set')
253
+ const header = await this._getHeader(this._headHeaderHash)
254
+ return header
255
+ })
256
+ }
257
+
258
+ /**
259
+ * Returns the latest full block in the canonical chain.
260
+ */
261
+ async getCanonicalHeadBlock(): Promise<Block> {
262
+ return this.runWithLock<Block>(async () => {
263
+ if (!this._headBlockHash) throw EthereumJSErrorWithoutCode('No head block set')
264
+ return this.getBlock(this._headBlockHash)
265
+ })
266
+ }
267
+
268
+ /**
269
+ * Adds blocks to the blockchain.
270
+ *
271
+ * If an invalid block is met the function will throw, blocks before will
272
+ * nevertheless remain in the DB. If any of the saved blocks has a higher
273
+ * total difficulty than the current max total difficulty the canonical
274
+ * chain is rebuilt and any stale heads/hashes are overwritten.
275
+ * @param blocks - The blocks to be added to the blockchain
276
+ */
277
+ async putBlocks(blocks: Block[]) {
278
+ for (let i = 0; i < blocks.length; i++) {
279
+ await this.putBlock(blocks[i])
280
+ }
281
+
282
+ this.DEBUG && this._debug(`put ${blocks.length} blocks`)
283
+ }
284
+
285
+ /**
286
+ * Adds a block to the blockchain.
287
+ *
288
+ * If the block is valid and has a higher total difficulty than the current
289
+ * max total difficulty, the canonical chain is rebuilt and any stale
290
+ * heads/hashes are overwritten.
291
+ * @param block - The block to be added to the blockchain
292
+ */
293
+ async putBlock(block: Block) {
294
+ await this._putBlockOrHeader(block)
295
+ }
296
+
297
+ /**
298
+ * Adds many headers to the blockchain.
299
+ *
300
+ * If an invalid header is met the function will throw, headers before will
301
+ * nevertheless remain in the DB. If any of the saved headers has a higher
302
+ * total difficulty than the current max total difficulty the canonical
303
+ * chain is rebuilt and any stale heads/hashes are overwritten.
304
+ * @param headers - The headers to be added to the blockchain
305
+ */
306
+ async putHeaders(headers: Array<any>) {
307
+ for (let i = 0; i < headers.length; i++) {
308
+ await this.putHeader(headers[i])
309
+ }
310
+
311
+ this.DEBUG && this._debug(`put ${headers.length} headers`)
312
+ }
313
+
314
+ /**
315
+ * Adds a header to the blockchain.
316
+ *
317
+ * If this header is valid and it has a higher total difficulty than the current
318
+ * max total difficulty, the canonical chain is rebuilt and any stale
319
+ * heads/hashes are overwritten.
320
+ * @param header - The header to be added to the blockchain
321
+ */
322
+ async putHeader(header: BlockHeader) {
323
+ await this._putBlockOrHeader(header)
324
+ }
325
+
326
+ /**
327
+ * Resets the canonical chain to canonicalHead number
328
+ *
329
+ * This updates the head hashes (if affected) to the hash corresponding to
330
+ * canonicalHead and cleans up canonical references greater than canonicalHead
331
+ * @param canonicalHead - The number to which chain should be reset to
332
+ */
333
+
334
+ async resetCanonicalHead(canonicalHead: bigint) {
335
+ let hash: Uint8Array | undefined
336
+ let canonicalHeadHash: Uint8Array | undefined
337
+ if (this.DEBUG) {
338
+ canonicalHeadHash = (await this.getCanonicalHeadHeader()).hash()
339
+ }
340
+ await this.runWithLock<void>(async () => {
341
+ hash = await this.dbManager.numberToHash(canonicalHead)
342
+ if (hash === undefined) {
343
+ throw EthereumJSErrorWithoutCode(`no block for ${canonicalHead} found in DB`)
344
+ }
345
+ const header = await this._getHeader(hash, canonicalHead)
346
+
347
+ const dbOps: DBOp[] = []
348
+ await this._deleteCanonicalChainReferences(canonicalHead + BIGINT_1, hash, dbOps)
349
+ const ops = dbOps.concat(this._saveHeadOps())
350
+
351
+ await this.dbManager.batch(ops)
352
+ await this.checkAndTransitionHardForkByNumber(canonicalHead, header.timestamp)
353
+ })
354
+ if (this._deletedBlocks.length > 0) {
355
+ this.events.emit('deletedCanonicalBlocks', this._deletedBlocks)
356
+ for (const block of this._deletedBlocks)
357
+ this.DEBUG &&
358
+ this._debug(
359
+ `deleted block along head reset: number ${block.header.number} hash ${bytesToHex(block.hash())}`,
360
+ )
361
+
362
+ this.DEBUG &&
363
+ this._debug(
364
+ `Canonical head set from ${bytesToHex(canonicalHeadHash!)} to ${bytesToHex(hash!)} (number ${bigIntToHex(canonicalHead)})`,
365
+ )
366
+
367
+ this._deletedBlocks = []
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Entrypoint for putting any block or block header. Verifies this block,
373
+ * checks the total TD: if this TD is higher than the current highest TD, we
374
+ * have thus found a new canonical block and have to rewrite the canonical
375
+ * chain. This also updates the head block hashes. If any of the older known
376
+ * canonical chains just became stale, then we also reset every _heads header
377
+ * which points to a stale header to the last verified header which was in the
378
+ * old canonical chain, but also in the new canonical chain. This thus rolls
379
+ * back these headers so that these can be updated to the "new" canonical
380
+ * header using the iterator method.
381
+ * @hidden
382
+ */
383
+ private async _putBlockOrHeader(item: Block | BlockHeader) {
384
+ await this.runWithLock<void>(async () => {
385
+ // Save the current sane state in case _putBlockOrHeader midway with some
386
+ // dirty changes in head trackers
387
+ const oldHeads = Object.assign({}, this._heads)
388
+ const oldHeadHeaderHash = this._headHeaderHash
389
+ const oldHeadBlockHash = this._headBlockHash
390
+ try {
391
+ const block =
392
+ item instanceof BlockHeader
393
+ ? new Block(item, undefined, undefined, undefined, { common: item.common })
394
+ : item
395
+ const isGenesis = block.isGenesis()
396
+
397
+ // we cannot overwrite the Genesis block after initializing the Blockchain
398
+ if (isGenesis) {
399
+ if (equalsBytes(this.genesisBlock.hash(), block.hash())) {
400
+ // Try to re-put the existing genesis block, accept this
401
+ return
402
+ }
403
+ throw EthereumJSErrorWithoutCode(
404
+ 'Cannot put a different genesis block than current blockchain genesis: create a new Blockchain',
405
+ )
406
+ }
407
+
408
+ const { header } = block
409
+ const blockHash = header.hash()
410
+ const blockNumber = header.number
411
+ let td = header.difficulty
412
+ const currentTd = { header: BIGINT_0, block: BIGINT_0 }
413
+ let dbOps: DBOp[] = []
414
+
415
+ if (block.common.chainId() !== this.common.chainId()) {
416
+ throw EthereumJSErrorWithoutCode(
417
+ `Chain mismatch while trying to put block or header. Chain ID of block: ${block.common.chainId}, chain ID of blockchain : ${this.common.chainId}`,
418
+ )
419
+ }
420
+
421
+ if (this._validateBlocks && !isGenesis && item instanceof Block) {
422
+ // this calls into `getBlock`, which is why we cannot lock yet
423
+ await this.validateBlock(block)
424
+ }
425
+
426
+ if (this._validateConsensus) {
427
+ await this.consensus!.validateConsensus(block)
428
+ }
429
+
430
+ // set total difficulty in the current context scope
431
+ if (this._headHeaderHash) {
432
+ currentTd.header = await this.getTotalDifficulty(this._headHeaderHash)
433
+ }
434
+ if (this._headBlockHash) {
435
+ currentTd.block = await this.getTotalDifficulty(this._headBlockHash)
436
+ }
437
+
438
+ // calculate the total difficulty of the new block
439
+ const parentTd = await this.getParentTD(header)
440
+ if (!block.isGenesis()) {
441
+ td += parentTd
442
+ }
443
+
444
+ // save total difficulty to the database
445
+ dbOps = dbOps.concat(DBSetTD(td, blockNumber, blockHash))
446
+
447
+ // save header/block to the database, but save the input not our wrapper block
448
+ dbOps = dbOps.concat(DBSetBlockOrHeader(item))
449
+
450
+ let commonAncestor: undefined | BlockHeader
451
+ let ancestorHeaders: undefined | BlockHeader[]
452
+ // if total difficulty is higher than current, add it to canonical chain
453
+ if (
454
+ block.isGenesis() ||
455
+ td > currentTd.header ||
456
+ block.common.consensusType() === ConsensusType.ProofOfStake
457
+ ) {
458
+ const foundCommon = await this.findCommonAncestor(header)
459
+ commonAncestor = foundCommon.commonAncestor
460
+ ancestorHeaders = foundCommon.ancestorHeaders
461
+
462
+ this._headHeaderHash = blockHash
463
+ if (item instanceof Block) {
464
+ this._headBlockHash = blockHash
465
+ }
466
+ if (this._hardforkByHeadBlockNumber) {
467
+ await this.checkAndTransitionHardForkByNumber(blockNumber, header.timestamp)
468
+ }
469
+
470
+ // delete higher number assignments and overwrite stale canonical chain
471
+ await this._deleteCanonicalChainReferences(blockNumber + BIGINT_1, blockHash, dbOps)
472
+ // from the current header block, check the blockchain in reverse (i.e.
473
+ // traverse `parentHash`) until `numberToHash` matches the current
474
+ // number/hash in the canonical chain also: overwrite any heads if these
475
+ // heads are stale in `_heads` and `_headBlockHash`
476
+ await this._rebuildCanonical(header, dbOps)
477
+ } else {
478
+ // the TD is lower than the current highest TD so we will add the block
479
+ // to the DB, but will not mark it as the canonical chain.
480
+ if (td > currentTd.block && item instanceof Block) {
481
+ this._headBlockHash = blockHash
482
+ }
483
+ // save hash to number lookup info even if rebuild not needed
484
+ dbOps.push(DBSetHashToNumber(blockHash, blockNumber))
485
+ }
486
+
487
+ const ops = dbOps.concat(this._saveHeadOps())
488
+ await this.dbManager.batch(ops)
489
+
490
+ await this.consensus?.newBlock(block, commonAncestor, ancestorHeaders)
491
+ this.DEBUG &&
492
+ this._debug(`put block number=${block.header.number} hash=${bytesToHex(blockHash)}`)
493
+ } catch (e) {
494
+ // restore head to the previously sane state
495
+ this._heads = oldHeads
496
+ this._headHeaderHash = oldHeadHeaderHash
497
+ this._headBlockHash = oldHeadBlockHash
498
+ throw e
499
+ }
500
+ })
501
+ if (this._deletedBlocks.length > 0) {
502
+ this.events.emit('deletedCanonicalBlocks', this._deletedBlocks)
503
+ for (const block of this._deletedBlocks)
504
+ this.DEBUG &&
505
+ this._debug(
506
+ `delete stale canonical block number=${block.header.number} hash=${bytesToHex(block.hash())}`,
507
+ )
508
+ this._deletedBlocks = []
509
+ }
510
+ }
511
+
512
+ /**
513
+ * Validates a block header, throwing if invalid. It is being validated against the reported `parentHash`.
514
+ * It verifies the current block against the `parentHash`:
515
+ * - The `parentHash` is part of the blockchain (it is a valid header)
516
+ * - Current block number is parent block number + 1
517
+ * - Current block has a strictly higher timestamp
518
+ * - Additional PoW checks ->
519
+ * - Current block has valid difficulty and gas limit
520
+ * - In case that the header is an uncle header, it should not be too old or young in the chain.
521
+ * - Additional PoA clique checks ->
522
+ * - Checks on coinbase and mixHash
523
+ * - Current block has a timestamp diff greater or equal to PERIOD
524
+ * - Current block has difficulty correctly marked as INTURN or NOTURN
525
+ * @param header - header to be validated
526
+ * @param height - If this is an uncle header, this is the height of the block that is including it
527
+ */
528
+ public async validateHeader(header: BlockHeader, height?: bigint) {
529
+ if (header.isGenesis()) {
530
+ return
531
+ }
532
+ const parentHeader = await this._getHeader(header.parentHash)
533
+
534
+ const { number } = header
535
+ if (number !== parentHeader.number + BIGINT_1) {
536
+ throw EthereumJSErrorWithoutCode(`invalid number ${header.errorStr()}`)
537
+ }
538
+
539
+ if (header.timestamp <= parentHeader.timestamp) {
540
+ throw EthereumJSErrorWithoutCode(`invalid timestamp ${header.errorStr()}`)
541
+ }
542
+
543
+ if (!(header.common.consensusType() === 'pos')) await this.consensus?.validateDifficulty(header)
544
+
545
+ if (this.common.consensusAlgorithm() === ConsensusAlgorithm.Clique) {
546
+ const period = (this.common.consensusConfig() as CliqueConfig).period
547
+ // Timestamp diff between blocks is lower than PERIOD (clique)
548
+ if (parentHeader.timestamp + BigInt(period) > header.timestamp) {
549
+ throw EthereumJSErrorWithoutCode(
550
+ `invalid timestamp diff (lower than period) ${header.errorStr()}`,
551
+ )
552
+ }
553
+ }
554
+
555
+ header.validateGasLimit(parentHeader)
556
+
557
+ if (height !== undefined) {
558
+ const dif = height - parentHeader.number
559
+
560
+ if (!(dif < BIGINT_8 && dif > BIGINT_1)) {
561
+ throw EthereumJSErrorWithoutCode(
562
+ `uncle block has a parent that is too old or too young ${header.errorStr()}`,
563
+ )
564
+ }
565
+ }
566
+
567
+ // check blockchain dependent EIP1559 values
568
+ if (header.common.isActivatedEIP(1559)) {
569
+ // check if the base fee is correct
570
+ let expectedBaseFee
571
+ const londonHfBlock = this.common.hardforkBlock(Hardfork.London)
572
+ const isInitialEIP1559Block = number === londonHfBlock
573
+ if (isInitialEIP1559Block) {
574
+ expectedBaseFee = header.common.param('initialBaseFee')
575
+ } else {
576
+ expectedBaseFee = parentHeader.calcNextBaseFee()
577
+ }
578
+
579
+ if (header.baseFeePerGas! !== expectedBaseFee) {
580
+ throw EthereumJSErrorWithoutCode(`Invalid block: base fee not correct ${header.errorStr()}`)
581
+ }
582
+ }
583
+
584
+ if (header.common.isActivatedEIP(4844)) {
585
+ const expectedExcessBlobGas = parentHeader.calcNextExcessBlobGas(header.common)
586
+ if (header.excessBlobGas !== expectedExcessBlobGas) {
587
+ throw EthereumJSErrorWithoutCode(
588
+ `expected blob gas: ${expectedExcessBlobGas}, got: ${header.excessBlobGas}`,
589
+ )
590
+ }
591
+ }
592
+
593
+ if (header.common.isActivatedEIP(7685)) {
594
+ if (header.requestsHash === undefined) {
595
+ throw EthereumJSErrorWithoutCode(`requestsHash must be provided when EIP-7685 is active`)
596
+ }
597
+ }
598
+ }
599
+
600
+ /**
601
+ * Validates a block, by validating the header against the current chain, any uncle headers, and then
602
+ * whether the block is internally consistent
603
+ * @param block block to be validated
604
+ */
605
+ public async validateBlock(block: Block) {
606
+ await this.validateHeader(block.header)
607
+ await this._validateUncleHeaders(block)
608
+ await block.validateData(false)
609
+ // TODO: Rethink how validateHeader vs validateBlobTransactions works since the parentHeader is retrieved multiple times
610
+ // (one for each uncle header and then for validateBlobTxs).
611
+ const parentBlock = await this.getBlock(block.header.parentHash)
612
+ block.validateBlobTransactions(parentBlock.header)
613
+ }
614
+ /**
615
+ * The following rules are checked in this method:
616
+ * Uncle Header is a valid header.
617
+ * Uncle Header is an orphan, i.e. it is not one of the headers of the canonical chain.
618
+ * Uncle Header has a parentHash which points to the canonical chain. This parentHash is within the last 7 blocks.
619
+ * Uncle Header is not already included as uncle in another block.
620
+ * @param block - block for which uncles are being validated
621
+ */
622
+ private async _validateUncleHeaders(block: Block) {
623
+ const uncleHeaders = block.uncleHeaders
624
+ if (uncleHeaders.length === 0) {
625
+ return
626
+ }
627
+
628
+ // Each Uncle Header is a valid header
629
+ await Promise.all(uncleHeaders.map((uh) => this.validateHeader(uh, block.header.number)))
630
+
631
+ // Check how many blocks we should get in order to validate the uncle.
632
+ // In the worst case, we get 8 blocks, in the best case, we only get 1 block.
633
+ const canonicalBlockMap: Block[] = []
634
+ let lowestUncleNumber = block.header.number
635
+
636
+ uncleHeaders.map((header) => {
637
+ if (header.number < lowestUncleNumber) {
638
+ lowestUncleNumber = header.number
639
+ }
640
+ })
641
+
642
+ // Helper variable: set hash to `true` if hash is part of the canonical chain
643
+ const canonicalChainHashes: { [key: string]: boolean } = {}
644
+
645
+ // Helper variable: set hash to `true` if uncle hash is included in any canonical block
646
+ const includedUncles: { [key: string]: boolean } = {}
647
+
648
+ // Due to the header validation check above, we know that `getBlocks` is between 1 and 8 inclusive.
649
+ const getBlocks = Number(block.header.number - lowestUncleNumber + BIGINT_1)
650
+
651
+ // See Geth: https://github.com/ethereum/go-ethereum/blob/b63bffe8202d46ea10ac8c4f441c582642193ac8/consensus/ethash/consensus.go#L207
652
+ // Here we get the necessary blocks from the chain.
653
+ let parentHash = block.header.parentHash
654
+ for (let i = 0; i < getBlocks; i++) {
655
+ const parentBlock = await this.getBlock(parentHash)
656
+ canonicalBlockMap.push(parentBlock)
657
+
658
+ // mark block hash as part of the canonical chain
659
+ // Using deprecated bytesToUnprefixedHex for performance: used as object keys for hash lookups.
660
+ canonicalChainHashes[bytesToUnprefixedHex(parentBlock.hash())] = true
661
+
662
+ // for each of the uncles, mark the uncle as included
663
+ parentBlock.uncleHeaders.map((uh) => {
664
+ includedUncles[bytesToUnprefixedHex(uh.hash())] = true
665
+ })
666
+
667
+ parentHash = parentBlock.header.parentHash
668
+ }
669
+
670
+ // Here we check:
671
+ // Uncle Header is an orphan, i.e. it is not one of the headers of the canonical chain.
672
+ // Uncle Header is not already included as uncle in another block.
673
+ // Uncle Header has a parentHash which points to the canonical chain.
674
+
675
+ uncleHeaders.map((uh) => {
676
+ // Using deprecated bytesToUnprefixedHex for performance: used as object keys for hash lookups.
677
+ const uncleHash = bytesToUnprefixedHex(uh.hash())
678
+ const parentHash = bytesToUnprefixedHex(uh.parentHash)
679
+
680
+ if (!canonicalChainHashes[parentHash]) {
681
+ throw EthereumJSErrorWithoutCode(
682
+ `The parent hash of the uncle header is not part of the canonical chain ${block.errorStr()}`,
683
+ )
684
+ }
685
+
686
+ if (includedUncles[uncleHash]) {
687
+ throw EthereumJSErrorWithoutCode(
688
+ `The uncle is already included in the canonical chain ${block.errorStr()}`,
689
+ )
690
+ }
691
+
692
+ if (canonicalChainHashes[uncleHash]) {
693
+ throw EthereumJSErrorWithoutCode(`The uncle is a canonical block ${block.errorStr()}`)
694
+ }
695
+ })
696
+ }
697
+
698
+ /**
699
+ * Gets a block by its hash or number. If a number is provided, the returned
700
+ * block will be the canonical block at that number in the chain
701
+ *
702
+ * @param blockId - The block's hash or number. If a hash is provided, then
703
+ * this will be immediately looked up, otherwise it will wait until we have
704
+ * unlocked the DB
705
+ */
706
+ async getBlock(blockId: Uint8Array | number | bigint): Promise<Block> {
707
+ // cannot wait for a lock here: it is used both in `validate` of `Block`
708
+ // (calls `getBlock` to get `parentHash`) it is also called from `runBlock`
709
+ // in the `VM` if we encounter a `BLOCKHASH` opcode: then a bigint is used we
710
+ // need to then read the block from the canonical chain Q: is this safe? We
711
+ // know it is OK if we call it from the iterator... (runBlock)
712
+ const block = await this.dbManager.getBlock(blockId)
713
+
714
+ if (block === undefined) {
715
+ if (typeof blockId === 'object') {
716
+ throw EthereumJSErrorWithoutCode(`Block with hash ${bytesToHex(blockId)} not found in DB`)
717
+ } else {
718
+ throw EthereumJSErrorWithoutCode(`Block number ${blockId} not found in DB`)
719
+ }
720
+ }
721
+ return block
722
+ }
723
+
724
+ /**
725
+ * Gets total difficulty for a block specified by hash and number
726
+ */
727
+ public async getTotalDifficulty(hash: Uint8Array, number?: bigint): Promise<bigint> {
728
+ if (number === undefined) {
729
+ number = await this.dbManager.hashToNumber(hash)
730
+ if (number === undefined) {
731
+ throw EthereumJSErrorWithoutCode(`Block with hash ${bytesToHex(hash)} not found in DB`)
732
+ }
733
+ }
734
+ return this.dbManager.getTotalDifficulty(hash, number)
735
+ }
736
+
737
+ /**
738
+ * Gets total difficulty for a header's parent, helpful for determining terminal block
739
+ * @param header - Block header whose parent td is desired
740
+ */
741
+ public async getParentTD(header: BlockHeader): Promise<bigint> {
742
+ return header.number === BIGINT_0
743
+ ? header.difficulty
744
+ : this.getTotalDifficulty(header.parentHash, header.number - BIGINT_1)
745
+ }
746
+
747
+ /**
748
+ * Looks up many blocks relative to blockId Note: due to `GetBlockHeaders
749
+ * (0x03)` (ETH wire protocol) we have to support skip/reverse as well.
750
+ * @param blockId - The block's hash or number
751
+ * @param maxBlocks - Max number of blocks to return
752
+ * @param skip - Number of blocks to skip apart
753
+ * @param reverse - Fetch blocks in reverse
754
+ */
755
+ async getBlocks(
756
+ blockId: Uint8Array | bigint | number,
757
+ maxBlocks: number,
758
+ skip: number,
759
+ reverse: boolean,
760
+ ): Promise<Block[]> {
761
+ return this.runWithLock<Block[]>(async () => {
762
+ const blocks: Block[] = []
763
+ let i = -1
764
+
765
+ const nextBlock = async (blockId: Uint8Array | bigint | number): Promise<any> => {
766
+ let block
767
+ try {
768
+ block = await this.getBlock(blockId)
769
+ } catch (err: any) {
770
+ if (err.message.includes('not found in DB') === true) {
771
+ return
772
+ } else throw err
773
+ }
774
+
775
+ i++
776
+ const nextBlockNumber = block.header.number + BigInt(reverse ? -1 : 1)
777
+ if (i !== 0 && skip && i % (skip + 1) !== 0) {
778
+ return nextBlock(nextBlockNumber)
779
+ }
780
+ blocks.push(block)
781
+ if (blocks.length < maxBlocks) {
782
+ await nextBlock(nextBlockNumber)
783
+ }
784
+ }
785
+
786
+ await nextBlock(blockId)
787
+ return blocks
788
+ })
789
+ }
790
+
791
+ /**
792
+ * Given an ordered array, returns an array of hashes that are not in the
793
+ * blockchain yet. Uses binary search to find out what hashes are missing.
794
+ * Therefore, the array needs to be ordered upon number.
795
+ * @param hashes - Ordered array of hashes (ordered on `number`).
796
+ */
797
+ async selectNeededHashes(hashes: Array<Uint8Array>): Promise<Uint8Array[]> {
798
+ return this.runWithLock<Uint8Array[]>(async () => {
799
+ let max: number
800
+ let mid: number
801
+ let min: number
802
+
803
+ max = hashes.length - 1
804
+ mid = min = 0
805
+
806
+ while (max >= min) {
807
+ let number
808
+ try {
809
+ number = await this.dbManager.hashToNumber(hashes[mid])
810
+ } catch (err: any) {
811
+ if (err.message.includes('not found in DB') === true) {
812
+ number = undefined
813
+ } else throw err
814
+ }
815
+
816
+ if (number !== undefined) {
817
+ min = mid + 1
818
+ } else {
819
+ max = mid - 1
820
+ }
821
+ mid = Math.floor((min + max) / 2)
822
+ }
823
+ return hashes.slice(min)
824
+ })
825
+ }
826
+
827
+ /**
828
+ * Completely deletes a block from the blockchain including any references to
829
+ * this block. If this block was in the canonical chain, then also each child
830
+ * block of this block is deleted Also, if this was a canonical block, each
831
+ * head header which is part of this now stale chain will be set to the
832
+ * parentHeader of this block An example reason to execute is when running the
833
+ * block in the VM invalidates this block: this will then reset the canonical
834
+ * head to the past block (which has been validated in the past by the VM, so
835
+ * we can be sure it is correct).
836
+ * @param blockHash - The hash of the block to be deleted
837
+ */
838
+ async delBlock(blockHash: Uint8Array) {
839
+ // Q: is it safe to make this not wait for a lock? this is called from
840
+ // `BlockchainTestsRunner` in case `runBlock` throws (i.e. the block is invalid).
841
+ // But is this the way to go? If we know this is called from the
842
+ // iterator we are safe, but if this is called from anywhere
843
+ // else then this might lead to a concurrency problem?
844
+ await this._delBlock(blockHash)
845
+ }
846
+
847
+ /**
848
+ * @hidden
849
+ */
850
+ private async _delBlock(blockHash: Uint8Array) {
851
+ const dbOps: DBOp[] = []
852
+
853
+ // get header
854
+ const header = await this._getHeader(blockHash)
855
+ const blockHeader = header
856
+ const blockNumber = blockHeader.number
857
+ const parentHash = blockHeader.parentHash
858
+
859
+ // check if block is in the canonical chain
860
+ const canonicalHash = await this.safeNumberToHash(blockNumber)
861
+
862
+ const inCanonical = canonicalHash !== false && equalsBytes(canonicalHash, blockHash)
863
+
864
+ // delete the block, and if block is in the canonical chain, delete all
865
+ // children as well
866
+ await this._delChild(blockHash, blockNumber, inCanonical ? parentHash : null, dbOps)
867
+
868
+ // delete all number to hash mappings for deleted block number and above
869
+ if (inCanonical) {
870
+ await this._deleteCanonicalChainReferences(blockNumber, parentHash, dbOps)
871
+ }
872
+
873
+ await this.dbManager.batch(dbOps)
874
+
875
+ if (this._deletedBlocks.length > 0) {
876
+ this.events.emit('deletedCanonicalBlocks', this._deletedBlocks)
877
+ for (const block of this._deletedBlocks)
878
+ this.DEBUG &&
879
+ this._debug(
880
+ `delete stale canonical block number=${block.header.number} hash=${blockHash})}`,
881
+ )
882
+ this._deletedBlocks = []
883
+ }
884
+ }
885
+
886
+ /**
887
+ * Updates the `DatabaseOperation` list to delete a block from the DB,
888
+ * identified by `blockHash` and `blockNumber`. Deletes fields from `Header`,
889
+ * `Body`, `HashToNumber` and `TotalDifficulty` tables. If child blocks of
890
+ * this current block are in the canonical chain, delete these as well. Does
891
+ * not actually commit these changes to the DB. Sets `_headHeaderHash` and
892
+ * `_headBlockHash` to `headHash` if any of these matches the current child to
893
+ * be deleted.
894
+ * @param blockHash - the block hash to delete
895
+ * @param blockNumber - the number corresponding to the block hash
896
+ * @param headHash - the current head of the chain (if null, do not update
897
+ * `_headHeaderHash` and `_headBlockHash`)
898
+ * @param ops - the `DatabaseOperation` list to add the delete operations to
899
+ * @hidden
900
+ */
901
+ private async _delChild(
902
+ blockHash: Uint8Array,
903
+ blockNumber: bigint,
904
+ headHash: Uint8Array | null,
905
+ ops: DBOp[],
906
+ ) {
907
+ // delete header, body, hash to number mapping and td
908
+ ops.push(DBOp.del(DBTarget.Header, { blockHash, blockNumber }))
909
+ ops.push(DBOp.del(DBTarget.Body, { blockHash, blockNumber }))
910
+ ops.push(DBOp.del(DBTarget.HashToNumber, { blockHash }))
911
+ ops.push(DBOp.del(DBTarget.TotalDifficulty, { blockHash, blockNumber }))
912
+
913
+ if (!headHash) {
914
+ return
915
+ }
916
+
917
+ if (
918
+ this._headHeaderHash !== undefined &&
919
+ equalsBytes(this._headHeaderHash, blockHash) === true
920
+ ) {
921
+ this._headHeaderHash = headHash
922
+ }
923
+
924
+ if (this._headBlockHash !== undefined && equalsBytes(this._headBlockHash, blockHash)) {
925
+ this._headBlockHash = headHash
926
+ }
927
+
928
+ try {
929
+ const childHeader = await this.getCanonicalHeader(blockNumber + BIGINT_1)
930
+ await this._delChild(childHeader.hash(), childHeader.number, headHash, ops)
931
+ } catch (err: any) {
932
+ if (err.message.includes('not found in canonical chain') !== true) {
933
+ throw err
934
+ }
935
+ }
936
+ }
937
+
938
+ /**
939
+ * Iterates through blocks starting at the specified iterator head and calls
940
+ * the onBlock function on each block. The current location of an iterator
941
+ * head can be retrieved using {@link Blockchain.getIteratorHead}.
942
+ *
943
+ * @param name - Name of the state root head
944
+ * @param onBlock - Function called on each block with params (block, reorg)
945
+ * @param maxBlocks - How many blocks to run. By default, run all unprocessed blocks in the canonical chain.
946
+ * @param releaseLockOnCallback - Do not lock the blockchain for running the callback (default: `false`)
947
+ * @returns number of blocks actually iterated
948
+ */
949
+ async iterator(
950
+ name: string,
951
+ onBlock: OnBlock,
952
+ maxBlocks?: number,
953
+ releaseLockOnCallback?: boolean,
954
+ ): Promise<number> {
955
+ return this.runWithLock<number>(async (): Promise<number> => {
956
+ let headHash = this._heads[name] ?? this.genesisBlock.hash()
957
+
958
+ if (typeof maxBlocks === 'number' && maxBlocks < 0) {
959
+ throw 'If maxBlocks is provided, it has to be a non-negative number'
960
+ }
961
+
962
+ let headBlockNumber = await this.dbManager.hashToNumber(headHash)
963
+ // `headBlockNumber` should always exist since it defaults to the genesis block
964
+ let nextBlockNumber = headBlockNumber! + BIGINT_1
965
+ let blocksRanCounter = 0
966
+ let lastBlock: Block | undefined
967
+
968
+ try {
969
+ while (maxBlocks !== blocksRanCounter) {
970
+ try {
971
+ let nextBlock = await this.getBlock(nextBlockNumber)
972
+ const reorg = lastBlock
973
+ ? !equalsBytes(lastBlock.hash(), nextBlock.header.parentHash)
974
+ : false
975
+ if (reorg) {
976
+ // If reorg has happened, the _heads must have been updated so lets reload the counters
977
+ headHash = this._heads[name] ?? this.genesisBlock.hash()
978
+ headBlockNumber = await this.dbManager.hashToNumber(headHash)
979
+ nextBlockNumber = headBlockNumber! + BIGINT_1
980
+ nextBlock = await this.getBlock(nextBlockNumber)
981
+ }
982
+
983
+ // While running onBlock with released lock, reorgs can happen via putBlocks
984
+ let reorgWhileOnBlock = false
985
+ if (releaseLockOnCallback === true) {
986
+ this._lock.release()
987
+ }
988
+ try {
989
+ await onBlock(nextBlock, reorg)
990
+ } finally {
991
+ if (releaseLockOnCallback === true) {
992
+ await this._lock.acquire()
993
+ // If lock was released check if reorg occurred
994
+ const nextBlockMayBeReorged = await this.getBlock(nextBlockNumber).catch(
995
+ (_e) => null,
996
+ )
997
+ reorgWhileOnBlock = nextBlockMayBeReorged
998
+ ? !equalsBytes(nextBlockMayBeReorged.hash(), nextBlock.hash())
999
+ : true
1000
+ }
1001
+ }
1002
+
1003
+ // if there was no reorg, update head
1004
+ if (!reorgWhileOnBlock) {
1005
+ this._heads[name] = nextBlock.hash()
1006
+ lastBlock = nextBlock
1007
+ nextBlockNumber++
1008
+ }
1009
+ // Successful execution of onBlock, move the head pointer
1010
+ blocksRanCounter++
1011
+ } catch (error: any) {
1012
+ if ((error.message as string).includes('not found in DB')) {
1013
+ break
1014
+ } else {
1015
+ throw error
1016
+ }
1017
+ }
1018
+ }
1019
+ return blocksRanCounter
1020
+ } finally {
1021
+ await this._saveHeads()
1022
+ }
1023
+ })
1024
+ }
1025
+
1026
+ /**
1027
+ * Set header hash of a certain `tag`.
1028
+ * When calling the iterator, the iterator will start running the first child block after the header hash currently stored.
1029
+ * @param tag - The tag to save the headHash to
1030
+ * @param headHash - The head hash to save
1031
+ */
1032
+ async setIteratorHead(tag: string, headHash: Uint8Array) {
1033
+ await this.runWithLock<void>(async () => {
1034
+ this._heads[tag] = headHash
1035
+ await this._saveHeads()
1036
+ })
1037
+ }
1038
+
1039
+ /* Methods regarding reorg operations */
1040
+
1041
+ /**
1042
+ * Find the common ancestor of the new block and the old block.
1043
+ * @param newHeader - the new block header
1044
+ */
1045
+ private async findCommonAncestor(newHeader: BlockHeader) {
1046
+ if (!this._headHeaderHash) throw EthereumJSErrorWithoutCode('No head header set')
1047
+ const ancestorHeaders = new Set<BlockHeader>()
1048
+
1049
+ let header = await this._getHeader(this._headHeaderHash)
1050
+ if (header.number > newHeader.number) {
1051
+ header = await this.getCanonicalHeader(newHeader.number)
1052
+ ancestorHeaders.add(header)
1053
+ } else {
1054
+ while (header.number !== newHeader.number && newHeader.number > BIGINT_0) {
1055
+ newHeader = await this._getHeader(newHeader.parentHash, newHeader.number - BIGINT_1)
1056
+ ancestorHeaders.add(newHeader)
1057
+ }
1058
+ }
1059
+ if (header.number !== newHeader.number) {
1060
+ throw EthereumJSErrorWithoutCode('Failed to find ancient header')
1061
+ }
1062
+ while (!equalsBytes(header.hash(), newHeader.hash()) && header.number > BIGINT_0) {
1063
+ header = await this.getCanonicalHeader(header.number - BIGINT_1)
1064
+ ancestorHeaders.add(header)
1065
+ newHeader = await this._getHeader(newHeader.parentHash, newHeader.number - BIGINT_1)
1066
+ ancestorHeaders.add(newHeader)
1067
+ }
1068
+ if (!equalsBytes(header.hash(), newHeader.hash())) {
1069
+ throw EthereumJSErrorWithoutCode('Failed to find ancient header')
1070
+ }
1071
+
1072
+ this.DEBUG && this._debug(`found common ancestor with hash=${bytesToHex(header.hash())}`)
1073
+ this.DEBUG && this._debug(`total ancestor headers num=${ancestorHeaders.size}`)
1074
+ return {
1075
+ commonAncestor: header,
1076
+ ancestorHeaders: Array.from(ancestorHeaders),
1077
+ }
1078
+ }
1079
+
1080
+ /**
1081
+ * Pushes DB operations to delete canonical number assignments for specified
1082
+ * block number and above. This only deletes `NumberToHash` references and not
1083
+ * the blocks themselves. Note: this does not write to the DB but only pushes
1084
+ * to a DB operations list.
1085
+ * @param blockNumber - the block number from which we start deleting
1086
+ * canonical chain assignments (including this block)
1087
+ * @param headHash - the hash of the current canonical chain head. The _heads
1088
+ * reference matching any hash of any of the deleted blocks will be set to
1089
+ * this
1090
+ * @param ops - the DatabaseOperation list to write DatabaseOperations to
1091
+ * @hidden
1092
+ */
1093
+ private async _deleteCanonicalChainReferences(
1094
+ blockNumber: bigint,
1095
+ headHash: Uint8Array,
1096
+ ops: DBOp[],
1097
+ ) {
1098
+ try {
1099
+ let hash: Uint8Array | false
1100
+
1101
+ hash = await this.safeNumberToHash(blockNumber)
1102
+ while (hash !== false) {
1103
+ ops.push(DBOp.del(DBTarget.NumberToHash, { blockNumber }))
1104
+
1105
+ if (this.events.listenerCount('deletedCanonicalBlocks') > 0) {
1106
+ const block = await this.getBlock(blockNumber)
1107
+ this._deletedBlocks.push(block)
1108
+ }
1109
+
1110
+ // reset stale iterator heads to current canonical head this can, for
1111
+ // instance, make the VM run "older" (i.e. lower number blocks than last
1112
+ // executed block) blocks to verify the chain up to the current, actual,
1113
+ // head.
1114
+ for (const name of Object.keys(this._heads)) {
1115
+ if (equalsBytes(this._heads[name], hash)) {
1116
+ this._heads[name] = headHash
1117
+ }
1118
+ }
1119
+
1120
+ // reset stale headHeader to current canonical
1121
+ if (
1122
+ this._headHeaderHash !== undefined &&
1123
+ equalsBytes(this._headHeaderHash, hash) === true
1124
+ ) {
1125
+ this._headHeaderHash = headHash
1126
+ }
1127
+ // reset stale headBlock to current canonical
1128
+ if (this._headBlockHash !== undefined && equalsBytes(this._headBlockHash, hash) === true) {
1129
+ this._headBlockHash = headHash
1130
+ }
1131
+
1132
+ blockNumber++
1133
+
1134
+ hash = await this.safeNumberToHash(blockNumber)
1135
+ }
1136
+
1137
+ this.DEBUG &&
1138
+ this._deletedBlocks.length > 0 &&
1139
+ this._debug(`deleted ${this._deletedBlocks.length} stale canonical blocks in total`)
1140
+ } catch (e) {
1141
+ // Ensure that if this method throws, `_deletedBlocks` is reset to the empty array
1142
+ this._deletedBlocks = []
1143
+ throw e
1144
+ }
1145
+ }
1146
+
1147
+ /**
1148
+ * Given a `header`, put all operations to change the canonical chain directly
1149
+ * into `ops`. This walks the supplied `header` backwards. It is thus assumed
1150
+ * that this header should be canonical header. For each header the
1151
+ * corresponding hash corresponding to the current canonical chain in the DB
1152
+ * is checked. If the number => hash reference does not correspond to the
1153
+ * reference in the DB, we overwrite this reference with the implied number =>
1154
+ * hash reference Also, each `_heads` member is checked; if these point to a
1155
+ * stale hash, then the hash which we terminate the loop (i.e. the first hash
1156
+ * which matches the number => hash of the implied chain) is put as this stale
1157
+ * head hash. The same happens to _headBlockHash.
1158
+ * @param header - The canonical header.
1159
+ * @param ops - The database operations list.
1160
+ * @hidden
1161
+ */
1162
+ private async _rebuildCanonical(header: BlockHeader, ops: DBOp[]) {
1163
+ let currentNumber = header.number
1164
+ let currentCanonicalHash: Uint8Array = header.hash()
1165
+
1166
+ // track the staleHash: this is the hash currently in the DB which matches
1167
+ // the block number of the provided header.
1168
+ let staleHash: Uint8Array | false = false
1169
+ let staleHeads: string[] = []
1170
+ let staleHeadBlock = false
1171
+
1172
+ const loopCondition = async () => {
1173
+ staleHash = await this.safeNumberToHash(currentNumber)
1174
+ currentCanonicalHash = header.hash()
1175
+ return staleHash === false || !equalsBytes(currentCanonicalHash, staleHash)
1176
+ }
1177
+
1178
+ while (await loopCondition()) {
1179
+ // handle genesis block
1180
+ const blockHash = header.hash()
1181
+ const blockNumber = header.number
1182
+
1183
+ if (blockNumber === BIGINT_0) {
1184
+ break
1185
+ }
1186
+
1187
+ DBSaveLookups(blockHash, blockNumber).map((op) => {
1188
+ ops.push(op)
1189
+ })
1190
+
1191
+ // mark each key `_heads` which is currently set to the hash in the DB as
1192
+ // stale to overwrite later in `_deleteCanonicalChainReferences`.
1193
+ for (const name of Object.keys(this._heads)) {
1194
+ if (staleHash && equalsBytes(this._heads[name], staleHash)) {
1195
+ staleHeads.push(name)
1196
+ }
1197
+ }
1198
+ // flag stale headBlock for reset
1199
+ if (
1200
+ staleHash &&
1201
+ this._headBlockHash !== undefined &&
1202
+ equalsBytes(this._headBlockHash, staleHash) === true
1203
+ ) {
1204
+ staleHeadBlock = true
1205
+ }
1206
+
1207
+ header = await this._getHeader(header.parentHash, --currentNumber)
1208
+ if (header === undefined) {
1209
+ staleHeads = []
1210
+ break
1211
+ }
1212
+ }
1213
+ // When the stale hash is equal to the blockHash of the provided header,
1214
+ // set stale heads to last previously valid canonical block
1215
+ for (const name of staleHeads) {
1216
+ this._heads[name] = currentCanonicalHash
1217
+ }
1218
+ // set stale headBlock to last previously valid canonical block
1219
+ if (staleHeadBlock) {
1220
+ this._headBlockHash = currentCanonicalHash
1221
+ }
1222
+
1223
+ this.DEBUG && this._debug(`stale heads found num=${staleHeads.length}`)
1224
+ }
1225
+
1226
+ /* Helper functions */
1227
+
1228
+ /**
1229
+ * Builds the `DatabaseOperation[]` list which describes the DB operations to
1230
+ * write the heads, head header hash and the head header block to the DB
1231
+ * @hidden
1232
+ */
1233
+ private _saveHeadOps(): DBOp[] {
1234
+ // Convert DB heads to hex strings for efficient storage in DB
1235
+ // LevelDB doesn't handle Uint8Arrays properly when they are part
1236
+ // of a JSON object being stored as a value in the DB
1237
+ // Using deprecated bytesToUnprefixedHex for performance: used for JSON serialization where unprefixed hex is needed.
1238
+ const hexHeads = Object.fromEntries(
1239
+ Object.entries(this._heads).map((entry) => [entry[0], bytesToUnprefixedHex(entry[1])]),
1240
+ )
1241
+ return [
1242
+ DBOp.set(DBTarget.Heads, hexHeads),
1243
+ DBOp.set(DBTarget.HeadHeader, this._headHeaderHash!),
1244
+ DBOp.set(DBTarget.HeadBlock, this._headBlockHash!),
1245
+ ]
1246
+ }
1247
+
1248
+ /**
1249
+ * Gets the `DatabaseOperation[]` list to save `_heads`, `_headHeaderHash` and
1250
+ * `_headBlockHash` and writes these to the DB
1251
+ * @hidden
1252
+ */
1253
+ private async _saveHeads() {
1254
+ return this.dbManager.batch(this._saveHeadOps())
1255
+ }
1256
+
1257
+ /**
1258
+ * Gets a header by hash and number. Header can exist outside the canonical
1259
+ * chain
1260
+ *
1261
+ * @hidden
1262
+ */
1263
+ private async _getHeader(hash: Uint8Array, number?: bigint) {
1264
+ if (number === undefined) {
1265
+ number = await this.dbManager.hashToNumber(hash)
1266
+ if (number === undefined)
1267
+ throw EthereumJSErrorWithoutCode(`no header for ${bytesToHex(hash)} found in DB`)
1268
+ }
1269
+ return this.dbManager.getHeader(hash, number)
1270
+ }
1271
+
1272
+ async checkAndTransitionHardForkByNumber(
1273
+ number: BigIntLike,
1274
+ timestamp?: BigIntLike,
1275
+ ): Promise<void> {
1276
+ this.common.setHardforkBy({
1277
+ blockNumber: number,
1278
+ timestamp,
1279
+ })
1280
+
1281
+ this._consensusCheck()
1282
+ await this.consensus?.setup({ blockchain: this })
1283
+ await this.consensus?.genesisInit(this.genesisBlock)
1284
+ }
1285
+
1286
+ /**
1287
+ * Gets a header by number. Header must be in the canonical chain
1288
+ */
1289
+ async getCanonicalHeader(number: bigint) {
1290
+ const hash = await this.dbManager.numberToHash(number)
1291
+ if (hash === undefined) {
1292
+ throw EthereumJSErrorWithoutCode(`header with number ${number} not found in canonical chain`)
1293
+ }
1294
+ return this._getHeader(hash, number)
1295
+ }
1296
+
1297
+ /**
1298
+ * This method either returns a Uint8Array if there exists one in the DB or if it
1299
+ * does not exist then return false If DB throws
1300
+ * any other error, this function throws.
1301
+ * @param number
1302
+ */
1303
+ async safeNumberToHash(number: bigint): Promise<Uint8Array | false> {
1304
+ const hash = await this.dbManager.numberToHash(number)
1305
+ return hash ?? false
1306
+ }
1307
+
1308
+ /**
1309
+ * The genesis {@link Block} for the blockchain.
1310
+ */
1311
+ get genesisBlock(): Block {
1312
+ if (!this._genesisBlock)
1313
+ throw EthereumJSErrorWithoutCode('genesis block not set (init may not be finished)')
1314
+ return this._genesisBlock
1315
+ }
1316
+
1317
+ /**
1318
+ * Creates a genesis {@link Block} for the blockchain with params from {@link Common.genesis}
1319
+ * @param stateRoot The genesis stateRoot
1320
+ */
1321
+ createGenesisBlock(stateRoot: Uint8Array): Block {
1322
+ const common = this.common.copy()
1323
+ common.setHardforkBy({
1324
+ blockNumber: 0,
1325
+ timestamp: common.genesis().timestamp,
1326
+ })
1327
+
1328
+ const header: HeaderData = {
1329
+ ...common.genesis(),
1330
+ number: 0,
1331
+ stateRoot,
1332
+ withdrawalsRoot: common.isActivatedEIP(4895) ? KECCAK256_RLP : undefined,
1333
+ requestsHash: common.isActivatedEIP(7685) ? SHA256_NULL : undefined,
1334
+ }
1335
+ if (common.consensusType() === 'poa') {
1336
+ if (common.genesis().extraData) {
1337
+ // Ensure extra data is populated from genesis data if provided
1338
+ header.extraData = common.genesis().extraData
1339
+ } else {
1340
+ // Add required extraData (32 bytes vanity + 65 bytes filled with zeroes
1341
+ header.extraData = concatBytes(new Uint8Array(32), new Uint8Array(65))
1342
+ }
1343
+ }
1344
+
1345
+ return createBlock(
1346
+ {
1347
+ header,
1348
+ withdrawals: common.isActivatedEIP(4895) ? [] : undefined,
1349
+ },
1350
+ { common },
1351
+ )
1352
+ }
1353
+ }