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

common: Relocate geth genesis, state parsers to common, blockchain respectively #2300

Merged
merged 13 commits into from
Sep 22, 2022
1 change: 1 addition & 0 deletions packages/blockchain/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { Blockchain } from './blockchain'
export { CasperConsensus, CliqueConsensus, Consensus, EthashConsensus } from './consensus'
export { BlockchainInterface, BlockchainOptions } from './types'
export * from './utils'
19 changes: 19 additions & 0 deletions packages/blockchain/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { addHexPrefix, bigIntToHex, isHexPrefixed } from '@ethereumjs/util'

import type { GenesisState } from './genesisStates'
/**
* Parses the geth genesis state into Blockchain {@link GenesisState}
* @param json representing the `alloc` key in a Geth genesis file
*/
export async function parseGethGenesisState(json: any) {
const state: GenesisState = {}
for (let address of Object.keys(json.alloc)) {
let { balance, code, storage } = json.alloc[address]
address = addHexPrefix(address)
balance = isHexPrefixed(balance) ? balance : bigIntToHex(BigInt(balance))
code = code !== undefined ? addHexPrefix(code) : undefined
storage = storage !== undefined ? Object.entries(storage) : undefined
state[address] = [balance, code, storage] as any
}
return state
}
21 changes: 8 additions & 13 deletions packages/client/bin/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env node

import { Blockchain } from '@ethereumjs/blockchain'
import { Chain, Common, ConsensusAlgorithm, Hardfork } from '@ethereumjs/common'
import { Blockchain, parseGethGenesisState } from '@ethereumjs/blockchain'
import { Chain, Common, ConsensusAlgorithm, Hardfork, parseGethGenesis } from '@ethereumjs/common'
import { Address, toBuffer } from '@ethereumjs/util'
import { randomBytes } from 'crypto'
import { existsSync } from 'fs'
Expand All @@ -14,12 +14,7 @@ import * as readline from 'readline'
import { EthereumClient } from '../lib/client'
import { Config, DataDirectory, SyncMode } from '../lib/config'
import { getLogger } from '../lib/logging'
import {
parseCustomParams,
parseGenesisState,
parseMultiaddrs,
setCommonForkHashes,
} from '../lib/util'
import { parseMultiaddrs } from '../lib/util'

import { helprpc, startRPCServers } from './startRpc'

Expand Down Expand Up @@ -389,7 +384,7 @@ async function startClient(config: Config, customGenesisState?: GenesisState) {
validateBlocks: true,
validateConsensus,
})
setCommonForkHashes(config.chainCommon, blockchain.genesisBlock.hash())
config.chainCommon.setForkHashes(blockchain.genesisBlock.hash())
}

