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: remove genesis state dependancy from blockchain #2844

Merged
merged 10 commits into from
Jul 5, 2023
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
6 changes: 4 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/blockchain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"@ethereumjs/block": "^4.2.2",
"@ethereumjs/common": "^3.1.2",
"@ethereumjs/ethash": "^2.0.5",
"@ethereumjs/genesis": "^0.0.1",
"@ethereumjs/rlp": "^4.0.1",
"@ethereumjs/trie": "^5.0.5",
"@ethereumjs/tx": "^4.1.2",
Expand Down
70 changes: 40 additions & 30 deletions packages/blockchain/src/blockchain.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Block, BlockHeader } from '@ethereumjs/block'
import { Chain, Common, ConsensusAlgorithm, ConsensusType, Hardfork } from '@ethereumjs/common'
import { getGenesis } from '@ethereumjs/genesis'
import { genesisStateRoot } from '@ethereumjs/trie'
import {
Chain,
ChainGenesis,
Common,
ConsensusAlgorithm,
ConsensusType,
Hardfork,
} from '@ethereumjs/common'
import { genesisStateRoot as genGenesisStateRoot } from '@ethereumjs/trie'
import {
KECCAK256_RLP,
Lock,
Expand All @@ -10,7 +16,6 @@ import {
bytesToUnprefixedHex,
concatBytes,
equalsBytes,
hexToBytes,
} from '@ethereumjs/util'

import { CasperConsensus, CliqueConsensus, EthashConsensus } from './consensus/index.js'
Expand All @@ -24,7 +29,13 @@ import {
import { DBManager } from './db/manager.js'
import { DBTarget } from './db/operation.js'

import type { BlockchainInterface, BlockchainOptions, Consensus, OnBlock } from './types.js'
import type {
BlockchainInterface,
BlockchainOptions,
Consensus,
GenesisOptions,
OnBlock,
} from './types.js'
import type { BlockData } from '@ethereumjs/block'
import type { CliqueConfig } from '@ethereumjs/common'
import type { BigIntLike, DB, DBObject, GenesisState } from '@ethereumjs/util'
Expand Down Expand Up @@ -75,7 +86,8 @@ export class Blockchain implements BlockchainInterface {

public static async create(opts: BlockchainOptions = {}) {
const blockchain = new Blockchain(opts)
await blockchain._init(opts.genesisBlock)

await blockchain._init(opts)
return blockchain
}

Expand Down Expand Up @@ -196,29 +208,31 @@ export class Blockchain implements BlockchainInterface {
* values from the DB and makes these available to the consumers of
* Blockchain.
*
* @param opts An options object to provide genesisBlock or ways to contruct it
*
* @hidden
*/
private async _init(genesisBlock?: Block): Promise<void> {
private async _init(opts: GenesisOptions = {}): Promise<void> {
await this.consensus.setup({ blockchain: this })

if (this._isInitialized) return

let stateRoot = opts.genesisBlock?.header.stateRoot ?? opts.genesisStateRoot
if (stateRoot === undefined) {
if (this._customGenesisState !== undefined) {
stateRoot = await genGenesisStateRoot(this._customGenesisState)
} else {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
stateRoot = await getGenesisStateRoot(Number(this.common.chainId()) as Chain)
}
}

const genesisBlock = opts.genesisBlock ?? this.createGenesisBlock(stateRoot)

let genesisHash = await this.dbManager.numberToHash(BigInt(0))

const dbGenesisBlock =
genesisHash !== undefined ? await this.dbManager.getBlock(genesisHash) : undefined

if (genesisBlock === undefined) {
let stateRoot
if (this.common.chainId() === BigInt(1) && this._customGenesisState === undefined) {
// For mainnet use the known genesis stateRoot to quicken setup
stateRoot = hexToBytes('0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544')
} else {
stateRoot = await genesisStateRoot(this.genesisState())
}
genesisBlock = this.createGenesisBlock(stateRoot)
}

// 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())) {
Expand Down Expand Up @@ -1338,16 +1352,12 @@ export class Blockchain implements BlockchainInterface {
{ common }
)
}
}

/**
* Returns the genesis state of the blockchain.
* All values are provided as hex-prefixed strings.
*/
genesisState(): GenesisState {
if (this._customGenesisState) {
return this._customGenesisState
}

return getGenesis(Number(this.common.chainId()) as Chain) ?? {}
}
/**
* Returns the genesis state root if chain is well known or an empty state's root otherwise
*/
async function getGenesisStateRoot(chainId: Chain): Promise<Uint8Array> {
const chainGenesis = ChainGenesis[chainId]
return chainGenesis !== undefined ? chainGenesis.stateRoot : genGenesisStateRoot({})
}
83 changes: 42 additions & 41 deletions packages/blockchain/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,57 @@ export interface BlockchainInterface {
getTotalDifficulty?(hash: Uint8Array, number?: bigint): Promise<bigint>

/**
* Returns the genesis state of the blockchain.
* All values are provided as hex-prefixed strings.
* Returns the latest full block in the canonical chain.
*/
genesisState(): GenesisState
getCanonicalHeadBlock(): Promise<Block>
}

export interface GenesisOptions {
/**
* Returns the latest full block in the canonical chain.
* The blockchain only initializes successfully if it has a genesis block. If
* there is no block available in the DB and a `genesisBlock` is provided,
* then the provided `genesisBlock` will be used as genesis. If no block is
* present in the DB and no block is provided, then the genesis block as
* provided from the `common` will be used.
*/
getCanonicalHeadBlock(): Promise<Block>
genesisBlock?: Block

/**
* If you are using a custom chain {@link Common}, pass the genesis state.
*
* Pattern 1 (with genesis state see {@link GenesisState} for format):
*
* ```javascript
* {
* '0x0...01': '0x100', // For EoA
* }
* ```
*
* Pattern 2 (with complex genesis state, containing contract accounts and storage).
* Note that in {@link AccountState} there are two
* accepted types. This allows to easily insert accounts in the genesis state:
*
* A complex genesis state with Contract and EoA states would have the following format:
*
* ```javascript
* {
* '0x0...01': '0x100', // For EoA
* '0x0...02': ['0x1', '0xRUNTIME_BYTECODE', [[storageKey1, storageValue1], [storageKey2, storageValue2]]] // For contracts
* }
* ```
*/
genesisState?: GenesisState

/**
* State root of the genesis state
*/
genesisStateRoot?: Uint8Array
}

/**
* This are the options that the Blockchain constructor can receive.
*/
export interface BlockchainOptions {
export interface BlockchainOptions extends GenesisOptions {
/**
* Specify the chain and hardfork by passing a {@link Common} instance.
*
Expand Down Expand Up @@ -135,41 +171,6 @@ export interface BlockchainOptions {
*/
validateBlocks?: boolean

/**
* The blockchain only initializes successfully if it has a genesis block. If
* there is no block available in the DB and a `genesisBlock` is provided,
* then the provided `genesisBlock` will be used as genesis. If no block is
* present in the DB and no block is provided, then the genesis block as
* provided from the `common` will be used.
*/
genesisBlock?: Block

/**
* If you are using a custom chain {@link Common}, pass the genesis state.
*
* Pattern 1 (with genesis state see {@link GenesisState} for format):
*
* ```javascript
* {
* '0x0...01': '0x100', // For EoA
* }
* ```
*
* Pattern 2 (with complex genesis state, containing contract accounts and storage).
* Note that in {@link AccountState} there are two
* accepted types. This allows to easily insert accounts in the genesis state:
*
* A complex genesis state with Contract and EoA states would have the following format:
*
* ```javascript
* {
* '0x0...01': '0x100', // For EoA
* '0x0...02': ['0x1', '0xRUNTIME_BYTECODE', [[storageKey1, storageValue1], [storageKey2, storageValue2]]] // For contracts
* }
* ```
*/
genesisState?: GenesisState

/**
* Optional custom consensus that implements the {@link Consensus} class
*/
Expand Down
1 change: 1 addition & 0 deletions packages/client/bin/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ async function startClient(config: Config, customGenesisState?: GenesisState) {
const client = await EthereumClient.create({
config,
blockchain,
genesisState: customGenesisState,
...dbs,
})
await client.open()
Expand Down
9 changes: 7 additions & 2 deletions packages/client/src/blockchain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { LevelDB } from '../execution/level'
import { Event } from '../types'

import type { Config } from '../config'
import type { DB, DBObject } from '@ethereumjs/util'
import type { DB, DBObject, GenesisState } from '@ethereumjs/util'
import type { AbstractLevel } from 'abstract-level'

/**
Expand All @@ -28,6 +28,8 @@ export interface ChainOptions {
* Specify a blockchain which implements the Chain interface
*/
blockchain?: Blockchain

genesisState?: GenesisState
}

/**
Expand Down Expand Up @@ -102,6 +104,8 @@ export class Chain {
public config: Config
public chainDB: DB<string | Uint8Array, string | Uint8Array | DBObject>
public blockchain: Blockchain
public _customGenesisState?: GenesisState

public opened: boolean

private _headers: ChainHeaders = {
Expand Down Expand Up @@ -158,6 +162,7 @@ export class Chain {
this.blockchain = options.blockchain!

this.chainDB = this.blockchain.db
this._customGenesisState = options.genesisState
this.opened = false
}

Expand Down Expand Up @@ -216,7 +221,7 @@ export class Chain {
async open(): Promise<boolean | void> {
if (this.opened) return false
await this.blockchain.db.open()
await (this.blockchain as any)._init()
await (this.blockchain as any)._init({ genesisState: this._customGenesisState })
this.opened = true
await this.update(false)

Expand Down
4 changes: 4 additions & 0 deletions packages/client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Event } from './types'
import type { Config } from './config'
import type { MultiaddrLike } from './types'
import type { Blockchain } from '@ethereumjs/blockchain'
import type { GenesisState } from '@ethereumjs/util'
import type { AbstractLevel } from 'abstract-level'

export interface EthereumClientOptions {
Expand Down Expand Up @@ -49,6 +50,9 @@ export interface EthereumClientOptions {

/* How often to discover new peers */
refreshInterval?: number

/* custom genesisState if any for the chain */
genesisState?: GenesisState
}

/**
Expand Down
9 changes: 6 additions & 3 deletions packages/client/src/execution/vmexecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DBSetTD,
} from '@ethereumjs/blockchain'
import { ConsensusType, Hardfork } from '@ethereumjs/common'
import { getGenesis } from '@ethereumjs/genesis'
import { CacheType, DefaultStateManager } from '@ethereumjs/statemanager'
import { Trie } from '@ethereumjs/trie'
import { Lock, bytesToHex, equalsBytes } from '@ethereumjs/util'
Expand Down Expand Up @@ -132,10 +133,12 @@ export class VMExecution extends Execution {
this.hardfork = this.config.execCommon.hardfork()
this.config.logger.info(`Initializing VM execution hardfork=${this.hardfork}`)
if (number === BigInt(0)) {
if (typeof this.vm.blockchain.genesisState !== 'function') {
throw new Error('cannot get iterator head: blockchain has no genesisState function')
const genesisState =
this.chain['_customGenesisState'] ?? getGenesis(Number(this.vm.common.chainId()))
if (!genesisState) {
throw new Error('genesisState not available')
}
await this.vm.stateManager.generateCanonicalGenesis(this.vm.blockchain.genesisState())
await this.vm.stateManager.generateCanonicalGenesis(genesisState)
}
await super.open()
// TODO: Should a run be started to execute any left over blocks?
Expand Down
2 changes: 1 addition & 1 deletion packages/client/test/rpc/eth/estimateGas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ tape(`${method}: call with valid arguments`, async (t) => {
const { execution } = client.services.find((s) => s.name === 'eth') as FullEthereumService
t.notEqual(execution, undefined, 'should have valid execution')
const { vm } = execution
await vm.stateManager.generateCanonicalGenesis(blockchain.genesisState())
await vm.stateManager.generateCanonicalGenesis({})

// genesis address with balance
const address = Address.fromString('0xccfd725760a68823ff1e062f4cc97e1360e8d997')
Expand Down
Loading