@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,69 @@
1
+ import { ConsensusAlgorithm } from '@feelyourprotocol/common'
2
+ import { EthereumJSErrorWithoutCode, bytesToHex, isDebugEnabled } from '@feelyourprotocol/util'
3
+ import debugDefault from 'debug'
4
+
5
+ import type { Block, BlockHeader } from '@feelyourprotocol/block'
6
+ import type { Debugger } from 'debug'
7
+ import type { Blockchain } from '../index.ts'
8
+ import type { Consensus, ConsensusOptions } from '../types.ts'
9
+
10
+ export type MinimalEthashInterface = {
11
+ cacheDB?: any
12
+ verifyPOW(block: Block): Promise<boolean>
13
+ }
14
+
15
+ /**
16
+ * This class encapsulates Ethash-related consensus functionality when used with the Blockchain class.
17
+ */
18
+ export class EthashConsensus implements Consensus {
19
+ blockchain: Blockchain | undefined
20
+ algorithm: ConsensusAlgorithm
21
+ _ethash: MinimalEthashInterface
22
+
23
+ private DEBUG: boolean // Guard for debug logs
24
+ private _debug: Debugger
25
+
26
+ constructor(ethash: MinimalEthashInterface) {
27
+ this.DEBUG = isDebugEnabled('ethjs')
28
+ this._debug = debugDefault('blockchain:ethash')
29
+
30
+ this.algorithm = ConsensusAlgorithm.Ethash
31
+ this._ethash = ethash
32
+ }
33
+
34
+ async validateConsensus(block: Block): Promise<void> {
35
+ const valid = await this._ethash.verifyPOW(block)
36
+ if (!valid) {
37
+ throw EthereumJSErrorWithoutCode('invalid POW')
38
+ }
39
+ this.DEBUG &&
40
+ this._debug(
41
+ `valid PoW consensus block: number ${block.header.number} hash ${bytesToHex(block.hash())}`,
42
+ )
43
+ }
44
+
45
+ /**
46
+ * Checks that the block's `difficulty` matches the canonical difficulty of the parent header.
47
+ * @param header - header of block to be checked
48
+ */
49
+ async validateDifficulty(header: BlockHeader) {
50
+ if (!this.blockchain) {
51
+ throw EthereumJSErrorWithoutCode('blockchain not provided')
52
+ }
53
+ const parentHeader = await this.blockchain['_getHeader'](header.parentHash)
54
+ if (header.ethashCanonicalDifficulty(parentHeader) !== header.difficulty) {
55
+ throw EthereumJSErrorWithoutCode(`invalid difficulty ${header.errorStr()}`)
56
+ }
57
+ this.DEBUG &&
58
+ this._debug(
59
+ `valid difficulty header: number ${header.number} difficulty ${header.difficulty} parentHash ${bytesToHex(header.parentHash)}`,
60
+ )
61
+ }
62
+
63
+ public async genesisInit(): Promise<void> {}
64
+ public async setup({ blockchain }: ConsensusOptions): Promise<void> {
65
+ this.blockchain = blockchain
66
+ this._ethash.cacheDB = this.blockchain.db
67
+ }
68
+ public async newBlock(): Promise<void> {}
69
+ }
@@ -0,0 +1,5 @@
1
+ import { CasperConsensus } from './casper.ts'
2
+ import { CliqueConsensus } from './clique.ts'
3
+ import { EthashConsensus } from './ethash.ts'
4
+
5
+ export { CasperConsensus, CliqueConsensus, EthashConsensus }
@@ -0,0 +1,119 @@
1
+ import { createBlock } from '@feelyourprotocol/block'
2
+ import {
3
+ BIGINT_0,
4
+ EthereumJSErrorWithoutCode,
5
+ bytesToHex,
6
+ equalsBytes,
7
+ isDebugEnabled,
8
+ } from '@feelyourprotocol/util'
9
+ import debugDefault from 'debug'
10
+
11
+ import {
12
+ Blockchain,
13
+ DBSaveLookups,
14
+ DBSetBlockOrHeader,
15
+ DBSetTD,
16
+ genGenesisStateRoot,
17
+ getGenesisStateRoot,
18
+ } from './index.ts'
19
+
20
+ import type { BlockData } from '@feelyourprotocol/block'
21
+ import type { Chain } from '@feelyourprotocol/common'
22
+ import type { BlockchainOptions, DBOp } from './index.ts'
23
+
24
+ const DEBUG = isDebugEnabled('ethjs')
25
+ const debug = debugDefault('blockchain:#')
26
+
27
+ export async function createBlockchain(opts: BlockchainOptions = {}) {
28
+ const blockchain = new Blockchain(opts)
29
+
30
+ await blockchain.consensus?.setup({ blockchain })
31
+
32
+ let stateRoot = opts.genesisBlock?.header.stateRoot ?? opts.genesisStateRoot
33
+ if (stateRoot === undefined) {
34
+ if (blockchain['_customGenesisState'] !== undefined) {
35
+ stateRoot = await genGenesisStateRoot(blockchain['_customGenesisState'], blockchain.common)
36
+ } else {
37
+ stateRoot = await getGenesisStateRoot(
38
+ Number(blockchain.common.chainId()) as Chain,
39
+ blockchain.common,
40
+ )
41
+ }
42
+ }
43
+
44
+ const genesisBlock = opts.genesisBlock ?? blockchain.createGenesisBlock(stateRoot)
45
+
46
+ let genesisHash = await blockchain.dbManager.numberToHash(BIGINT_0)
47
+
48
+ const dbGenesisBlock =
49
+ genesisHash !== undefined ? await blockchain.dbManager.getBlock(genesisHash) : undefined
50
+
51
+ // If the DB has a genesis block, then verify that the genesis block in the
52
+ // DB is indeed the Genesis block generated or assigned.
53
+ if (dbGenesisBlock !== undefined && !equalsBytes(genesisBlock.hash(), dbGenesisBlock.hash())) {
54
+ throw EthereumJSErrorWithoutCode(
55
+ 'The genesis block in the DB has a different hash than the provided genesis block.',
56
+ )
57
+ }
58
+
59
+ genesisHash = genesisBlock.hash()
60
+
61
+ if (!dbGenesisBlock) {
62
+ // If there is no genesis block put the genesis block in the DB.
63
+ // For that TD, the BlockOrHeader, and the Lookups have to be saved.
64
+ const dbOps: DBOp[] = []
65
+ dbOps.push(DBSetTD(genesisBlock.header.difficulty, BIGINT_0, genesisHash))
66
+ DBSetBlockOrHeader(genesisBlock).map((op) => dbOps.push(op))
67
+ DBSaveLookups(genesisHash, BIGINT_0).map((op) => dbOps.push(op))
68
+ await blockchain.dbManager.batch(dbOps)
69
+ await blockchain.consensus?.genesisInit(genesisBlock)
70
+ }
71
+
72
+ // At this point, we can safely set the genesis:
73
+ // it is either the one we put in the DB, or it is equal to the one
74
+ // which we read from the DB.
75
+ blockchain['_genesisBlock'] = genesisBlock
76
+
77
+ // load verified iterator heads
78
+ const heads = await blockchain.dbManager.getHeads()
79
+ blockchain['_heads'] = heads ?? {}
80
+
81
+ // load headerchain head
82
+ let hash = await blockchain.dbManager.getHeadHeader()
83
+ blockchain['_headHeaderHash'] = hash ?? genesisHash
84
+
85
+ // load blockchain head
86
+ hash = await blockchain.dbManager.getHeadBlock()
87
+ blockchain['_headBlockHash'] = hash ?? genesisHash
88
+
89
+ if (blockchain['_hardforkByHeadBlockNumber']) {
90
+ const latestHeader = await blockchain['_getHeader'](blockchain['_headHeaderHash'])
91
+ await blockchain.checkAndTransitionHardForkByNumber(latestHeader.number, latestHeader.timestamp)
92
+ }
93
+
94
+ DEBUG && debug(`genesis block initialized with hash ${bytesToHex(genesisHash!)}`)
95
+
96
+ return blockchain
97
+ }
98
+
99
+ /**
100
+ * Creates a blockchain from a list of block objects,
101
+ * objects must be readable by {@link createBlock}
102
+ *
103
+ * @param blockData List of block objects
104
+ * @param opts Constructor options, see {@link BlockchainOptions}
105
+ */
106
+ export async function createBlockchainFromBlocksData(
107
+ blocksData: BlockData[],
108
+ opts: BlockchainOptions = {},
109
+ ) {
110
+ const blockchain = await createBlockchain(opts)
111
+ for (const blockData of blocksData) {
112
+ const block = createBlock(blockData, {
113
+ common: blockchain.common,
114
+ setHardfork: true,
115
+ })
116
+ await blockchain.putBlock(block)
117
+ }
118
+ return blockchain
119
+ }
@@ -0,0 +1,39 @@
1
+ import { bytesToUnprefixedHex } from '@feelyourprotocol/util'
2
+ import { LRUCache } from 'lru-cache'
3
+
4
+ /**
5
+ * Simple LRU Cache that allows for keys of type Uint8Array
6
+ * @hidden
7
+ */
8
+ export class Cache<V> {
9
+ _cache: LRUCache<string, { value: V }, void>
10
+
11
+ constructor(opts: LRUCache.Options<string, { value: V }, void>) {
12
+ this._cache = new LRUCache(opts)
13
+ }
14
+
15
+ set(key: string | Uint8Array, value: V): void {
16
+ if (key instanceof Uint8Array) {
17
+ // Using deprecated bytesToUnprefixedHex for performance: used as LRU cache keys (string encoding).
18
+ key = bytesToUnprefixedHex(key)
19
+ }
20
+ this._cache.set(key, { value })
21
+ }
22
+
23
+ get(key: string | Uint8Array): V | undefined {
24
+ if (key instanceof Uint8Array) {
25
+ // Using deprecated bytesToUnprefixedHex for performance: used as LRU cache keys (string encoding).
26
+ key = bytesToUnprefixedHex(key)
27
+ }
28
+ const elem = this._cache.get(key)
29
+ return elem !== undefined ? elem.value : undefined
30
+ }
31
+
32
+ del(key: string | Uint8Array): void {
33
+ if (key instanceof Uint8Array) {
34
+ // Using deprecated bytesToUnprefixedHex for performance: used as LRU cache keys (string encoding).
35
+ key = bytesToUnprefixedHex(key)
36
+ }
37
+ this._cache.delete(key)
38
+ }
39
+ }
@@ -0,0 +1,73 @@
1
+ import { bigIntToBytes, concatBytes, utf8ToBytes } from '@feelyourprotocol/util'
2
+
3
+ // Geth compatible DB keys
4
+
5
+ const HEADS_KEY = 'heads'
6
+
7
+ /**
8
+ * Current canonical head for light sync
9
+ */
10
+ const HEAD_HEADER_KEY = 'LastHeader'
11
+
12
+ /**
13
+ * Current canonical head for full sync
14
+ */
15
+ const HEAD_BLOCK_KEY = 'LastBlock'
16
+
17
+ /**
18
+ * headerPrefix + number + hash -> header
19
+ */
20
+ const HEADER_PREFIX = utf8ToBytes('h')
21
+
22
+ /**
23
+ * headerPrefix + number + hash + tdSuffix -> td
24
+ */
25
+ const TD_SUFFIX = utf8ToBytes('t')
26
+
27
+ /**
28
+ * headerPrefix + number + numSuffix -> hash
29
+ */
30
+ const NUM_SUFFIX = utf8ToBytes('n')
31
+
32
+ /**
33
+ * blockHashPrefix + hash -> number
34
+ */
35
+ const BLOCK_HASH_PREFIX = utf8ToBytes('H')
36
+
37
+ /**
38
+ * bodyPrefix + number + hash -> block body
39
+ */
40
+ const BODY_PREFIX = utf8ToBytes('b')
41
+
42
+ // Utility functions
43
+
44
+ /**
45
+ * Convert bigint to big endian Uint8Array
46
+ */
47
+ const bytesBE8 = (n: bigint) => bigIntToBytes(BigInt.asUintN(64, n))
48
+
49
+ const tdKey = (n: bigint, hash: Uint8Array) =>
50
+ concatBytes(HEADER_PREFIX, bytesBE8(n), hash, TD_SUFFIX)
51
+
52
+ const headerKey = (n: bigint, hash: Uint8Array) => concatBytes(HEADER_PREFIX, bytesBE8(n), hash)
53
+
54
+ const bodyKey = (n: bigint, hash: Uint8Array) => concatBytes(BODY_PREFIX, bytesBE8(n), hash)
55
+
56
+ const numberToHashKey = (n: bigint) => concatBytes(HEADER_PREFIX, bytesBE8(n), NUM_SUFFIX)
57
+
58
+ const hashToNumberKey = (hash: Uint8Array) => concatBytes(BLOCK_HASH_PREFIX, hash)
59
+
60
+ /**
61
+ * @hidden
62
+ */
63
+ export {
64
+ bodyKey,
65
+ bytesBE8,
66
+ hashToNumberKey,
67
+ HEAD_BLOCK_KEY,
68
+ HEAD_HEADER_KEY,
69
+ headerKey,
70
+ HEADS_KEY,
71
+ numberToHashKey,
72
+ tdKey,
73
+ }
@@ -0,0 +1,81 @@
1
+ import { Block } from '@feelyourprotocol/block'
2
+ import { RLP } from '@feelyourprotocol/rlp'
3
+ import { BIGINT_0 } from '@feelyourprotocol/util'
4
+
5
+ import { bytesBE8 } from './constants.ts'
6
+ import { DBOp, DBTarget } from './operation.ts'
7
+
8
+ import type { BlockHeader } from '@feelyourprotocol/block'
9
+
10
+ /*
11
+ * This extra helper file serves as an interface between the blockchain API functionality
12
+ * and the DB operations from `db/operation.ts` and also handles the right encoding of the keys
13
+ */
14
+
15
+ function DBSetTD(TD: bigint, blockNumber: bigint, blockHash: Uint8Array): DBOp {
16
+ return DBOp.set(DBTarget.TotalDifficulty, RLP.encode(TD), {
17
+ blockNumber,
18
+ blockHash,
19
+ })
20
+ }
21
+
22
+ /*
23
+ * This method accepts either a BlockHeader or a Block and returns a list of DatabaseOperation instances
24
+ *
25
+ * - A "Set Header Operation" is always added
26
+ * - A "Set Body Operation" is only added if the body is not empty (it has transactions/uncles) or if the block is the genesis block
27
+ * (if there is a header but no block saved the DB will implicitly assume the block to be empty)
28
+ */
29
+ function DBSetBlockOrHeader(blockBody: Block | BlockHeader): DBOp[] {
30
+ const header: BlockHeader = blockBody instanceof Block ? blockBody.header : blockBody
31
+ const dbOps = []
32
+
33
+ const blockNumber = header.number
34
+ const blockHash = header.hash()
35
+
36
+ const headerValue = header.serialize()
37
+ dbOps.push(
38
+ DBOp.set(DBTarget.Header, headerValue, {
39
+ blockNumber,
40
+ blockHash,
41
+ }),
42
+ )
43
+
44
+ const isGenesis = header.number === BIGINT_0
45
+
46
+ if (isGenesis || blockBody instanceof Block) {
47
+ const bodyValue = RLP.encode(blockBody.raw().slice(1))
48
+ dbOps.push(
49
+ DBOp.set(DBTarget.Body, bodyValue, {
50
+ blockNumber,
51
+ blockHash,
52
+ }),
53
+ )
54
+ }
55
+
56
+ return dbOps
57
+ }
58
+
59
+ function DBSetHashToNumber(blockHash: Uint8Array, blockNumber: bigint): DBOp {
60
+ const blockNumber8Byte = bytesBE8(blockNumber)
61
+ return DBOp.set(DBTarget.HashToNumber, blockNumber8Byte, {
62
+ blockHash,
63
+ })
64
+ }
65
+
66
+ function DBSaveLookups(blockHash: Uint8Array, blockNumber: bigint, skipNumIndex?: boolean): DBOp[] {
67
+ const ops = []
68
+ if (skipNumIndex !== true) {
69
+ ops.push(DBOp.set(DBTarget.NumberToHash, blockHash, { blockNumber }))
70
+ }
71
+
72
+ const blockNumber8Bytes = bytesBE8(blockNumber)
73
+ ops.push(
74
+ DBOp.set(DBTarget.HashToNumber, blockNumber8Bytes, {
75
+ blockHash,
76
+ }),
77
+ )
78
+ return ops
79
+ }
80
+
81
+ export { DBOp, DBSaveLookups, DBSetBlockOrHeader, DBSetHashToNumber, DBSetTD }
@@ -0,0 +1,243 @@
1
+ import { createBlockFromBytesArray, createBlockHeaderFromBytesArray } from '@feelyourprotocol/block'
2
+ import { RLP } from '@feelyourprotocol/rlp'
3
+ import {
4
+ EthereumJSErrorWithoutCode,
5
+ KECCAK256_RLP,
6
+ KECCAK256_RLP_ARRAY,
7
+ bytesToBigInt,
8
+ bytesToHex,
9
+ equalsBytes,
10
+ unprefixedHexToBytes,
11
+ } from '@feelyourprotocol/util'
12
+
13
+ import { Cache } from './cache.ts'
14
+ import { DBOp, DBTarget } from './operation.ts'
15
+
16
+ import type { Block, BlockBodyBytes, BlockBytes, BlockOptions } from '@feelyourprotocol/block'
17
+ import type { Common } from '@feelyourprotocol/common'
18
+ import type { BatchDBOp, DB, DBObject, DelBatch, PutBatch } from '@feelyourprotocol/util'
19
+ import type { DatabaseKey } from './operation.ts'
20
+
21
+ /**
22
+ * @hidden
23
+ */
24
+ export interface GetOpts {
25
+ keyEncoding?: string
26
+ valueEncoding?: string
27
+ cache?: string
28
+ }
29
+
30
+ export type CacheMap = { [key: string]: Cache<Uint8Array> }
31
+
32
+ /**
33
+ * Abstraction over a DB to facilitate storing/fetching blockchain-related
34
+ * data, such as blocks and headers, indices, and the head block.
35
+ * @hidden
36
+ */
37
+ export class DBManager {
38
+ private _cache: CacheMap
39
+ public readonly common: Common
40
+ private _db: DB<Uint8Array | string, Uint8Array | string | DBObject>
41
+
42
+ constructor(db: DB<Uint8Array | string, Uint8Array | string | DBObject>, common: Common) {
43
+ this._db = db
44
+ this.common = common
45
+ this._cache = {
46
+ td: new Cache({ max: 1024 }),
47
+ header: new Cache({ max: 512 }),
48
+ body: new Cache({ max: 256 }),
49
+ numberToHash: new Cache({ max: 2048 }),
50
+ hashToNumber: new Cache({ max: 2048 }),
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Fetches iterator heads from the db.
56
+ */
57
+ async getHeads(): Promise<{ [key: string]: Uint8Array }> {
58
+ const heads = (await this.get(DBTarget.Heads)) as DBObject
59
+ if (heads === undefined) return heads
60
+ const decodedHeads: { [key: string]: Uint8Array } = {}
61
+ for (const key of Object.keys(heads)) {
62
+ // Heads are stored in DB as hex strings since Level converts Uint8Arrays
63
+ // to nested JSON objects when they are included in a value being stored
64
+ // in the DB
65
+ decodedHeads[key] = unprefixedHexToBytes(heads[key] as string)
66
+ }
67
+ return decodedHeads
68
+ }
69
+
70
+ /**
71
+ * Fetches header of the head block.
72
+ */
73
+ async getHeadHeader(): Promise<Uint8Array | undefined> {
74
+ return this.get(DBTarget.HeadHeader)
75
+ }
76
+
77
+ /**
78
+ * Fetches head block.
79
+ */
80
+ async getHeadBlock(): Promise<Uint8Array | undefined> {
81
+ return this.get(DBTarget.HeadBlock)
82
+ }
83
+
84
+ /**
85
+ * Fetches a block (header and body) given a block id,
86
+ * which can be either its hash or its number.
87
+ */
88
+ async getBlock(blockId: Uint8Array | bigint | number): Promise<Block | undefined> {
89
+ if (typeof blockId === 'number' && Number.isInteger(blockId)) {
90
+ blockId = BigInt(blockId)
91
+ }
92
+
93
+ let number
94
+ let hash
95
+ if (blockId === undefined) return undefined
96
+ if (blockId instanceof Uint8Array) {
97
+ hash = blockId
98
+ number = await this.hashToNumber(blockId)
99
+ } else if (typeof blockId === 'bigint') {
100
+ number = blockId
101
+ hash = await this.numberToHash(blockId)
102
+ } else {
103
+ throw EthereumJSErrorWithoutCode('Unknown blockId type')
104
+ }
105
+
106
+ if (hash === undefined || number === undefined) return undefined
107
+ const header = await this.getHeader(hash, number)
108
+ let body = await this.getBody(hash, number)
109
+
110
+ // be backward compatible where we didn't use to store a body with no txs, uncles, withdrawals
111
+ // otherwise the body is never partially stored and if we have some body, its in entirety
112
+ if (body === undefined) {
113
+ body = [[], []] as BlockBodyBytes
114
+ // Do extra validations on the header since we are assuming empty transactions and uncles
115
+ if (!equalsBytes(header.transactionsTrie, KECCAK256_RLP)) {
116
+ throw EthereumJSErrorWithoutCode('transactionsTrie root should be equal to hash of null')
117
+ }
118
+
119
+ if (!equalsBytes(header.uncleHash, KECCAK256_RLP_ARRAY)) {
120
+ throw EthereumJSErrorWithoutCode('uncle hash should be equal to hash of empty array')
121
+ }
122
+
123
+ // If this block had empty withdrawals push an empty array in body
124
+ if (header.withdrawalsRoot !== undefined) {
125
+ // Do extra validations for withdrawal before assuming empty withdrawals
126
+ if (!equalsBytes(header.withdrawalsRoot, KECCAK256_RLP)) {
127
+ throw EthereumJSErrorWithoutCode(
128
+ 'withdrawals root shoot be equal to hash of null when no withdrawals',
129
+ )
130
+ } else {
131
+ body.push([])
132
+ }
133
+ }
134
+ }
135
+
136
+ const blockData = [header.raw(), ...body] as BlockBytes
137
+ const opts: BlockOptions = { common: this.common, setHardfork: true }
138
+ return createBlockFromBytesArray(blockData, opts)
139
+ }
140
+
141
+ /**
142
+ * Fetches body of a block given its hash and number.
143
+ */
144
+ async getBody(blockHash: Uint8Array, blockNumber: bigint): Promise<BlockBodyBytes | undefined> {
145
+ const body = await this.get(DBTarget.Body, { blockHash, blockNumber })
146
+ return body !== undefined ? (RLP.decode(body) as BlockBodyBytes) : undefined
147
+ }
148
+
149
+ /**
150
+ * Fetches header of a block given its hash and number.
151
+ */
152
+ async getHeader(blockHash: Uint8Array, blockNumber: bigint) {
153
+ const encodedHeader = await this.get(DBTarget.Header, { blockHash, blockNumber })
154
+ const headerValues = RLP.decode(encodedHeader)
155
+
156
+ const opts: BlockOptions = { common: this.common, setHardfork: true }
157
+ return createBlockHeaderFromBytesArray(headerValues as Uint8Array[], opts)
158
+ }
159
+
160
+ /**
161
+ * Fetches total difficulty for a block given its hash and number.
162
+ */
163
+ async getTotalDifficulty(blockHash: Uint8Array, blockNumber: bigint): Promise<bigint> {
164
+ const td = await this.get(DBTarget.TotalDifficulty, { blockHash, blockNumber })
165
+ return bytesToBigInt(RLP.decode(td) as Uint8Array)
166
+ }
167
+
168
+ /**
169
+ * Performs a block hash to block number lookup.
170
+ */
171
+ async hashToNumber(blockHash: Uint8Array): Promise<bigint | undefined> {
172
+ const value = await this.get(DBTarget.HashToNumber, { blockHash })
173
+ if (value === undefined) {
174
+ throw EthereumJSErrorWithoutCode(`value for ${bytesToHex(blockHash)} not found in DB`)
175
+ }
176
+ return value !== undefined ? bytesToBigInt(value) : undefined
177
+ }
178
+
179
+ /**
180
+ * Performs a block number to block hash lookup.
181
+ */
182
+ async numberToHash(blockNumber: bigint): Promise<Uint8Array | undefined> {
183
+ const value = await this.get(DBTarget.NumberToHash, { blockNumber })
184
+ return value
185
+ }
186
+
187
+ /**
188
+ * Fetches a key from the db. If `opts.cache` is specified
189
+ * it first tries to load from cache, and on cache miss will
190
+ * try to put the fetched item on cache afterwards.
191
+ */
192
+ async get(dbOperationTarget: DBTarget, key?: DatabaseKey): Promise<any> {
193
+ const dbGetOperation = DBOp.get(dbOperationTarget, key)
194
+
195
+ const cacheString = dbGetOperation.cacheString
196
+ const dbKey = dbGetOperation.baseDBOp.key
197
+
198
+ if (cacheString !== undefined) {
199
+ if (this._cache[cacheString] === undefined) {
200
+ throw EthereumJSErrorWithoutCode(`Invalid cache: ${cacheString}`)
201
+ }
202
+ let value = this._cache[cacheString].get(dbKey)
203
+ if (value === undefined) {
204
+ value = (await this._db.get(dbKey, {
205
+ keyEncoding: dbGetOperation.baseDBOp.keyEncoding,
206
+ valueEncoding: dbGetOperation.baseDBOp.valueEncoding,
207
+ })) as Uint8Array | undefined
208
+ if (value !== undefined) {
209
+ this._cache[cacheString].set(dbKey, value)
210
+ }
211
+ }
212
+
213
+ return value
214
+ }
215
+ return this._db.get(dbKey, {
216
+ keyEncoding: dbGetOperation.baseDBOp.keyEncoding,
217
+ valueEncoding: dbGetOperation.baseDBOp.valueEncoding,
218
+ })
219
+ }
220
+
221
+ /**
222
+ * Performs a batch operation on db.
223
+ */
224
+ async batch(ops: DBOp[]) {
225
+ const convertedOps: BatchDBOp[] = ops.map((op) => {
226
+ const type = op.baseDBOp.type ?? (op.baseDBOp.value !== undefined ? 'put' : 'del')
227
+ const convertedOp = {
228
+ key: op.baseDBOp.key,
229
+ value: op.baseDBOp.value,
230
+ type,
231
+ opts: {
232
+ keyEncoding: op.baseDBOp.keyEncoding,
233
+ valueEncoding: op.baseDBOp.valueEncoding,
234
+ },
235
+ }
236
+ if (type === 'put') return convertedOp as PutBatch
237
+ else return convertedOp as DelBatch
238
+ })
239
+ // update the current cache for each operation
240
+ ops.map((op) => op.updateCache(this._cache))
241
+ return this._db.batch(convertedOps)
242
+ }
243
+ }