@ethereumjs/blockchain
Version:
A module to store and interact with blocks
115 lines (94 loc) • 3.97 kB
text/typescript
import { createBlock } from '@ethereumjs/block'
import { BIGINT_0, EthereumJSErrorWithoutCode, bytesToHex, equalsBytes } from '@ethereumjs/util'
import debugDefault from 'debug'
import {
Blockchain,
DBSaveLookups,
DBSetBlockOrHeader,
DBSetTD,
genGenesisStateRoot,
getGenesisStateRoot,
} from './index.ts'
import type { BlockData } from '@ethereumjs/block'
import type { Chain } from '@ethereumjs/common'
import type { BlockchainOptions, DBOp } from './index.ts'
const DEBUG =
typeof window === 'undefined' ? (process?.env?.DEBUG?.includes('ethjs') ?? false) : false
const debug = debugDefault('blockchain:#')
export async function createBlockchain(opts: BlockchainOptions = {}) {
const blockchain = new Blockchain(opts)
await blockchain.consensus?.setup({ blockchain })
let stateRoot = opts.genesisBlock?.header.stateRoot ?? opts.genesisStateRoot
if (stateRoot === undefined) {
if (blockchain['_customGenesisState'] !== undefined) {
stateRoot = await genGenesisStateRoot(blockchain['_customGenesisState'], blockchain.common)
} else {
stateRoot = await getGenesisStateRoot(
Number(blockchain.common.chainId()) as Chain,
blockchain.common,
)
}
}
const genesisBlock = opts.genesisBlock ?? blockchain.createGenesisBlock(stateRoot)
let genesisHash = await blockchain.dbManager.numberToHash(BIGINT_0)
const dbGenesisBlock =
genesisHash !== undefined ? await blockchain.dbManager.getBlock(genesisHash) : undefined
// If the DB has a genesis block, then verify that the genesis block in the
// DB is indeed the Genesis block generated or assigned.
if (dbGenesisBlock !== undefined && !equalsBytes(genesisBlock.hash(), dbGenesisBlock.hash())) {
throw EthereumJSErrorWithoutCode(
'The genesis block in the DB has a different hash than the provided genesis block.',
)
}
genesisHash = genesisBlock.hash()
if (!dbGenesisBlock) {
// If there is no genesis block put the genesis block in the DB.
// For that TD, the BlockOrHeader, and the Lookups have to be saved.
const dbOps: DBOp[] = []
dbOps.push(DBSetTD(genesisBlock.header.difficulty, BIGINT_0, genesisHash))
DBSetBlockOrHeader(genesisBlock).map((op) => dbOps.push(op))
DBSaveLookups(genesisHash, BIGINT_0).map((op) => dbOps.push(op))
await blockchain.dbManager.batch(dbOps)
await blockchain.consensus?.genesisInit(genesisBlock)
}
// At this point, we can safely set the genesis:
// it is either the one we put in the DB, or it is equal to the one
// which we read from the DB.
blockchain['_genesisBlock'] = genesisBlock
// load verified iterator heads
const heads = await blockchain.dbManager.getHeads()
blockchain['_heads'] = heads ?? {}
// load headerchain head
let hash = await blockchain.dbManager.getHeadHeader()
blockchain['_headHeaderHash'] = hash ?? genesisHash
// load blockchain head
hash = await blockchain.dbManager.getHeadBlock()
blockchain['_headBlockHash'] = hash ?? genesisHash
if (blockchain['_hardforkByHeadBlockNumber']) {
const latestHeader = await blockchain['_getHeader'](blockchain['_headHeaderHash'])
await blockchain.checkAndTransitionHardForkByNumber(latestHeader.number, latestHeader.timestamp)
}
DEBUG && debug(`genesis block initialized with hash ${bytesToHex(genesisHash!)}`)
return blockchain
}
/**
* Creates a blockchain from a list of block objects,
* objects must be readable by {@link createBlock}
*
* @param blockData List of block objects
* @param opts Constructor options, see {@link BlockchainOptions}
*/
export async function createBlockchainFromBlocksData(
blocksData: BlockData[],
opts: BlockchainOptions = {},
) {
const blockchain = await createBlockchain(opts)
for (const blockData of blocksData) {
const block = createBlock(blockData, {
common: blockchain.common,
setHardfork: true,
})
await blockchain.putBlock(block)
}
return blockchain
}