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

Chain setup API / Centralize default HF / Common options dict / Block HFbyBlockNumber & initWithGenesisHeader options #863

Merged
merged 13 commits into from
Sep 10, 2020
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
47 changes: 14 additions & 33 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BaseTrie as Trie } from 'merkle-patricia-tree'
import Common from '@ethereumjs/common'
import { BN, rlp, keccak256, KECCAK256_RLP, baToJSON } from 'ethereumjs-util'
import { BN, rlp, keccak256, KECCAK256_RLP, baToJSON, bufferToInt } from 'ethereumjs-util'
import { Transaction, TransactionOptions } from '@ethereumjs/tx'
import { BlockHeader } from './header'
import { Blockchain, BlockData, ChainOptions } from './types'
import { Blockchain, BlockData, BlockOptions } from './types'

/**
* An object that represents the block
Expand All @@ -19,34 +19,22 @@ export class Block {
/**
* Creates a new block object
*
* Please solely use this constructor to pass in block header data
* and don't modfiy header data after initialization since this can lead to
* undefined behavior regarding HF rule implemenations within the class.
*
* @param data - The block's data.
* @param chainOptions - The network options for this block, and its header, uncle headers and txs.
* @param options - The options for this block (like the chain setup)
*/
constructor(
data: Buffer | [Buffer[], Buffer[], Buffer[]] | BlockData = {},
chainOptions: ChainOptions = {},
options: BlockOptions = {},
) {
// Checking at runtime, to prevent errors down the path for JavaScript consumers.
if (data === null) {
data = {}
}

if (chainOptions.common) {
if (chainOptions.chain !== undefined || chainOptions.hardfork !== undefined) {
throw new Error(
'Instantiation with both chainOptions.common and chainOptions.chain / chainOptions.hardfork parameter not allowed!',
)
}

this._common = chainOptions.common
} else {
const chain = chainOptions.chain ? chainOptions.chain : 'mainnet'
// TODO: Compute the hardfork based on this block's number. It can be implemented right now
// because the block number is not immutable, so the Common can get out of sync.
const hardfork = chainOptions.hardfork ? chainOptions.hardfork : null
this._common = new Common(chain, hardfork)
}

let rawTransactions
let rawUncleHeaders

Expand All @@ -57,26 +45,26 @@ export class Block {
data = dataAsAny as [Buffer[], Buffer[], Buffer[]]
}

// Initialize the block header
if (Array.isArray(data)) {
this.header = new BlockHeader(data[0], { common: this._common })
this.header = new BlockHeader(data[0], options)
rawTransactions = data[1]
rawUncleHeaders = data[2]
} else {
this.header = new BlockHeader(data.header, { common: this._common })
this.header = new BlockHeader(data.header, options)
rawTransactions = data.transactions || []
rawUncleHeaders = data.uncleHeaders || []
}
this._common = this.header._common

// parse uncle headers
for (let i = 0; i < rawUncleHeaders.length; i++) {
this.uncleHeaders.push(new BlockHeader(rawUncleHeaders[i], chainOptions))
this.uncleHeaders.push(new BlockHeader(rawUncleHeaders[i], options))
}

// parse transactions
for (let i = 0; i < rawTransactions.length; i++) {
// TODO: Pass the common object instead of the options. It can't be implemented right now
// because the hardfork may be `null`. Read the above TODO for more info.
const tx = new Transaction(rawTransactions[i], chainOptions as TransactionOptions)
const tx = new Transaction(rawTransactions[i], { common: this._common })
this.transactions.push(tx)
}
}
Expand All @@ -99,13 +87,6 @@ export class Block {
return this.header.isGenesis()
}

/**
* Turns the block into the canonical genesis block
*/
setGenesisParams(): void {
this.header.setGenesisParams()
}

