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

Client/Block: Clique PoA fixes #1088

Merged
merged 18 commits into from
Feb 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b301a51
block -> clique PoA: fixed hash() function, made cliqueSigHash() func…
holgerd77 Feb 8, 2021
6943f0a
blockchain -> PoA: reactivated total difficulty for PoA still being a…
holgerd77 Feb 8, 2021
836c25c
block: added error postfixes to the clique validation checks
holgerd77 Feb 8, 2021
a619dd2
vm, client -> PoA: do not assign block rewards in VM when running PoA…
holgerd77 Feb 8, 2021
08ffcd8
client: added common to ETH/LES protocol messages
holgerd77 Feb 9, 2021
2ea6761
client -> VM execution: fixed HF switch logic and log output
holgerd77 Feb 9, 2021
a738424
block -> error handling: added BlockHeader._error() helper function t…
holgerd77 Feb 9, 2021
264632b
tx: make internal copy of common instance provided to remain stable H…
holgerd77 Feb 9, 2021
b715d53
block: make internal copy of common instance provided to remain stabl…
holgerd77 Feb 9, 2021
1935427
client: create Block and BlockHeader objects with hardforkByBlockNumb…
holgerd77 Feb 9, 2021
8e00fa2
block, blockchain: fixed txs being created with the wrong HF when har…
holgerd77 Feb 9, 2021
010c04d
client -> VM execution: added hardfork to execution failure log output
holgerd77 Feb 9, 2021
67f4dfd
vm: improved early exit debug message in call or create execution
holgerd77 Feb 9, 2021
4fed17c
vm -> debugging: added vm:state,vm:state:root loggers
holgerd77 Feb 10, 2021
95546cf
vm -> debugging: removed state:root logger (verbose), aded vm:block l…
holgerd77 Feb 10, 2021
26274d8
vm -> debugging: logger improvements
holgerd77 Feb 10, 2021
e5d9cde
vm -> clique PoA: use signer for fee attribution, added backwards-com…
holgerd77 Feb 10, 2021
0df1f45
Merge pull request #1089 from ethereumjs/block-tx-copy-common
holgerd77 Feb 11, 2021
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
34 changes: 30 additions & 4 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ export class Block {
// parse transactions
const transactions = []
for (const txData of txsData || []) {
const tx = Transaction.fromTxData(txData, opts as TxOptions)
const tx = Transaction.fromTxData(txData, {
...opts,
// Use header common in case of hardforkByBlockNumber being activated
common: header._common,
} as TxOptions)
transactions.push(tx)
}

Expand All @@ -34,7 +38,10 @@ export class Block {
for (const uhData of uhsData || []) {
const uh = BlockHeader.fromHeaderData(uhData, {
...opts,
// Disable this option here (all other options carried over), since this overwrites the provided Difficulty to an incorrect value
// Use header common in case of hardforkByBlockNumber being activated
common: header._common,
// Disable this option here (all other options carried over), since this overwrites
// the provided Difficulty to an incorrect value
calcDifficultyFromHeader: undefined,
})
uncleHeaders.push(uh)
Expand Down Expand Up @@ -65,7 +72,13 @@ export class Block {
// parse transactions
const transactions = []
for (const txData of txsData || []) {
transactions.push(Transaction.fromValuesArray(txData, opts))
transactions.push(
Transaction.fromValuesArray(txData, {
...opts,
// Use header common in case of hardforkByBlockNumber being activated
common: header._common,
})
)
}

// parse uncle headers
Expand All @@ -74,6 +87,8 @@ export class Block {
uncleHeaders.push(
BlockHeader.fromValuesArray(uncleHeaderData, {
...opts,
// Use header common in case of hardforkByBlockNumber being activated
common: header._common,
// Disable this option here (all other options carried over), since this overwrites the provided Difficulty to an incorrect value
calcDifficultyFromHeader: undefined,
})
Expand Down Expand Up @@ -263,7 +278,8 @@ export class Block {
async validateData(): Promise<void> {
const txErrors = this.validateTransactions(true)
if (txErrors.length > 0) {
throw new Error(`invalid transactions: ${txErrors.join(' ')}`)
const msg = `invalid transactions: ${txErrors.join(' ')}`
throw this.header._error(msg)
}

const validateTxTrie = await this.validateTransactionsTrie()
Expand Down Expand Up @@ -358,6 +374,16 @@ export class Block {
}
}

/**
* Internal helper function to create an annotated error message
*
* @param msg Base error message
* @hidden
*/
_error(msg: string) {
return this.header._error(msg)
}

/**
* The following rules are checked in this method:
* Uncle Header is a valid header.
Expand Down
62 changes: 41 additions & 21 deletions packages/block/src/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class BlockHeader {
public readonly nonce: Buffer

public readonly _common: Common
public _errorPostfix = ''

public static fromHeaderData(headerData: HeaderData = {}, opts?: BlockOptions) {
const {
Expand Down Expand Up @@ -172,7 +173,10 @@ export class BlockHeader {
options: BlockOptions = {}
) {
if (options.common) {
this._common = options.common
this._common = Object.assign(
Object.create(Object.getPrototypeOf(options.common)),
options.common
)
} else {
const chain = 'mainnet' // default
if (options.initWithGenesisHeader) {
Expand Down Expand Up @@ -245,6 +249,10 @@ export class BlockHeader {
this.extraData = this.cliqueSealBlock(options.cliqueSigner)
}

this._errorPostfix = `block number=${this.number.toNumber()} hash=${this.hash().toString(
'hex'
)}`

const freeze = options?.freeze ?? true
if (freeze) {
Object.freeze(this)
Expand Down Expand Up @@ -455,37 +463,37 @@ export class BlockHeader {
if (
this.extraData.length > this._common.paramByHardfork('vm', 'maxExtraDataSize', hardfork)
) {
throw new Error('invalid amount of extra data')
const msg = 'invalid amount of extra data'
throw this._error(msg)
}
} else {
const minLength = CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL
if (!this.cliqueIsEpochTransition()) {
// ExtraData length on epoch transition
if (this.extraData.length !== minLength) {
throw new Error(
`extraData must be ${minLength} bytes on non-epoch transition blocks, received ${this.extraData.length} bytes`
)
const msg = `extraData must be ${minLength} bytes on non-epoch transition blocks, received ${this.extraData.length} bytes`
throw this._error(msg)
}
} else {
const signerLength = this.extraData.length - minLength
if (signerLength % 20 !== 0) {
throw new Error(
`invalid signer list length in extraData, received signer length of ${signerLength} (not divisible by 20)`
)
const msg = `invalid signer list length in extraData, received signer length of ${signerLength} (not divisible by 20)`
throw this._error(msg)
}
// coinbase (beneficiary) on epoch transition
if (!this.coinbase.isZero()) {
throw new Error(
`coinbase must be filled with zeros on epoch transition blocks, received ${this.coinbase.toString()}`
)
const msg = `coinbase must be filled with zeros on epoch transition blocks, received ${this.coinbase.toString()}`
throw this._error(msg)
}
}
// MixHash format
if (!this.mixHash.equals(Buffer.alloc(32))) {
throw new Error(`mixHash must be filled with zeros, received ${this.mixHash}`)
const msg = `mixHash must be filled with zeros, received ${this.mixHash}`
throw this._error(msg)
}
if (!this.validateCliqueDifficulty(blockchain)) {
throw new Error('invalid clique difficulty')
const msg = `invalid clique difficulty`
throw this._error(msg)
}
}

Expand Down Expand Up @@ -557,9 +565,6 @@ export class BlockHeader {
* Returns the hash of the block header.
*/
hash(): Buffer {
if (this._common.consensusAlgorithm() === 'clique' && !this.isGenesis()) {
return this.cliqueHash()
}
return rlphash(this.raw())
}

Expand All @@ -577,10 +582,9 @@ export class BlockHeader {
}

/**
* Hash for PoA clique blocks is created without the seal.
* @hidden
* PoA clique signature hash without the seal.
*/
private cliqueHash() {
cliqueSigHash() {
const raw = this.raw()
raw[12] = this.extraData.slice(0, this.extraData.length - CLIQUE_EXTRA_SEAL)
return rlphash(raw)
Expand Down Expand Up @@ -623,7 +627,7 @@ export class BlockHeader {
*/
private cliqueSealBlock(privateKey: Buffer) {
this._requireClique('cliqueSealBlock')
const signature = ecsign(this.hash(), privateKey)
const signature = ecsign(this.cliqueSigHash(), privateKey)
const signatureB = Buffer.concat([signature.r, signature.s, intToBuffer(signature.v - 27)])

let extraDataWithoutSeal = this.extraData.slice(0, this.extraData.length - CLIQUE_EXTRA_SEAL)
Expand Down Expand Up @@ -684,10 +688,14 @@ export class BlockHeader {
cliqueSigner(): Address {
this._requireClique('cliqueSigner')
const extraSeal = this.cliqueExtraSeal()
// Reasonable default for default blocks
if (extraSeal.length === 0) {
return Address.zero()
}
const r = extraSeal.slice(0, 32)
const s = extraSeal.slice(32, 64)
const v = bufferToInt(extraSeal.slice(64, 65)) + 27
const pubKey = ecrecover(this.hash(), v, r, s)
const pubKey = ecrecover(this.cliqueSigHash(), v, r, s)
return Address.fromPublicKey(pubKey)
}

Expand Down Expand Up @@ -721,6 +729,18 @@ export class BlockHeader {
}
}

/**
* Internal helper function to create an annotated error message
*
* @param msg Base error message
* @hidden
*/
_error(msg: string) {
msg += ` (${this._errorPostfix})`
const e = new Error(msg)
return e
}

private _getHardfork(): string {
return this._common.hardfork() || this._common.activeHardfork(this.number.toNumber())
}
Expand Down
3 changes: 3 additions & 0 deletions packages/block/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface BlockOptions {
/**
* A Common object defining the chain and the hardfork a block/block header belongs to.
*
* Object will be internally copied so that tx behavior don't incidentally
* change on future HF changes.
*
* Default: `Common` object set to `mainnet` and the HF currently defined as the default
* hardfork in the `Common` class.
*
Expand Down
9 changes: 8 additions & 1 deletion packages/block/test/clique.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ tape('[Header]: Clique PoA Functionality', function (t) {
t.test('Signing', function (st) {
const cliqueSigner = A.privateKey

const header = BlockHeader.fromHeaderData(
let header = BlockHeader.fromHeaderData(
{ number: 1, extraData: Buffer.alloc(97) },
{ common, freeze: false, cliqueSigner }
)
Expand All @@ -89,6 +89,13 @@ tape('[Header]: Clique PoA Functionality', function (t) {
st.ok(header.cliqueVerifySignature([A.address]), 'should verify signature')
st.ok(header.cliqueSigner().equals(A.address), 'should recover the correct signer address')

header = BlockHeader.fromHeaderData({}, { common })
st.deepEqual(
header.cliqueSigner(),
Address.zero(),
'should return zero address on default block'
)

st.end()
})
})
47 changes: 39 additions & 8 deletions packages/block/test/header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { BlockHeader } from '../src/header'
import { Block } from '../src'
import { Mockchain } from './mockchain'
const testData = require('./testdata/testdata.json')
const blocksMainnet = require('./testdata/blocks_mainnet.json')
const blocksGoerli = require('./testdata/blocks_goerli.json')

tape('[Block]: Header functions', function (t) {
t.test('should create with default constructor', function (st) {
Expand Down Expand Up @@ -39,6 +41,14 @@ tape('[Block]: Header functions', function (t) {
const common = new Common({ chain: 'ropsten', hardfork: 'chainstart' })
let header = BlockHeader.genesis(undefined, { common })
st.ok(header.hash().toString('hex'), 'genesis block should initialize')
st.equal(header._common.hardfork(), 'chainstart', 'should initialize with correct HF provided')

common.setHardfork('byzantium')
st.equal(
header._common.hardfork(),
'chainstart',
'should stay on correct HF if outer common HF changes'
)

header = BlockHeader.fromHeaderData({}, { common })
st.ok(header.hash().toString('hex'), 'default block should initialize')
Expand Down Expand Up @@ -137,7 +147,7 @@ tape('[Block]: Header functions', function (t) {
await header.validate(blockchain)
st.fail(testCase)
} catch (error) {
st.equal(error.message, 'invalid amount of extra data', testCase)
st.ok(error.message.includes('invalid amount of extra data'), testCase)
}

// PoA
Expand Down Expand Up @@ -171,9 +181,10 @@ tape('[Block]: Header functions', function (t) {
await header.validate(blockchain)
t.fail(testCase)
} catch (error) {
t.equal(
error.message,
'extraData must be 97 bytes on non-epoch transition blocks, received 32 bytes',
t.ok(
error.message.includes(
'extraData must be 97 bytes on non-epoch transition blocks, received 32 bytes'
),
testCase
)
}
Expand All @@ -192,9 +203,10 @@ tape('[Block]: Header functions', function (t) {
await header.validate(blockchain)
st.fail(testCase)
} catch (error) {
st.equals(
error.message,
'invalid signer list length in extraData, received signer length of 41 (not divisible by 20)',
st.ok(
error.message.includes(
'invalid signer list length in extraData, received signer length of 41 (not divisible by 20)'
),
testCase
)
}
Expand Down Expand Up @@ -271,7 +283,7 @@ tape('[Block]: Header functions', function (t) {
st.end()
})

t.test('should test validateGasLimit', function (st) {
t.test('should test validateGasLimit()', function (st) {
const testData = require('./testdata/bcBlockGasLimitTest.json').tests
const bcBlockGasLimitTestData = testData.BlockGasLimit2p63m1

Expand Down Expand Up @@ -313,4 +325,23 @@ tape('[Block]: Header functions', function (t) {
st.strictEqual(genesis.stateRoot.toString('hex'), ropstenStateRoot, 'genesis stateRoot match')
st.end()
})

t.test('should test hash() function', function (st) {
let common = new Common({ chain: 'mainnet', hardfork: 'chainstart' })
let header = BlockHeader.fromHeaderData(blocksMainnet[0]['header'], { common })
st.equal(
header.hash().toString('hex'),
'88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6',
'correct PoW hash (mainnet block 1)'
)

common = new Common({ chain: 'goerli', hardfork: 'chainstart' })
header = BlockHeader.fromHeaderData(blocksGoerli[0]['header'], { common })
st.equal(
header.hash().toString('hex'),
'8f5bab218b6bb34476f51ca588e9f4553a3a7ce5e13a66c660a5283e97e9a85a',
'correct PoA clique hash (goerli block 1)'
)
st.end()
})
})
Loading