Skip to content

Commit

Permalink
Blockchain: replace static constructors (#3491)
Browse files Browse the repository at this point in the history
* blockchain: move static methods to external functions

* monorepo: switch static Blockchain methods for functions

* fix import

* update in example

* update block constructor

* lint fix

* blockchain: move constructor functions to 'constructors.ts'
  • Loading branch information
ScottyPoi authored Jul 12, 2024
1 parent ef20930 commit 80434b7
Show file tree
Hide file tree
Showing 52 changed files with 336 additions and 312 deletions.
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

0 comments on commit 80434b7

Please sign in to comment.