Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blockchain: replace static constructors #3491

Merged
merged 7 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/blockchain/examples/gethGenesis.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Blockchain } from '@ethereumjs/blockchain'
import { createBlockchain } from '@ethereumjs/blockchain'
import { Common, parseGethGenesis } from '@ethereumjs/common'
import { bytesToHex, parseGethGenesisState } from '@ethereumjs/util'
import gethGenesisJson from './genesisData/post-merge.json'
Expand All @@ -7,7 +7,7 @@ const main = async () => {
// Load geth genesis json file into lets say `gethGenesisJson`
const common = Common.fromGethGenesis(gethGenesisJson, { chain: 'customChain' })
const genesisState = parseGethGenesisState(gethGenesisJson)
const blockchain = await Blockchain.create({
const blockchain = await createBlockchain({
genesisState,
common,
})
Expand Down
4 changes: 2 additions & 2 deletions packages/blockchain/examples/simple.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Block, createBlockFromBlockData } from '@ethereumjs/block'
import { Blockchain } from '@ethereumjs/blockchain'
import { createBlockchain } from '@ethereumjs/blockchain'
import { Common, Hardfork } from '@ethereumjs/common'
import { bytesToHex } from '@ethereumjs/util'

const main = async () => {
const common = new Common({ chain: 'mainnet', hardfork: Hardfork.London })
// Use the safe static constructor which awaits the init method
const blockchain = await Blockchain.create({
const blockchain = await createBlockchain({
validateBlocks: false, // Skipping validation so we can make a simple chain without having to provide complete blocks
validateConsensus: false,
common,
Expand Down
148 changes: 4 additions & 144 deletions packages/blockchain/src/blockchain.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import { Block, BlockHeader, createBlockFromBlockData } from '@ethereumjs/block'
import {
Chain,
ChainGenesis,
Common,
ConsensusAlgorithm,
ConsensusType,
Hardfork,
} from '@ethereumjs/common'
import { genesisStateRoot as genMerkleGenesisStateRoot } from '@ethereumjs/trie'
import { Chain, Common, ConsensusAlgorithm, ConsensusType, Hardfork } from '@ethereumjs/common'
import {
AsyncEventEmitter,
BIGINT_0,
Expand Down Expand Up @@ -40,41 +32,10 @@ import type {
Consensus,
OnBlock,
} from './types.js'
import type { BlockData, HeaderData } from '@ethereumjs/block'
import type { HeaderData } from '@ethereumjs/block'
import type { CliqueConfig } from '@ethereumjs/common'
import type { BigIntLike, DB, DBObject, GenesisState } from '@ethereumjs/util'

/**
* Verkle or Merkle genesis root
* @param genesisState
* @param common
* @returns
*/
async function genGenesisStateRoot(
genesisState: GenesisState,
common: Common
): Promise<Uint8Array> {
const genCommon = common.copy()
genCommon.setHardforkBy({
blockNumber: 0,
td: BigInt(genCommon.genesis().difficulty),
timestamp: genCommon.genesis().timestamp,
})
if (genCommon.isActivatedEIP(6800)) {
throw Error(`Verkle tree state not yet supported`)
} else {
return genMerkleGenesisStateRoot(genesisState)
}
}

/**
* Returns the genesis state root if chain is well known or an empty state's root otherwise
*/
async function getGenesisStateRoot(chainId: Chain, common: Common): Promise<Uint8Array> {
const chainGenesis = ChainGenesis[chainId]
return chainGenesis !== undefined ? chainGenesis.stateRoot : genGenesisStateRoot({}, common)
}

/**
* This class stores and interacts with blocks.
*/
Expand Down Expand Up @@ -119,118 +80,17 @@ export class Blockchain implements BlockchainInterface {
*/
private _deletedBlocks: Block[] = []

/**
* Safe creation of a new Blockchain object awaiting the initialization function,
* encouraged method to use when creating a blockchain object.
*
* @param opts Constructor options, see {@link BlockchainOptions}
*/

public static async create(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 new Error(
'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 !== undefined ? heads : {}

// load headerchain head
let hash = await blockchain.dbManager.getHeadHeader()
blockchain._headHeaderHash = hash !== undefined ? hash : genesisHash

// load blockchain head
hash = await blockchain.dbManager.getHeadBlock()
blockchain._headBlockHash = hash !== undefined ? hash : genesisHash

if (blockchain._hardforkByHeadBlockNumber) {
const latestHeader = await blockchain._getHeader(blockchain._headHeaderHash)
const td = await blockchain.getParentTD(latestHeader)
await blockchain.checkAndTransitionHardForkByNumber(
latestHeader.number,
td,
latestHeader.timestamp
)
}

return blockchain
}

/**
* Creates a blockchain from a list of block objects,
* objects must be readable by {@link createBlockFromBlockData}
*
* @param blockData List of block objects
* @param opts Constructor options, see {@link BlockchainOptions}
*/
public static async fromBlocksData(blocksData: BlockData[], opts: BlockchainOptions = {}) {
const blockchain = await Blockchain.create(opts)
for (const blockData of blocksData) {
const block = createBlockFromBlockData(blockData, {
common: blockchain.common,
setHardfork: true,
})
await blockchain.putBlock(block)
}
return blockchain
}

/**
* Creates new Blockchain object.
*
* @deprecated The direct usage of this constructor is discouraged since
* non-finalized async initialization might lead to side effects. Please
* use the async {@link Blockchain.create} constructor instead (same API).
* use the async {@link createBlockchain} constructor instead (same API).
*
* @param opts An object with the options that this constructor takes. See
* {@link BlockchainOptions}.
*/
protected constructor(opts: BlockchainOptions = {}) {
constructor(opts: BlockchainOptions = {}) {
if (opts.common) {
this.common = opts.common
} else {
Expand Down
112 changes: 112 additions & 0 deletions packages/blockchain/src/constructors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { createBlockFromBlockData } from '@ethereumjs/block'
import { BIGINT_0, equalsBytes } from '@ethereumjs/util'

import {
Blockchain,
DBSaveLookups,
DBSetBlockOrHeader,
DBSetTD,
genGenesisStateRoot,
getGenesisStateRoot,
} from './index.js'

import type { BlockchainOptions, DBOp } from './index.js'
import type { BlockData } from '@ethereumjs/block'
import type { Chain } from '@ethereumjs/common'

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 new Error(
'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 !== undefined ? heads : {}

// load headerchain head
let hash = await blockchain.dbManager.getHeadHeader()
blockchain['_headHeaderHash'] = hash !== undefined ? hash : genesisHash

// load blockchain head
hash = await blockchain.dbManager.getHeadBlock()
blockchain['_headBlockHash'] = hash !== undefined ? hash : genesisHash

if (blockchain['_hardforkByHeadBlockNumber']) {
const latestHeader = await blockchain['_getHeader'](blockchain['_headHeaderHash'])
const td = await blockchain.getParentTD(latestHeader)
await blockchain.checkAndTransitionHardForkByNumber(
latestHeader.number,
td,
latestHeader.timestamp
)
}

return blockchain
}

/**
* Creates a blockchain from a list of block objects,
* objects must be readable by {@link Block.fromBlockData}
*
* @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 = createBlockFromBlockData(blockData, {
common: blockchain.common,
setHardfork: true,
})
await blockchain.putBlock(block)
}
return blockchain
}
43 changes: 43 additions & 0 deletions packages/blockchain/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ChainGenesis } from '@ethereumjs/common'
import { genesisStateRoot as genMerkleGenesisStateRoot } from '@ethereumjs/trie'

import type { Chain, Common } from '@ethereumjs/common'
import type { GenesisState } from '@ethereumjs/util'

/**
* Safe creation of a new Blockchain object awaiting the initialization function,
* encouraged method to use when creating a blockchain object.
*
* @param opts Constructor options, see {@link BlockchainOptions}
*/

/**
* Verkle or Merkle genesis root
* @param genesisState
* @param common
* @returns
*/
export async function genGenesisStateRoot(
genesisState: GenesisState,
common: Common
): Promise<Uint8Array> {
const genCommon = common.copy()
genCommon.setHardforkBy({
blockNumber: 0,
td: BigInt(genCommon.genesis().difficulty),
timestamp: genCommon.genesis().timestamp,
})
if (genCommon.isActivatedEIP(6800)) {
throw Error(`Verkle tree state not yet supported`)
} else {
return genMerkleGenesisStateRoot(genesisState)
}
}

/**
* Returns the genesis state root if chain is well known or an empty state's root otherwise
*/
export async function getGenesisStateRoot(chainId: Chain, common: Common): Promise<Uint8Array> {
const chainGenesis = ChainGenesis[chainId]
return chainGenesis !== undefined ? chainGenesis.stateRoot : genGenesisStateRoot({}, common)
}
2 changes: 2 additions & 0 deletions packages/blockchain/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
export { Blockchain } from './blockchain.js'
export { CasperConsensus, CliqueConsensus, EthashConsensus } from './consensus/index.js'
export * from './constructors.js'
export {
DBOp,
DBSaveLookups,
DBSetBlockOrHeader,
DBSetHashToNumber,
DBSetTD,
} from './db/helpers.js'
export * from './helpers.js'
export * from './types.js'
Loading
Loading