Skip to content

Commit 6822c08

Browse files
authored
Merge pull request #1088 from ethereumjs/client-block-poa-fixes
Client/Block: Clique PoA fixes
2 parents 4cb7fae + 0df1f45 commit 6822c08

File tree

26 files changed

+927
-214
lines changed

26 files changed

+927
-214
lines changed

packages/block/src/block.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ export class Block {
2525
// parse transactions
2626
const transactions = []
2727
for (const txData of txsData || []) {
28-
const tx = Transaction.fromTxData(txData, opts as TxOptions)
28+
const tx = Transaction.fromTxData(txData, {
29+
...opts,
30+
// Use header common in case of hardforkByBlockNumber being activated
31+
common: header._common,
32+
} as TxOptions)
2933
transactions.push(tx)
3034
}
3135

@@ -34,7 +38,10 @@ export class Block {
3438
for (const uhData of uhsData || []) {
3539
const uh = BlockHeader.fromHeaderData(uhData, {
3640
...opts,
37-
// Disable this option here (all other options carried over), since this overwrites the provided Difficulty to an incorrect value
41+
// Use header common in case of hardforkByBlockNumber being activated
42+
common: header._common,
43+
// Disable this option here (all other options carried over), since this overwrites
44+
// the provided Difficulty to an incorrect value
3845
calcDifficultyFromHeader: undefined,
3946
})
4047
uncleHeaders.push(uh)
@@ -65,7 +72,13 @@ export class Block {
6572
// parse transactions
6673
const transactions = []
6774
for (const txData of txsData || []) {
68-
transactions.push(Transaction.fromValuesArray(txData, opts))
75+
transactions.push(
76+
Transaction.fromValuesArray(txData, {
77+
...opts,
78+
// Use header common in case of hardforkByBlockNumber being activated
79+
common: header._common,
80+
})
81+
)
6982
}
7083

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

269285
const validateTxTrie = await this.validateTransactionsTrie()
@@ -358,6 +374,16 @@ export class Block {
358374
}
359375
}
360376

377+
/**
378+
* Internal helper function to create an annotated error message
379+
*
380+
* @param msg Base error message
381+
* @hidden
382+
*/
383+
_error(msg: string) {
384+
return this.header._error(msg)
385+
}
386+
361387
/**
362388
* The following rules are checked in this method:
363389
* Uncle Header is a valid header.

packages/block/src/header.ts

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export class BlockHeader {
4646
public readonly nonce: Buffer
4747

4848
public readonly _common: Common
49+
public _errorPostfix = ''
4950

5051
public static fromHeaderData(headerData: HeaderData = {}, opts?: BlockOptions) {
5152
const {
@@ -172,7 +173,10 @@ export class BlockHeader {
172173
options: BlockOptions = {}
173174
) {
174175
if (options.common) {
175-
this._common = options.common
176+
this._common = Object.assign(
177+
Object.create(Object.getPrototypeOf(options.common)),
178+
options.common
179+
)
176180
} else {
177181
const chain = 'mainnet' // default
178182
if (options.initWithGenesisHeader) {
@@ -245,6 +249,10 @@ export class BlockHeader {
245249
this.extraData = this.cliqueSealBlock(options.cliqueSigner)
246250
}
247251

252+
this._errorPostfix = `block number=${this.number.toNumber()} hash=${this.hash().toString(
253+
'hex'
254+
)}`
255+
248256
const freeze = options?.freeze ?? true
249257
if (freeze) {
250258
Object.freeze(this)
@@ -455,37 +463,37 @@ export class BlockHeader {
455463
if (
456464
this.extraData.length > this._common.paramByHardfork('vm', 'maxExtraDataSize', hardfork)
457465
) {
458-
throw new Error('invalid amount of extra data')
466+
const msg = 'invalid amount of extra data'
467+
throw this._error(msg)
459468
}
460469
} else {
461470
const minLength = CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL
462471
if (!this.cliqueIsEpochTransition()) {
463472
// ExtraData length on epoch transition
464473
if (this.extraData.length !== minLength) {
465-
throw new Error(
466-
`extraData must be ${minLength} bytes on non-epoch transition blocks, received ${this.extraData.length} bytes`
467-
)
474+
const msg = `extraData must be ${minLength} bytes on non-epoch transition blocks, received ${this.extraData.length} bytes`
475+
throw this._error(msg)
468476
}
469477
} else {
470478
const signerLength = this.extraData.length - minLength
471479
if (signerLength % 20 !== 0) {
472-
throw new Error(
473-
`invalid signer list length in extraData, received signer length of ${signerLength} (not divisible by 20)`
474-
)
480+
const msg = `invalid signer list length in extraData, received signer length of ${signerLength} (not divisible by 20)`
481+
throw this._error(msg)
475482
}
476483
// coinbase (beneficiary) on epoch transition
477484
if (!this.coinbase.isZero()) {
478-
throw new Error(
479-
`coinbase must be filled with zeros on epoch transition blocks, received ${this.coinbase.toString()}`
480-
)
485+
const msg = `coinbase must be filled with zeros on epoch transition blocks, received ${this.coinbase.toString()}`
486+
throw this._error(msg)
481487
}
482488
}
483489
// MixHash format
484490
if (!this.mixHash.equals(Buffer.alloc(32))) {
485-
throw new Error(`mixHash must be filled with zeros, received ${this.mixHash}`)
491+
const msg = `mixHash must be filled with zeros, received ${this.mixHash}`
492+
throw this._error(msg)
486493
}
487494
if (!this.validateCliqueDifficulty(blockchain)) {
488-
throw new Error('invalid clique difficulty')
495+
const msg = `invalid clique difficulty`
496+
throw this._error(msg)
489497
}
490498
}
491499

@@ -557,9 +565,6 @@ export class BlockHeader {
557565
* Returns the hash of the block header.
558566
*/
559567
hash(): Buffer {
560-
if (this._common.consensusAlgorithm() === 'clique' && !this.isGenesis()) {
561-
return this.cliqueHash()
562-
}
563568
return rlphash(this.raw())
564569
}
565570

@@ -577,10 +582,9 @@ export class BlockHeader {
577582
}
578583

579584
/**
580-
* Hash for PoA clique blocks is created without the seal.
581-
* @hidden
585+
* PoA clique signature hash without the seal.
582586
*/
583-
private cliqueHash() {
587+
cliqueSigHash() {
584588
const raw = this.raw()
585589
raw[12] = this.extraData.slice(0, this.extraData.length - CLIQUE_EXTRA_SEAL)
586590
return rlphash(raw)
@@ -623,7 +627,7 @@ export class BlockHeader {
623627
*/
624628
private cliqueSealBlock(privateKey: Buffer) {
625629
this._requireClique('cliqueSealBlock')
626-
const signature = ecsign(this.hash(), privateKey)
630+
const signature = ecsign(this.cliqueSigHash(), privateKey)
627631
const signatureB = Buffer.concat([signature.r, signature.s, intToBuffer(signature.v - 27)])
628632

629633
let extraDataWithoutSeal = this.extraData.slice(0, this.extraData.length - CLIQUE_EXTRA_SEAL)
@@ -684,10 +688,14 @@ export class BlockHeader {
684688
cliqueSigner(): Address {
685689
this._requireClique('cliqueSigner')
686690
const extraSeal = this.cliqueExtraSeal()
691+
// Reasonable default for default blocks
692+
if (extraSeal.length === 0) {
693+
return Address.zero()
694+
}
687695
const r = extraSeal.slice(0, 32)
688696
const s = extraSeal.slice(32, 64)
689697
const v = bufferToInt(extraSeal.slice(64, 65)) + 27
690-
const pubKey = ecrecover(this.hash(), v, r, s)
698+
const pubKey = ecrecover(this.cliqueSigHash(), v, r, s)
691699
return Address.fromPublicKey(pubKey)
692700
}
693701

@@ -721,6 +729,18 @@ export class BlockHeader {
721729
}
722730
}
723731

732+
/**
733+
* Internal helper function to create an annotated error message
734+
*
735+
* @param msg Base error message
736+
* @hidden
737+
*/
738+
_error(msg: string) {
739+
msg += ` (${this._errorPostfix})`
740+
const e = new Error(msg)
741+
return e
742+
}
743+
724744
private _getHardfork(): string {
725745
return this._common.hardfork() || this._common.activeHardfork(this.number.toNumber())
726746
}

packages/block/src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export interface BlockOptions {
1313
/**
1414
* A Common object defining the chain and the hardfork a block/block header belongs to.
1515
*
16+
* Object will be internally copied so that tx behavior don't incidentally
17+
* change on future HF changes.
18+
*
1619
* Default: `Common` object set to `mainnet` and the HF currently defined as the default
1720
* hardfork in the `Common` class.
1821
*

packages/block/test/clique.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ tape('[Header]: Clique PoA Functionality', function (t) {
8080
t.test('Signing', function (st) {
8181
const cliqueSigner = A.privateKey
8282

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

92+
header = BlockHeader.fromHeaderData({}, { common })
93+
st.deepEqual(
94+
header.cliqueSigner(),
95+
Address.zero(),
96+
'should return zero address on default block'
97+
)
98+
9299
st.end()
93100
})
94101
})

packages/block/test/header.spec.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { BlockHeader } from '../src/header'
55
import { Block } from '../src'
66
import { Mockchain } from './mockchain'
77
const testData = require('./testdata/testdata.json')
8+
const blocksMainnet = require('./testdata/blocks_mainnet.json')
9+
const blocksGoerli = require('./testdata/blocks_goerli.json')
810

911
tape('[Block]: Header functions', function (t) {
1012
t.test('should create with default constructor', function (st) {
@@ -39,6 +41,14 @@ tape('[Block]: Header functions', function (t) {
3941
const common = new Common({ chain: 'ropsten', hardfork: 'chainstart' })
4042
let header = BlockHeader.genesis(undefined, { common })
4143
st.ok(header.hash().toString('hex'), 'genesis block should initialize')
44+
st.equal(header._common.hardfork(), 'chainstart', 'should initialize with correct HF provided')
45+
46+
common.setHardfork('byzantium')
47+
st.equal(
48+
header._common.hardfork(),
49+
'chainstart',
50+
'should stay on correct HF if outer common HF changes'
51+
)
4252

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

143153
// PoA
@@ -171,9 +181,10 @@ tape('[Block]: Header functions', function (t) {
171181
await header.validate(blockchain)
172182
t.fail(testCase)
173183
} catch (error) {
174-
t.equal(
175-
error.message,
176-
'extraData must be 97 bytes on non-epoch transition blocks, received 32 bytes',
184+
t.ok(
185+
error.message.includes(
186+
'extraData must be 97 bytes on non-epoch transition blocks, received 32 bytes'
187+
),
177188
testCase
178189
)
179190
}
@@ -192,9 +203,10 @@ tape('[Block]: Header functions', function (t) {
192203
await header.validate(blockchain)
193204
st.fail(testCase)
194205
} catch (error) {
195-
st.equals(
196-
error.message,
197-
'invalid signer list length in extraData, received signer length of 41 (not divisible by 20)',
206+
st.ok(
207+
error.message.includes(
208+
'invalid signer list length in extraData, received signer length of 41 (not divisible by 20)'
209+
),
198210
testCase
199211
)
200212
}
@@ -271,7 +283,7 @@ tape('[Block]: Header functions', function (t) {
271283
st.end()
272284
})
273285

274-
t.test('should test validateGasLimit', function (st) {
286+
t.test('should test validateGasLimit()', function (st) {
275287
const testData = require('./testdata/bcBlockGasLimitTest.json').tests
276288
const bcBlockGasLimitTestData = testData.BlockGasLimit2p63m1
277289

@@ -313,4 +325,23 @@ tape('[Block]: Header functions', function (t) {
313325
st.strictEqual(genesis.stateRoot.toString('hex'), ropstenStateRoot, 'genesis stateRoot match')
314326
st.end()
315327
})
328+
329+
t.test('should test hash() function', function (st) {
330+
let common = new Common({ chain: 'mainnet', hardfork: 'chainstart' })
331+
let header = BlockHeader.fromHeaderData(blocksMainnet[0]['header'], { common })
332+
st.equal(
333+
header.hash().toString('hex'),
334+
'88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6',
335+
'correct PoW hash (mainnet block 1)'
336+
)
337+
338+
common = new Common({ chain: 'goerli', hardfork: 'chainstart' })
339+
header = BlockHeader.fromHeaderData(blocksGoerli[0]['header'], { common })
340+
st.equal(
341+
header.hash().toString('hex'),
342+
'8f5bab218b6bb34476f51ca588e9f4553a3a7ce5e13a66c660a5283e97e9a85a',
343+
'correct PoA clique hash (goerli block 1)'
344+
)
345+
st.end()
346+
})
316347
})

0 commit comments

Comments
 (0)