const client = new EthereumClient({
Expand Down Expand Up @@ -462,8 +457,8 @@ async function setupDevnet(prefundAddress: Address) {
extraData,
alloc: { [addr]: { balance: '0x10000000000000000000' } },
}
const chainParams = await parseCustomParams(chainData, 'devnet')
const customGenesisState = await parseGenesisState(chainData)
const chainParams = await parseGethGenesis(chainData, 'devnet')
const customGenesisState = await parseGethGenesisState(chainData)
const common = new Common({
chain: 'devnet',
customChains: [chainParams],
Expand Down Expand Up @@ -617,12 +612,12 @@ async function run() {
// Use geth genesis parameters file if specified
const genesisFile = JSON.parse(readFileSync(args.gethGenesis, 'utf-8'))
const chainName = path.parse(args.gethGenesis).base.split('.')[0]
const genesisParams = await parseCustomParams(genesisFile, chainName)
const genesisParams = await parseGethGenesis(genesisFile, chainName)
common = new Common({
chain: genesisParams.name,
customChains: [genesisParams],
})
customGenesisState = await parseGenesisState(genesisFile)
customGenesisState = await parseGethGenesisState(genesisFile)
}

if (args.mine === true && accounts.length === 0) {
Expand Down
172 changes: 0 additions & 172 deletions packages/client/lib/util/parse.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import { Hardfork } from '@ethereumjs/common'
import {
addHexPrefix,
bigIntToHex,
intToHex,
isHexPrefixed,
stripHexPrefix,
} from '@ethereumjs/util'
import { Multiaddr, multiaddr } from 'multiaddr'
import { URL } from 'url'

import type { MultiaddrLike } from '../types'
import type { GenesisState } from '@ethereumjs/blockchain/dist/genesisStates'
import type { Common } from '@ethereumjs/common'

/**
* Parses multiaddrs and bootnodes to multiaddr format.
Expand Down Expand Up @@ -80,151 +70,6 @@ export function parseTransports(transports: string[]) {
})
}

/**
* Transforms Geth formatted nonce (i.e. hex string) to 8 byte 0x-prefixed string used internally
* @param nonce string parsed from the Geth genesis file
* @returns nonce as a 0x-prefixed 8 byte string
*/
function formatNonce(nonce: string): string {
if (!nonce || nonce === '0x0') {
return '0x0000000000000000'
}
if (isHexPrefixed(nonce)) {
return '0x' + stripHexPrefix(nonce).padStart(16, '0')
}
return '0x' + nonce.padStart(16, '0')
}

/**
* Converts Geth genesis parameters to an EthereumJS compatible `CommonOpts` object
* @param json object representing the Geth genesis file
* @returns genesis parameters in a `CommonOpts` compliant object
*/
async function parseGethParams(json: any) {
const { name, config, difficulty, mixHash, gasLimit, coinbase, baseFeePerGas } = json
let { extraData, timestamp, nonce } = json
const { chainId } = config

// geth is not strictly putting empty fields with a 0x prefix
if (extraData === '') {
extraData = '0x'
}
// geth may use number for timestamp
if (!isHexPrefixed(timestamp)) {
timestamp = intToHex(parseInt(timestamp))
}
// geth may not give us a nonce strictly formatted to an 8 byte hex string
if (nonce.length !== 18) {
nonce = formatNonce(nonce)
}

// EIP155 and EIP158 are both part of Spurious Dragon hardfork and must occur at the same time
// but have different configuration parameters in geth genesis parameters
if (config.eip155Block !== config.eip158Block) {
throw new Error(
'EIP155 block number must equal EIP 158 block number since both are part of SpuriousDragon hardfork and the client only supports activating the full hardfork'
)
}

const params: any = {
name,
chainId,
networkId: chainId,
genesis: {
timestamp,
gasLimit: parseInt(gasLimit), // geth gasLimit and difficulty are hex strings while ours are `number`s
difficulty: parseInt(difficulty),
nonce,
extraData,
mixHash,
coinbase,
baseFeePerGas,
},
bootstrapNodes: [],
consensus:
config.clique !== undefined
? {
type: 'poa',
algorithm: 'clique',
clique: {
period: config.clique.period,
epoch: config.clique.epoch,
},
}
: {
type: 'pow',
algorithm: 'ethash',
ethash: {},
},
}

const forkMap: { [key: string]: string } = {
[Hardfork.Homestead]: 'homesteadBlock',
[Hardfork.Dao]: 'daoForkBlock',
[Hardfork.TangerineWhistle]: 'eip150Block',
[Hardfork.SpuriousDragon]: 'eip155Block',
[Hardfork.Byzantium]: 'byzantiumBlock',
[Hardfork.Constantinople]: 'constantinopleBlock',
[Hardfork.Petersburg]: 'petersburgBlock',
[Hardfork.Istanbul]: 'istanbulBlock',
[Hardfork.MuirGlacier]: 'muirGlacierBlock',
[Hardfork.Berlin]: 'berlinBlock',
[Hardfork.London]: 'londonBlock',
[Hardfork.MergeForkIdTransition]: 'mergeForkBlock',
}
params.hardforks = Object.values(Hardfork)
.map((name) => ({
name,
block: name === Hardfork.Chainstart ? 0 : config[forkMap[name]] ?? null,
}))
.filter((fork) => fork.block !== null)
if (config.terminalTotalDifficulty !== undefined) {
params.hardforks.push({
name: Hardfork.Merge,
ttd: config.terminalTotalDifficulty,
block: null,
})
}
return params
}

/**
* Parses a genesis.json exported from Geth into parameters for Common instance
* @param json representing the Geth genesis file
* @param name optional chain name
* @returns parsed params
*/
export async function parseCustomParams(json: any, name?: string) {
try {
if (['config', 'difficulty', 'gasLimit', 'alloc'].some((field) => !(field in json))) {
throw new Error('Invalid format, expected geth genesis fields missing')
}
if (name !== undefined) {
json.name = name
}
return await parseGethParams(json)
} catch (e: any) {
throw new Error(`Error parsing parameters file: ${e.message}`)
}
}

/**
* Parses the geth genesis state into Blockchain {@link GenesisState}
* @param json representing the `alloc` key in a Geth genesis file
*/
export async function parseGenesisState(json: any) {
const state: GenesisState = {}
for (let address of Object.keys(json.alloc)) {
let { balance, code, storage } = json.alloc[address]
address = addHexPrefix(address)
balance = isHexPrefixed(balance) ? balance : bigIntToHex(BigInt(balance))
code = code !== undefined ? addHexPrefix(code) : undefined
storage = storage !== undefined ? Object.entries(storage) : undefined
state[address] = [balance, code, storage] as any
}
return state
}

/**
* Returns Buffer from input hexadecimal string or Buffer
* @param input hexadecimal string or Buffer
Expand All @@ -235,20 +80,3 @@ export function parseKey(input: string | Buffer) {
}
return Buffer.from(input, 'hex')
}

/**
* Sets any missing forkHashes on the passed-in {@link Common} instance
* @param common The {@link Common} to set the forkHashes for
* @param genesisHash The genesis block hash
*/
export function setCommonForkHashes(common: Common, genesisHash: Buffer) {
for (const hf of (common as any)._chainParams.hardforks) {
if (
(hf.forkHash === null || hf.forkhash === undefined) &&
typeof hf.block !== 'undefined' &&
(hf.block !== null || typeof hf.td !== 'undefined')
) {
hf.forkHash = common.forkHash(hf.name, genesisHash)
}
}
}
5 changes: 2 additions & 3 deletions packages/client/test/integration/beaconsync.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { BlockHeader } from '@ethereumjs/block'
import { Common } from '@ethereumjs/common'
import { Common, parseGethGenesis } from '@ethereumjs/common'
import * as tape from 'tape'
import * as td from 'testdouble'

import { Event } from '../../lib/types'
import { parseCustomParams } from '../../lib/util'
import * as genesisJSON from '../testdata/geth-genesis/post-merge.json'

import { destroy, setup, wait } from './util'

const originalValidate = BlockHeader.prototype._consensusFormatValidation

tape('[Integration:BeaconSync]', async (t) => {
const params = await parseCustomParams(genesisJSON, 'post-merge')
const params = await parseGethGenesis(genesisJSON, 'post-merge')
const common = new Common({
chain: params.name,
customChains: [params],
Expand Down
15 changes: 5 additions & 10 deletions packages/client/test/rpc/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BlockHeader } from '@ethereumjs/block'
import { Blockchain } from '@ethereumjs/blockchain'
import { Chain as ChainEnum, Common } from '@ethereumjs/common'
import { Blockchain, parseGethGenesisState } from '@ethereumjs/blockchain'
import { Chain as ChainEnum, Common, parseGethGenesis } from '@ethereumjs/common'
import { Address } from '@ethereumjs/util'
import { Server as RPCServer } from 'jayson/promise'
import { MemoryLevel } from 'memory-level'
Expand All @@ -13,12 +13,7 @@ import { RlpxServer } from '../../lib/net/server/rlpxserver'
import { RPCManager as Manager } from '../../lib/rpc'
import { TxPool } from '../../lib/service/txpool'
import { Event } from '../../lib/types'
import {
createRPCServerListener,
createWsRPCServerListener,
parseCustomParams,
parseGenesisState,
} from '../../lib/util'
import { createRPCServerListener, createWsRPCServerListener } from '../../lib/util'

import { mockBlockchain } from './mockBlockchain'

Expand Down Expand Up @@ -203,8 +198,8 @@ export async function baseRequest(
* Sets up a custom chain with metaDB enabled (saving receipts, logs, indexes)
*/
export async function setupChain(genesisFile: any, chainName = 'dev', clientOpts: any = {}) {
const genesisParams = await parseCustomParams(genesisFile, chainName)
const genesisState = await parseGenesisState(genesisFile)
const genesisParams = await parseGethGenesis(genesisFile, chainName)
const genesisState = await parseGethGenesisState(genesisFile)

const common = new Common({
chain: chainName,
Expand Down
5 changes: 2 additions & 3 deletions packages/client/test/rpc/util/CLConnectionManager.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Common } from '@ethereumjs/common'
import { Common, parseGethGenesis } from '@ethereumjs/common'
import * as tape from 'tape'

import { Config } from '../../../lib'
import { CLConnectionManager } from '../../../lib/rpc/util/CLConnectionManager'
import { Event } from '../../../lib/types'
import { parseCustomParams } from '../../../lib/util'
import genesisJSON = require('../../testdata/geth-genesis/post-merge.json')

const payload = {
Expand Down Expand Up @@ -44,7 +43,7 @@ tape('[CLConnectionManager]', (t) => {
manager.stop()
st.ok(!manager.running, 'should stop')
;(genesisJSON.config as any).mergeForkBlock = 0
const params = await parseCustomParams(genesisJSON, 'post-merge')
const params = await parseGethGenesis(genesisJSON, 'post-merge')
let common = new Common({
chain: params.name,
customChains: [params],
Expand Down
5 changes: 2 additions & 3 deletions packages/client/test/service/fullethereumservice.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Common } from '@ethereumjs/common'
import { Common, parseGethGenesis } from '@ethereumjs/common'
import * as tape from 'tape'
import * as td from 'testdouble'

import { Chain } from '../../lib/blockchain'
import { Config } from '../../lib/config'
import { Event } from '../../lib/types'
import { parseCustomParams } from '../../lib/util'
import genesisJSON = require('../testdata/geth-genesis/post-merge.json')

import type { Log } from '@ethereumjs/evm/dist/types'
Expand Down Expand Up @@ -195,7 +194,7 @@ tape('[FullEthereumService]', async (t) => {
})

t.test('should start on beacon sync when past merge', async (t) => {
const params = await parseCustomParams(genesisJSON, 'post-merge')
const params = await parseGethGenesis(genesisJSON, 'post-merge')
const common = new Common({ chain: params.name, customChains: [params] })
common.setHardforkByBlockNumber(BigInt(0), BigInt(0))
const config = new Config({ transports: [], common })
Expand Down
Loading