Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c053761
block -> refactor: reworked header class with static factory instanti…
holgerd77 Sep 21, 2020
ff46c55
block -> refactoring: added new static factory helpers to block class
holgerd77 Sep 21, 2020
dd228bc
block -> refactor: fix build errors, remove unused imports, unpad num…
jochem-brouwer Sep 24, 2020
608d804
block -> rename Header to BlockHeader
jochem-brouwer Sep 28, 2020
17a33a8
block/tx -> fix block tests
jochem-brouwer Sep 28, 2020
72b5b78
block -> enforce BNs on fields which are interpreted as numbers
jochem-brouwer Sep 28, 2020
8adcdf9
block -> edge case in toBN
jochem-brouwer Sep 29, 2020
8987003
ethash -> make ethash compatible with block
jochem-brouwer Sep 29, 2020
5f5e9f2
Merge branch 'master' into refactor-block-library
ryanio Oct 7, 2020
5351fb8
have validateTransactions return a string[] (https://github.com/ether…
ryanio Oct 7, 2020
ce1dac1
let => const
ryanio Oct 7, 2020
526f986
set default param to resolve js runtime check
ryanio Oct 7, 2020
75689e6
continue refactoring and simplifying methods
ryanio Oct 8, 2020
8923f71
api updates
ryanio Oct 8, 2020
6a2c193
continuing work
ryanio Oct 8, 2020
381f5e1
inline buffer validations. add checks for extraData, mixHash and nonce
ryanio Oct 8, 2020
7eecf80
various fixups
ryanio Oct 8, 2020
395c6f8
continuing various work
ryanio Oct 9, 2020
be5c8d2
continuing work and refactoring
ryanio Oct 9, 2020
7fa486d
Merge branch 'master' into refactor-block-library
ryanio Oct 9, 2020
91d45d7
re-add timestamp to genesis (for rinkeby)
ryanio Oct 9, 2020
7e788c3
last fixups
ryanio Oct 9, 2020
bc459e8
update readme, benchmarks
ryanio Oct 9, 2020
ea8a401
update vm readme, simplify validate
ryanio Oct 10, 2020
b694010
fix timestamp validation
ryanio Oct 10, 2020
1f66378
use native eq
ryanio Oct 10, 2020
9f9bab0
make blockchain optional in block.validate()
ryanio Oct 10, 2020
7ce9132
fixups
ryanio Oct 10, 2020
a5d3d14
remove BLOCK_difficulty_GivenAsList from skip list (https://github.co…
ryanio Oct 12, 2020
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
Next Next commit
block -> refactor: reworked header class with static factory instanti…
…ation helpers, removed defineProperties usage, fixed header tests
  • Loading branch information
holgerd77 authored and jochem-brouwer committed Sep 24, 2020
commit c053761ac9092f003b702b10029c37da8880581c
313 changes: 183 additions & 130 deletions packages/block/src/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,156 @@ import {
zeros,
KECCAK256_RLP_ARRAY,
KECCAK256_RLP,
rlp,
toBuffer,
defineProperties,
unpadBuffer,
bufferToInt,
rlphash,
unpadArray,
} from 'ethereumjs-util'
import { Blockchain, BlockHeaderData, BufferLike, BlockOptions, PrefixedHexString } from './types'
import { Blockchain, HeaderData, BufferLike, BlockOptions, PrefixedHexString } from './types'
import { Buffer } from 'buffer'
import { Block } from './block'
import { checkBufferLength } from './util'
import { Options } from 'lru-cache'
import { statSync } from 'fs'

/**
* An object that represents the block header
*/
export class BlockHeader {
public raw!: Buffer[]
public parentHash!: Buffer
public uncleHash!: Buffer
public coinbase!: Buffer
public stateRoot!: Buffer
public transactionsTrie!: Buffer
public receiptTrie!: Buffer
public bloom!: Buffer
public difficulty!: Buffer
public number!: Buffer
public gasLimit!: Buffer
public gasUsed!: Buffer
public timestamp!: Buffer
public extraData!: Buffer
public mixHash!: Buffer
public nonce!: Buffer

readonly _common: Common
export class Header {
public readonly _common: Common
public readonly parentHash: Buffer
public readonly uncleHash: Buffer
public readonly coinbase: Buffer
public readonly stateRoot: Buffer
public readonly transactionsTrie: Buffer
public readonly receiptTrie: Buffer
public readonly bloom: Buffer
public readonly difficulty: Buffer
public readonly number: Buffer
public readonly gasLimit: Buffer
public readonly gasUsed: Buffer
public readonly timestamp: Buffer
public readonly extraData: Buffer
public readonly mixHash: Buffer
public readonly nonce: Buffer

public static fromHeaderData(headerData: HeaderData, opts: BlockOptions = {}) {
const {
parentHash,
uncleHash,
coinbase,
stateRoot,
transactionsTrie,
receiptTrie,
bloom,
difficulty,
number,
gasLimit,
gasUsed,
timestamp,
extraData,
mixHash,
nonce,
} = headerData

return new Header(
parentHash ? checkBufferLength(toBuffer(parentHash), 32) : zeros(32),
uncleHash ? toBuffer(uncleHash) : KECCAK256_RLP_ARRAY,
coinbase ? checkBufferLength(toBuffer(coinbase), 20) : zeros(20),
stateRoot ? checkBufferLength(toBuffer(stateRoot), 32) : zeros(32),
transactionsTrie ? checkBufferLength(toBuffer(transactionsTrie), 32) : KECCAK256_RLP,
receiptTrie ? checkBufferLength(toBuffer(receiptTrie), 32) : KECCAK256_RLP,
bloom ? toBuffer(bloom) : zeros(256),
difficulty ? toBuffer(difficulty) : Buffer.from([]),
number ? toBuffer(number) : Buffer.from([]),
gasLimit ? toBuffer(gasLimit) : Buffer.from('ffffffffffffff', 'hex'),
gasUsed ? toBuffer(gasUsed) : Buffer.from([]),
timestamp ? toBuffer(timestamp) : Buffer.from([]),
extraData ? toBuffer(extraData) : Buffer.from([]),
mixHash ? toBuffer(mixHash) : zeros(32),
nonce ? toBuffer(nonce) : zeros(8),
opts,
)
}

public static fromRLPSerializedHeader(serialized: Buffer, opts: BlockOptions) {
const values = rlp.decode(serialized)

if (!Array.isArray(values)) {
throw new Error('Invalid serialized header input. Must be array')
}

return this.fromValuesArray(values, opts)
}

public static fromValuesArray(values: Buffer[], opts: BlockOptions) {
if (values.length > 15) {
throw new Error('invalid header. More values than expected were received')
}

const [
parentHash,
uncleHash,
coinbase,
stateRoot,
transactionsTrie,
receiptTrie,
bloom,
difficulty,
number,
gasLimit,
gasUsed,
timestamp,
extraData,
mixHash,
nonce,
] = values
return new Header(
parentHash,
uncleHash,
coinbase,
stateRoot,
transactionsTrie,
receiptTrie,
bloom,
difficulty,
number,
gasLimit,
gasUsed,
timestamp,
extraData,
mixHash,
nonce,
opts,
)
}

/**
* 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.
* This constructor takes the values, validates them, assigns them and freezes the object.
* Use the public static factory methods to assist in creating a Header object from
* varying data types.
*/
constructor(
data: Buffer | PrefixedHexString | BufferLike[] | BlockHeaderData = {},
parentHash: Buffer,
uncleHash: Buffer,
coinbase: Buffer,
stateRoot: Buffer,
transactionsTrie: Buffer,
receiptTrie: Buffer,
bloom: Buffer,
difficulty: Buffer,
number: Buffer,
gasLimit: Buffer,
gasUsed: Buffer,
timestamp: Buffer,
extraData: Buffer,
mixHash: Buffer,
nonce: Buffer,
//data: Buffer | PrefixedHexString | BufferLike[] | HeaderData = {},
options: BlockOptions = {},
) {
// 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')
}

if (options.common) {
this._common = options.common
} else {
Expand All @@ -67,89 +165,43 @@ export class BlockHeader {
this._common = new Common({ chain: DEFAULT_CHAIN })
}
}
const fields = [
{
name: 'parentHash',
length: 32,
default: zeros(32),
},
{
name: 'uncleHash',
default: KECCAK256_RLP_ARRAY,
},
{
name: 'coinbase',
length: 20,
default: zeros(20),
},
{
name: 'stateRoot',
length: 32,
default: zeros(32),
},
{
name: 'transactionsTrie',
length: 32,
default: KECCAK256_RLP,
},
{
name: 'receiptTrie',
length: 32,
default: KECCAK256_RLP,
},
{
name: 'bloom',
default: zeros(256),
},
{
name: 'difficulty',
default: Buffer.from([]),
},
{
name: 'number',
// TODO: params.homeSteadForkNumber.v left for legacy reasons, replace on future release
default: toBuffer(1150000),
},
{
name: 'gasLimit',
default: Buffer.from('ffffffffffffff', 'hex'),
},
{
name: 'gasUsed',
empty: true,
default: Buffer.from([]),
},
{
name: 'timestamp',
default: Buffer.from([]),
},
{
name: 'extraData',
allowZero: true,
empty: true,
default: Buffer.from([]),
},
{
name: 'mixHash',
default: zeros(32),
// length: 32
},
{
name: 'nonce',
default: zeros(8), // sha3(42)
},
]
defineProperties(this, fields, data)

this.parentHash = parentHash
this.uncleHash = uncleHash
this.coinbase = coinbase
this.stateRoot = stateRoot
this.transactionsTrie = transactionsTrie
this.receiptTrie = receiptTrie
this.bloom = bloom
this.difficulty = difficulty
this.number = number
this.gasLimit = gasLimit
this.gasUsed = gasUsed
this.timestamp = timestamp
this.extraData = extraData
this.mixHash = mixHash
this.nonce = nonce

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

if (options.initWithGenesisHeader) {
this._setGenesisParams()
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
this.extraData = this._common.genesis().extraData
this.nonce = this._common.genesis().nonce
this.stateRoot = this._common.genesis().stateRoot
this.number = toBuffer(0)
}

this._checkDAOExtraData()

Object.freeze(this)
}

/**
Expand Down Expand Up @@ -325,30 +377,31 @@ export class BlockHeader {
* Returns the hash of the block header.
*/
hash(): Buffer {
return rlphash(this.raw)
const values: Buffer[] = [
this.parentHash,
this.uncleHash,
this.coinbase,
this.stateRoot,
this.transactionsTrie,
this.receiptTrie,
this.bloom,
this.difficulty,
unpadBuffer(this.number),
this.gasLimit,
this.gasUsed,
this.timestamp,
this.extraData,
this.mixHash,
this.nonce,
]
return rlphash(values)
}

/**
* Checks if the block header is a genesis header.
*/
isGenesis(): boolean {
return this.number.length === 0
}

/**
* Turns the header into the canonical genesis block header.
*/
_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
this.extraData = this._common.genesis().extraData
this.nonce = this._common.genesis().nonce
this.stateRoot = this._common.genesis().stateRoot
this.number = Buffer.from([])
return this.number.equals(toBuffer(0))
}

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/block/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export type BufferLike = Buffer | TransformableToBuffer | PrefixedHexString | nu
/**
* A block header's data.
*/
export interface BlockHeaderData {
export interface HeaderData {
parentHash?: BufferLike
uncleHash?: BufferLike
coinbase?: BufferLike
Expand All @@ -76,9 +76,9 @@ export interface BlockHeaderData {
* A block's data.
*/
export interface BlockData {
header?: Buffer | PrefixedHexString | BufferLike[] | BlockHeaderData
header?: Buffer | PrefixedHexString | BufferLike[] | HeaderData
transactions?: Array<Buffer | PrefixedHexString | BufferLike[] | TxData>
uncleHeaders?: Array<Buffer | PrefixedHexString | BufferLike[] | BlockHeaderData>
uncleHeaders?: Array<Buffer | PrefixedHexString | BufferLike[] | HeaderData>
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the motivation for this interface? Doesn't the toJSON function return an object in this format "automatically" by the structure of the function? Does JsonBlock also have some use case? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: ah, ok, got it, this is used subsequently in the JsonBlock interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was just following the pattern set up in the tx refactor, I'm not entirely sure the specific reason to have them optional instead of required (maybe more valuable in the tx context, or some general rpc thing), but should be fine.


export interface Blockchain {
Expand Down
9 changes: 9 additions & 0 deletions packages/block/src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function checkBufferLength(value: Buffer, expected: number): Buffer {
const provided = value.length
if (provided != expected) {
throw new Error(
`Expected Buffer length for ${value} on initialization is ${expected}, provided: ${provided}`,
)
}
return value
}
Loading