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

Block: extract static Header constructors #3550

Merged
merged 7 commits into from
Jul 30, 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
8 changes: 5 additions & 3 deletions packages/block/examples/simple.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { BlockHeader } from '@ethereumjs/block'
import { createHeader } from '@ethereumjs/block'
import { bytesToHex } from '@ethereumjs/util'

const headerData = {
import type { HeaderData } from '@ethereumjs/block'

const headerData: HeaderData = {
number: 15,
parentHash: '0x6bfee7294bf44572b7266358e627f3c35105e1c3851f3de09e6d646f955725a7',
gasLimit: 8000000,
timestamp: 1562422144,
}
const header = BlockHeader.fromHeaderData(headerData)
const header = createHeader(headerData)
console.log(`Created block header with hash=${bytesToHex(header.hash())}`)
20 changes: 10 additions & 10 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from '@ethereumjs/util'
import { keccak256 } from 'ethereum-cryptography/keccak.js'

import { BlockHeader } from './header.js'
import { genRequestsTrieRoot, genTransactionsTrieRoot, genWithdrawalsTrieRoot } from './helpers.js'

/* eslint-disable */
Expand All @@ -21,14 +20,15 @@ import { genRequestsTrieRoot, genTransactionsTrieRoot, genWithdrawalsTrieRoot }
// TODO: See if there is an easier way to achieve the same result.
// See: https://github.com/microsoft/TypeScript/issues/47558
// (situation will eventually improve on Typescript and/or Eslint update)
import type {
createBlockFromBeaconPayloadJson,
createBlockFromBlockData,
createBlockFromExecutionPayload,
createBlockFromJsonRpcProvider,
createBlockFromRLPSerializedBlock,
createBlockFromRPC,
createBlockFromValuesArray,
import {
BlockHeader,
type createBlockFromBeaconPayloadJson,
type createBlockFromBlockData,
type createBlockFromExecutionPayload,
type createBlockFromJsonRpcProvider,
type createBlockFromRLPSerializedBlock,
type createBlockFromRPC,
type createBlockFromValuesArray,
} from './index.js'
/* eslint-enable */
import type { BlockBytes, BlockOptions, ExecutionPayload, JsonBlock } from './types.js'
Expand Down Expand Up @@ -100,7 +100,7 @@ export class Block {
requests?: CLRequest<CLRequestType>[],
executionWitness?: VerkleExecutionWitness | null,
) {
this.header = header ?? BlockHeader.fromHeaderData({}, opts)
this.header = header ?? new BlockHeader({}, opts)
this.common = this.header.common
this.keccakFunction = this.common.customCrypto.keccak256 ?? keccak256

Expand Down
78 changes: 73 additions & 5 deletions packages/block/src/constructors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
DepositRequest,
Withdrawal,
WithdrawalRequest,
bigIntToBytes,
bigIntToHex,
bytesToHex,
bytesToUtf8,
Expand All @@ -24,14 +25,20 @@ import {
} from '@ethereumjs/util'

import { createBlockFromRpc } from './from-rpc.js'
import { genRequestsTrieRoot, genTransactionsTrieRoot, genWithdrawalsTrieRoot } from './helpers.js'
import {
genRequestsTrieRoot,
genTransactionsTrieRoot,
genWithdrawalsTrieRoot,
valuesArrayToHeaderData,
} from './helpers.js'

import { Block, BlockHeader, executionPayloadFromBeaconPayload } from './index.js'

import type { BeaconPayloadJson } from './from-beacon-payload.js'
import type {
BlockBytes,
BlockData,
BlockHeaderBytes,
BlockOptions,
ExecutionPayload,
ExecutionWitnessBytes,
Expand All @@ -49,6 +56,67 @@ import type {
WithdrawalBytes,
} from '@ethereumjs/util'

/**
* Static constructor to create a block header from a header data dictionary
*
* @param headerData
* @param opts
*/
export function createHeader(headerData: HeaderData = {}, opts: BlockOptions = {}) {
return new BlockHeader(headerData, opts)
}

/**
* Static constructor to create a block header from an array of Bytes values
*
* @param values
* @param opts
*/
export function createHeaderFromValuesArray(values: BlockHeaderBytes, opts: BlockOptions = {}) {
const headerData = valuesArrayToHeaderData(values)
const { number, baseFeePerGas, excessBlobGas, blobGasUsed, parentBeaconBlockRoot, requestsRoot } =
headerData
const header = createHeader(headerData, opts)
if (header.common.isActivatedEIP(1559) && baseFeePerGas === undefined) {
const eip1559ActivationBlock = bigIntToBytes(header.common.eipBlock(1559)!)
if (
eip1559ActivationBlock !== undefined &&
equalsBytes(eip1559ActivationBlock, number as Uint8Array)
) {
throw new Error('invalid header. baseFeePerGas should be provided')
}
}
if (header.common.isActivatedEIP(4844)) {
if (excessBlobGas === undefined) {
throw new Error('invalid header. excessBlobGas should be provided')
} else if (blobGasUsed === undefined) {
throw new Error('invalid header. blobGasUsed should be provided')
}
}
if (header.common.isActivatedEIP(4788) && parentBeaconBlockRoot === undefined) {
throw new Error('invalid header. parentBeaconBlockRoot should be provided')
}

if (header.common.isActivatedEIP(7685) && requestsRoot === undefined) {
throw new Error('invalid header. requestsRoot should be provided')
}
return header
}

/**
* Static constructor to create a block header from a RLP-serialized header
*
* @param serializedHeaderData
* @param opts
*/
export function createHeaderFromRLP(serializedHeaderData: Uint8Array, opts: BlockOptions = {}) {
const values = RLP.decode(serializedHeaderData)
if (!Array.isArray(values)) {
throw new Error('Invalid serialized header input. Must be array')
}
return createHeaderFromValuesArray(values as Uint8Array[], opts)
}

/**
* Static constructor to create a block from a block data dictionary
*
Expand All @@ -65,7 +133,7 @@ export function createBlockFromBlockData(blockData: BlockData = {}, opts?: Block
requests: clRequests,
} = blockData

const header = BlockHeader.fromHeaderData(headerData, opts)
const header = createHeader(headerData, opts)

// parse transactions
const transactions = []
Expand All @@ -92,7 +160,7 @@ export function createBlockFromBlockData(blockData: BlockData = {}, opts?: Block
uncleOpts.setHardfork = true
}
for (const uhData of uhsData ?? []) {
const uh = BlockHeader.fromHeaderData(uhData, uncleOpts)
const uh = createHeader(uhData, uncleOpts)
uncleHeaders.push(uh)
}

Expand Down Expand Up @@ -126,7 +194,7 @@ export function createBlockFromValuesArray(values: BlockBytes, opts?: BlockOptio
// First try to load header so that we can use its common (in case of setHardfork being activated)
// to correctly make checks on the hardforks
const [headerData, txsData, uhsData, ...valuesTail] = values
const header = BlockHeader.fromValuesArray(headerData, opts)
const header = createHeaderFromValuesArray(headerData, opts)

// conditional assignment of rest of values and splicing them out from the valuesTail
const withdrawalBytes = header.common.isActivatedEIP(4895)
Expand Down Expand Up @@ -191,7 +259,7 @@ export function createBlockFromValuesArray(values: BlockBytes, opts?: BlockOptio
uncleOpts.setHardfork = true
}
for (const uncleHeaderData of uhsData ?? []) {
uncleHeaders.push(BlockHeader.fromValuesArray(uncleHeaderData, uncleOpts))
uncleHeaders.push(createHeaderFromValuesArray(uncleHeaderData, uncleOpts))
}

const withdrawals = (withdrawalBytes as WithdrawalBytes[])
Expand Down
5 changes: 3 additions & 2 deletions packages/block/src/header-from-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BlockHeader } from './header.js'
import { numberToHex } from './helpers.js'

import { BlockHeader } from './index.js'

import type { BlockOptions, JsonRpcBlock } from './types.js'

/**
Expand Down Expand Up @@ -34,7 +35,7 @@ export function blockHeaderFromRpc(blockParams: JsonRpcBlock, options?: BlockOpt
requestsRoot,
} = blockParams

const blockHeader = BlockHeader.fromHeaderData(
const blockHeader = new BlockHeader(
{
parentHash,
uncleHash: sha3Uncles,
Expand Down
70 changes: 2 additions & 68 deletions packages/block/src/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
import { keccak256 } from 'ethereum-cryptography/keccak.js'

import { CLIQUE_EXTRA_SEAL, CLIQUE_EXTRA_VANITY } from './clique.js'
import { fakeExponential, valuesArrayToHeaderData } from './helpers.js'
import { fakeExponential } from './helpers.js'
import { paramsBlock } from './params.js'

import type { BlockHeaderBytes, BlockOptions, HeaderData, JsonHeader } from './types.js'
Expand Down Expand Up @@ -89,77 +89,11 @@ export class BlockHeader {
return this.mixHash
}

/**
* Static constructor to create a block header from a header data dictionary
*
* @param headerData
* @param opts
*/
public static fromHeaderData(headerData: HeaderData = {}, opts: BlockOptions = {}) {
return new BlockHeader(headerData, opts)
}

/**
* Static constructor to create a block header from a RLP-serialized header
*
* @param serializedHeaderData
* @param opts
*/
public static fromRLPSerializedHeader(serializedHeaderData: Uint8Array, opts: BlockOptions = {}) {
const values = RLP.decode(serializedHeaderData)
if (!Array.isArray(values)) {
throw new Error('Invalid serialized header input. Must be array')
}
return BlockHeader.fromValuesArray(values as Uint8Array[], opts)
}

/**
* Static constructor to create a block header from an array of Bytes values
*
* @param values
* @param opts
*/
public static fromValuesArray(values: BlockHeaderBytes, opts: BlockOptions = {}) {
const headerData = valuesArrayToHeaderData(values)
const {
number,
baseFeePerGas,
excessBlobGas,
blobGasUsed,
parentBeaconBlockRoot,
requestsRoot,
} = headerData
const header = BlockHeader.fromHeaderData(headerData, opts)
if (header.common.isActivatedEIP(1559) && baseFeePerGas === undefined) {
const eip1559ActivationBlock = bigIntToBytes(header.common.eipBlock(1559)!)
if (
eip1559ActivationBlock !== undefined &&
equalsBytes(eip1559ActivationBlock, number as Uint8Array)
) {
throw new Error('invalid header. baseFeePerGas should be provided')
}
}
if (header.common.isActivatedEIP(4844)) {
if (excessBlobGas === undefined) {
throw new Error('invalid header. excessBlobGas should be provided')
} else if (blobGasUsed === undefined) {
throw new Error('invalid header. blobGasUsed should be provided')
}
}
if (header.common.isActivatedEIP(4788) && parentBeaconBlockRoot === undefined) {
throw new Error('invalid header. parentBeaconBlockRoot should be provided')
}

if (header.common.isActivatedEIP(7685) && requestsRoot === undefined) {
throw new Error('invalid header. requestsRoot should be provided')
}
return header
}
/**
* This constructor takes the values, validates them, assigns them and freezes the object.
*
* @deprecated Use the public static factory methods to assist in creating a Header object from
* varying data types. For a default empty header, use {@link BlockHeader.fromHeaderData}.
* varying data types. For a default empty header, use {@link createHeader}.
*
*/
constructor(headerData: HeaderData, opts: BlockOptions = {}) {
Expand Down
17 changes: 7 additions & 10 deletions packages/block/test/clique.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Chain, Common, Hardfork } from '@ethereumjs/common'
import { Address, createZeroAddress, hexToBytes } from '@ethereumjs/util'
import { assert, describe, it } from 'vitest'

import { BlockHeader } from '../src/header.js'
import { createHeader } from '../src/constructors.js'

describe('[Header]: Clique PoA Functionality', () => {
const common = new Common({ chain: Chain.Goerli, hardfork: Hardfork.Chainstart })

it('Header Data', () => {
let header = BlockHeader.fromHeaderData({ number: 1 })
let header = createHeader({ number: 1 })
assert.throws(
() => {
header.cliqueIsEpochTransition()
Expand All @@ -18,13 +18,13 @@ describe('[Header]: Clique PoA Functionality', () => {
'cliqueIsEpochTransition() -> should throw on PoW networks',
)

header = BlockHeader.fromHeaderData({ extraData: new Uint8Array(97) }, { common })
header = createHeader({ extraData: new Uint8Array(97) }, { common })
assert.ok(
header.cliqueIsEpochTransition(),
'cliqueIsEpochTransition() -> should indicate an epoch transition for the genesis block',
)

header = BlockHeader.fromHeaderData({ number: 1, extraData: new Uint8Array(97) }, { common })
header = createHeader({ number: 1, extraData: new Uint8Array(97) }, { common })
assert.notOk(
header.cliqueIsEpochTransition(),
'cliqueIsEpochTransition() -> should correctly identify a non-epoch block',
Expand All @@ -48,10 +48,7 @@ describe('[Header]: Clique PoA Functionality', () => {
'cliqueEpochTransitionSigners() -> should throw on non-epch block',
)

header = BlockHeader.fromHeaderData(
{ number: 60000, extraData: new Uint8Array(137) },
{ common },
)
header = createHeader({ number: 60000, extraData: new Uint8Array(137) }, { common })
assert.ok(
header.cliqueIsEpochTransition(),
'cliqueIsEpochTransition() -> should correctly identify an epoch block',
Expand Down Expand Up @@ -92,7 +89,7 @@ describe('[Header]: Clique PoA Functionality', () => {
it('Signing', () => {
const cliqueSigner = A.privateKey

let header = BlockHeader.fromHeaderData(
let header = createHeader(
{ number: 1, extraData: new Uint8Array(97) },
{ common, freeze: false, cliqueSigner },
)
Expand All @@ -101,7 +98,7 @@ describe('[Header]: Clique PoA Functionality', () => {
assert.ok(header.cliqueVerifySignature([A.address]), 'should verify signature')
assert.ok(header.cliqueSigner().equals(A.address), 'should recover the correct signer address')

header = BlockHeader.fromHeaderData({ extraData: new Uint8Array(97) }, { common })
header = createHeader({ extraData: new Uint8Array(97) }, { common })
assert.ok(
header.cliqueSigner().equals(createZeroAddress()),
'should return zero address on default block',
Expand Down
Loading
Loading