/**
* Produces a serialization of the block.
*
Expand Down
16 changes: 6 additions & 10 deletions packages/block/src/from-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FakeTransaction, TransactionOptions } from '@ethereumjs/tx'
import { toBuffer, setLengthLeft } from 'ethereumjs-util'
import { Block, ChainOptions } from './index'
import { Block, BlockOptions } from './index'

import blockHeaderFromRpc from './header-from-rpc'

Expand All @@ -11,22 +11,18 @@ import blockHeaderFromRpc from './header-from-rpc'
* @param uncles - Optional list of Ethereum JSON RPC of uncles (eth_getUncleByBlockHashAndIndex)
* @param chainOptions - An object describing the blockchain
*/
export default function blockFromRpc(
blockParams: any,
uncles?: any[],
chainOptions?: ChainOptions,
) {
export default function blockFromRpc(blockParams: any, uncles?: any[], options?: BlockOptions) {
uncles = uncles || []

const header = blockHeaderFromRpc(blockParams, chainOptions)
const header = blockHeaderFromRpc(blockParams, options)

const block = new Block(
{
header: header.toJSON(true),
transactions: [],
uncleHeaders: uncles.map((uh) => blockHeaderFromRpc(uh, chainOptions).toJSON(true)),
uncleHeaders: uncles.map((uh) => blockHeaderFromRpc(uh, options).toJSON(true)),
},
chainOptions,
options,
)

if (blockParams.transactions) {
Expand All @@ -36,7 +32,7 @@ export default function blockFromRpc(
const fromAddress = toBuffer(txParams.from)
delete txParams.from

const tx = new FakeTransaction(txParams, chainOptions as TransactionOptions)
const tx = new FakeTransaction(txParams, options as TransactionOptions)
tx.from = fromAddress
tx.getSenderAddress = function () {
return fromAddress
Expand Down
6 changes: 3 additions & 3 deletions packages/block/src/header-from-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { BlockHeader } from './header'
import { KECCAK256_NULL, toBuffer } from 'ethereumjs-util'
import { ChainOptions } from './types'
import { BlockOptions } from './types'

/**
* Creates a new block header object from Ethereum JSON RPC.
*
* @param blockParams - Ethereum JSON RPC of block (eth_getBlockByNumber)
* @param chainOptions - An object describing the blockchain
*/
export default function blockHeaderFromRpc(blockParams: any, chainOptions?: ChainOptions) {
export default function blockHeaderFromRpc(blockParams: any, options?: BlockOptions) {
const blockHeader = new BlockHeader(
{
parentHash: blockParams.parentHash,
Expand All @@ -27,7 +27,7 @@ export default function blockHeaderFromRpc(blockParams: any, chainOptions?: Chai
mixHash: blockParams.mixHash,
nonce: blockParams.nonce,
},
chainOptions,
options,
)

// override hash in case something was missing
Expand Down
49 changes: 34 additions & 15 deletions packages/block/src/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
bufferToInt,
rlphash,
} from 'ethereumjs-util'
import { Blockchain, BlockHeaderData, BufferLike, ChainOptions, PrefixedHexString } from './types'
import { Blockchain, BlockHeaderData, BufferLike, BlockOptions, PrefixedHexString } from './types'
import { Buffer } from 'buffer'
import { Block } from './block'

Expand All @@ -34,31 +34,39 @@ export class BlockHeader {
public mixHash!: Buffer
public nonce!: Buffer

private readonly _common: Common
readonly _common: Common

/**
* Creates a new block header.
*
* Please solely use this constructor to pass in block header data
* and don't modfiy header data after initialization since this can lead to
* undefined behavior regarding HF rule implemenations within the class.
*
* @param data - The data of the block header.
* @param opts - The network options for this block, and its header, uncle headers and txs.
*/
constructor(
data: Buffer | PrefixedHexString | BufferLike[] | BlockHeaderData = {},
opts: ChainOptions = {},
options: BlockOptions = {},
) {
if (opts.common !== undefined) {
if (opts.chain !== undefined || opts.hardfork !== undefined) {
throw new Error(
'Instantiation with both opts.common and opts.chain / opts.hardfork parameter not allowed!',
)
}
// Throw on chain or hardfork options removed in latest major release
// to prevent implicit chain setup on a wrong chain
if ('chain' in options || 'hardfork' in options) {
throw new Error('Chain/hardfork options are not allowed any more on initialization')
}

this._common = opts.common
if (options.common) {
this._common = options.common
} else {
const chain = opts.chain ? opts.chain : 'mainnet'
const hardfork = opts.hardfork ? opts.hardfork : null
this._common = new Common(chain, hardfork)
const DEFAULT_CHAIN = 'mainnet'
if (options.initWithGenesisHeader) {
this._common = new Common({ chain: DEFAULT_CHAIN, hardfork: 'chainstart' })
} else {
// This initializes on the Common default hardfork
this._common = new Common({ chain: DEFAULT_CHAIN })
}
}

const fields = [
{
name: 'parentHash',
Expand Down Expand Up @@ -133,6 +141,14 @@ export class BlockHeader {
]
defineProperties(this, fields, data)

if (options.hardforkByBlockNumber) {
this._common.setHardforkByBlockNumber(bufferToInt(this.number))
}

if (options.initWithGenesisHeader) {
this._setGenesisParams()
}

this._checkDAOExtraData()
}

Expand Down Expand Up @@ -316,7 +332,10 @@ export class BlockHeader {
/**
* Turns the header into the canonical genesis block header.
*/
setGenesisParams(): void {
_setGenesisParams(): void {
if (this._common.hardfork() !== 'chainstart') {
throw new Error('Genesis parameters can only be set with a Common instance set to chainstart')
}
this.timestamp = this._common.genesis().timestamp
this.gasLimit = this._common.genesis().gasLimit
this.difficulty = this._common.genesis().difficulty
Expand Down
24 changes: 17 additions & 7 deletions packages/block/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,31 @@ import { Block } from './block'
* using a Common object, or `chain` and `hardfork`. Defaults to mainnet without specifying a
* hardfork.
*/
export interface ChainOptions {
export interface BlockOptions {
/**
* A Common object defining the chain and the hardfork a block/block header belongs to.
*
* Default: `Common` object set to `mainnet` and the HF currently defined as the default
* hardfork in the `Common` class
*/
common?: Common

/**
* The chain of the block/block header, default: 'mainnet'
* Determine the HF by the block number
*
* Default: `false` (HF is set to whatever default HF is set by the Common instance)
*/
chain?: number | string

hardforkByBlockNumber?: boolean
/**
* The hardfork of the block/block header, default: 'petersburg'
* Turns the block header into the canonical genesis block header
*
* If set to `true` all other header data is ignored.
*
* If a Common instance is passed the instance need to be set to `chainstart` as a HF,
* otherwise usage of this option will throw
*
* Default: `false`
*/
hardfork?: string
initWithGenesisHeader?: boolean
}

/**
Expand Down
42 changes: 15 additions & 27 deletions packages/block/test/block.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,9 @@ import { Block } from '../src/block'

tape('[Block]: block functions', function (t) {
t.test('should test block initialization', function (st) {
const block1 = new Block(undefined, { chain: 'ropsten' })
const common = new Common('ropsten')
const block2 = new Block(undefined, { common: common })
block1.setGenesisParams()
block2.setGenesisParams()
st.ok(block1.hash().equals(block2.hash()), 'block hashes match')

st.throws(
function () {
new Block(undefined, { chain: 'ropsten', common: common })
},
/not allowed!$/,
'should throw on initialization with chain and common parameter',
) // eslint-disable-line
const common = new Common({ chain: 'ropsten', hardfork: 'chainstart' })
const block1 = new Block(undefined, { common: common, initWithGenesisHeader: true })
st.ok(block1.hash().toString('hex'), 'block should initialize')
st.end()
})

Expand All @@ -32,7 +21,8 @@ tape('[Block]: block functions', function (t) {

t.test('should initialize with null parameters without throwing', function (st) {
st.doesNotThrow(function () {
const opts = { chain: 'mainnet' }
const common = new Common({ chain: 'ropsten' })
const opts = { common }
new Block(undefined, opts)
st.end()
})
Expand Down Expand Up @@ -77,7 +67,8 @@ tape('[Block]: block functions', function (t) {
})

t.test('should test isGenesis (ropsten)', function (st) {
const block = new Block(undefined, { chain: 'ropsten' })
const common = new Common({ chain: 'ropsten' })
const block = new Block(undefined, { common })
st.notEqual(block.isGenesis(), true)
block.header.number = Buffer.from([])
st.equal(block.isGenesis(), true)
Expand All @@ -86,8 +77,7 @@ tape('[Block]: block functions', function (t) {

const testDataGenesis = require('./testdata/genesishashestest.json').test
t.test('should test genesis hashes (mainnet default)', function (st) {
const genesisBlock = new Block()
genesisBlock.setGenesisParams()
const genesisBlock = new Block(undefined, { initWithGenesisHeader: true })
const rlp = genesisBlock.serialize()
st.strictEqual(rlp.toString('hex'), testDataGenesis.genesis_rlp_hex, 'rlp hex match')
st.strictEqual(
Expand All @@ -99,9 +89,8 @@ tape('[Block]: block functions', function (t) {
})

t.test('should test genesis hashes (ropsten)', function (st) {
const common = new Common('ropsten')
const genesisBlock = new Block(undefined, { common: common })
genesisBlock.setGenesisParams()
const common = new Common({ chain: 'ropsten', hardfork: 'chainstart' })
const genesisBlock = new Block(undefined, { common: common, initWithGenesisHeader: true })
st.strictEqual(
genesisBlock.hash().toString('hex'),
common.genesis().hash.slice(2),
Expand All @@ -111,9 +100,8 @@ tape('[Block]: block functions', function (t) {
})

t.test('should test genesis hashes (rinkeby)', function (st) {
const common = new Common('rinkeby')
const genesisBlock = new Block(undefined, { common: common })
genesisBlock.setGenesisParams()
const common = new Common({ chain: 'rinkeby', hardfork: 'chainstart' })
const genesisBlock = new Block(undefined, { common: common, initWithGenesisHeader: true })
st.strictEqual(
genesisBlock.hash().toString('hex'),
common.genesis().hash.slice(2),
Expand All @@ -123,8 +111,8 @@ tape('[Block]: block functions', function (t) {
})

t.test('should test genesis parameters (ropsten)', function (st) {
const genesisBlock = new Block(undefined, { chain: 'ropsten' })
genesisBlock.setGenesisParams()
const common = new Common({ chain: 'ropsten', hardfork: 'chainstart' })
const genesisBlock = new Block(undefined, { common, initWithGenesisHeader: true })
const ropstenStateRoot = '217b0bbcfb72e2d57e28f33cb361b9983513177755dc3f33ce3e7022ed62b77b'
st.strictEqual(
genesisBlock.header.stateRoot.toString('hex'),
Expand All @@ -146,7 +134,7 @@ tape('[Block]: block functions', function (t) {
// Set block number from test block to mainnet DAO fork block 1920000
blockData[0][8] = Buffer.from('1D4C00', 'hex')

const common = new Common('mainnet', 'dao')
const common = new Common({ chain: 'mainnet', hardfork: 'dao' })
st.throws(
function () {
new Block(blockData, { common: common })
Expand Down
Loading