Skip to content

Commit 761bef0

Browse files
authored
Merge pull request #1103 from ethereumjs/fix-goerli-clique-difficulty-bug
Client, Blockchain: Fix Goerli Clique Difficulty Bug
2 parents 0dda417 + b5f0ca9 commit 761bef0

File tree

8 files changed

+151
-75
lines changed

8 files changed

+151
-75
lines changed

packages/blockchain/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ blockchain.iterator('i', (block) => {
4646

4747
[Documentation](./docs/README.md)
4848

49+
# DEVELOPER
50+
51+
For debugging blockchain control flows the [debug](https://github.com/visionmedia/debug) library is used and can be activated on the CL with `DEBUG=[Logger Selection] node [Your Script to Run].js`.
52+
53+
The following initial logger is currently available:
54+
55+
| Logger | Description |
56+
| - | - |
57+
| `blockchain:clique` | Clique operations like updating the vote and/or signer list |
58+
59+
The following is an example for a logger run:
60+
61+
Run with the clique logger:
62+
63+
```shell
64+
DEBUG=blockchain:clique ts-node test.ts
65+
```
66+
4967
# EthereumJS
5068

5169
See our organizational [documentation](https://ethereumjs.readthedocs.io) for an introduction to `EthereumJS` as well as information on current standards and best practices.

packages/blockchain/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@ethereumjs/block": "^3.0.0",
4040
"@ethereumjs/common": "^2.0.0",
4141
"@ethereumjs/ethash": "^1.0.0",
42+
"debug": "^2.2.0",
4243
"ethereumjs-util": "^7.0.8",
4344
"level-mem": "^5.0.1",
4445
"lru-cache": "^5.1.1",

packages/blockchain/src/index.ts

Lines changed: 103 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { debug as createDebugLogger } from 'debug'
12
import Semaphore from 'semaphore-async-await'
23
import { Address, BN, rlp } from 'ethereumjs-util'
34
import { Block, BlockData, BlockHeader } from '@ethereumjs/block'
@@ -17,6 +18,8 @@ import {
1718
CLIQUE_NONCE_DROP,
1819
} from './clique'
1920

21+
const debug = createDebugLogger('blockchain:clique')
22+
2023
import type { LevelUp } from 'levelup'
2124
const level = require('level-mem')
2225

@@ -445,6 +448,7 @@ export default class Blockchain implements BlockchainInterface {
445448
genesisBlock.header.cliqueEpochTransitionSigners(),
446449
]
447450
await this.cliqueUpdateSignerStates(genesisSignerState)
451+
debug(`[Block 0] Genesis block -> update signer states`)
448452
await this.cliqueUpdateVotes()
449453
}
450454

@@ -483,6 +487,12 @@ export default class Blockchain implements BlockchainInterface {
483487
dbOps.push(DBOp.set(DBTarget.CliqueSignerStates, rlp.encode(formatted)))
484488

485489
await this.dbManager.batch(dbOps)
490+
// Output active signers for debugging purposes
491+
let i = 0
492+
for (const signer of this.cliqueActiveSigners()) {
493+
debug(`Clique signer [${i}]: ${signer}`)
494+
i++
495+
}
486496
}
487497

488498
/**
@@ -498,71 +508,115 @@ export default class Blockchain implements BlockchainInterface {
498508
const nonce = header.nonce
499509
const latestVote: CliqueVote = [header.number, [signer, beneficiary, nonce]]
500510

501-
const alreadyVoted = this._cliqueLatestVotes.find((vote) => {
502-
return (
503-
vote[1][0].equals(signer) && vote[1][1].equals(beneficiary) && vote[1][2].equals(nonce)
504-
)
505-
})
506-
? true
507-
: false
508-
509-
// Always add the latest vote to the history no matter if already voted
510-
// the same vote or not
511-
this._cliqueLatestVotes.push(latestVote)
512-
513-
// remove any opposite votes for [signer, beneficiary]
514-
const oppositeNonce = nonce.equals(CLIQUE_NONCE_AUTH) ? CLIQUE_NONCE_DROP : CLIQUE_NONCE_AUTH
515-
this._cliqueLatestVotes = this._cliqueLatestVotes.filter(
516-
(vote) =>
517-
!(
518-
vote[1][0].equals(signer) &&
519-
vote[1][1].equals(beneficiary) &&
520-
vote[1][2].equals(oppositeNonce)
521-
)
522-
)
523-
524-
// If same vote not already in history see if there is a new majority consensus
525-
// to update the signer list
526-
if (!alreadyVoted) {
511+
// Do two rounds here, one to execute on a potential previously reached consensus
512+
// on the newly touched beneficiary, one with the added new vote
513+
for (let round = 1; round <= 2; round++) {
514+
// See if there is a new majority consensus to update the signer list
527515
const lastEpochBlockNumber = header.number.sub(
528516
header.number.mod(new BN(this._common.consensusConfig().epoch))
529517
)
530-
const beneficiaryVotesAuth = this._cliqueLatestVotes.filter(
531-
(vote) =>
518+
const limit = this.cliqueSignerLimit()
519+
let activeSigners = this.cliqueActiveSigners()
520+
let consensus = false
521+
522+
// AUTH vote analysis
523+
let votes = this._cliqueLatestVotes.filter((vote) => {
524+
return (
532525
vote[0].gte(lastEpochBlockNumber) &&
526+
!vote[1][0].equals(signer) &&
533527
vote[1][1].equals(beneficiary) &&
534528
vote[1][2].equals(CLIQUE_NONCE_AUTH)
535-
)
536-
const beneficiaryVotesDrop = this._cliqueLatestVotes.filter(
537-
(vote) =>
529+
)
530+
})
531+
const beneficiaryVotesAUTH: Address[] = []
532+
for (const vote of votes) {
533+
const num = beneficiaryVotesAUTH.filter((voteCMP) => {
534+
return voteCMP.equals(vote[1][0])
535+
}).length
536+
if (num === 0) {
537+
beneficiaryVotesAUTH.push(vote[1][0])
538+
}
539+
}
540+
let numBeneficiaryVotesAUTH = beneficiaryVotesAUTH.length
541+
if (round === 2 && nonce.equals(CLIQUE_NONCE_AUTH)) {
542+
numBeneficiaryVotesAUTH += 1
543+
}
544+
// Majority consensus
545+
if (numBeneficiaryVotesAUTH >= limit) {
546+
consensus = true
547+
// Authorize new signer
548+
activeSigners.push(beneficiary)
549+
activeSigners.sort((a, b) => {
550+
// Sort by buffer size
551+
return a.toBuffer().compare(b.toBuffer())
552+
})
553+
// Discard votes for added signer
554+
this._cliqueLatestVotes = this._cliqueLatestVotes.filter(
555+
(vote) => !vote[1][1].equals(beneficiary)
556+
)
557+
debug(
558+
`[Block ${header.number.toNumber()}] Clique majority consensus (AUTH ${beneficiary})`
559+
)
560+
}
561+
// DROP vote
562+
votes = this._cliqueLatestVotes.filter((vote) => {
563+
return (
538564
vote[0].gte(lastEpochBlockNumber) &&
565+
!vote[1][0].equals(signer) &&
539566
vote[1][1].equals(beneficiary) &&
540567
vote[1][2].equals(CLIQUE_NONCE_DROP)
541-
)
542-
const limit = this.cliqueSignerLimit()
543-
const consensus =
544-
beneficiaryVotesAuth.length >= limit || beneficiaryVotesDrop.length >= limit
545-
const auth = beneficiaryVotesAuth.length >= limit
568+
)
569+
})
570+
const beneficiaryVotesDROP: Address[] = []
571+
for (const vote of votes) {
572+
const num = beneficiaryVotesDROP.filter((voteCMP) => {
573+
return voteCMP.equals(vote[1][0])
574+
}).length
575+
if (num === 0) {
576+
beneficiaryVotesDROP.push(vote[1][0])
577+
}
578+
}
579+
let numBeneficiaryVotesDROP = beneficiaryVotesDROP.length
580+
581+
if (round === 2 && nonce.equals(CLIQUE_NONCE_DROP)) {
582+
numBeneficiaryVotesDROP += 1
583+
}
546584
// Majority consensus
585+
if (numBeneficiaryVotesDROP >= limit) {
586+
consensus = true
587+
// Drop signer
588+
activeSigners = activeSigners.filter((signer) => !signer.equals(beneficiary))
589+
this._cliqueLatestVotes = this._cliqueLatestVotes.filter(
590+
// Discard votes from removed signer and for removed signer
591+
(vote) => !vote[1][0].equals(beneficiary) && !vote[1][1].equals(beneficiary)
592+
)
593+
debug(
594+
`[Block ${header.number.toNumber()}] Clique majority consensus (DROP ${beneficiary})`
595+
)
596+
}
597+
if (round === 1) {
598+
// Always add the latest vote to the history no matter if already voted
599+
// the same vote or not
600+
this._cliqueLatestVotes.push(latestVote)
601+
debug(
602+
`[Block ${header.number.toNumber()}] New clique vote: ${signer} -> ${beneficiary} ${
603+
nonce.equals(CLIQUE_NONCE_AUTH) ? 'AUTH' : 'DROP'
604+
}`
605+
)
606+
}
547607
if (consensus) {
548-
let activeSigners = this.cliqueActiveSigners()
549-
if (auth) {
550-
// Authorize new signer
551-
activeSigners.push(beneficiary)
552-
// Discard votes for added signer
553-
this._cliqueLatestVotes = this._cliqueLatestVotes.filter(
554-
(vote) => !vote[1][1].equals(beneficiary)
608+
if (round === 1) {
609+
debug(
610+
`[Block ${header.number.toNumber()}] Clique majority consensus on existing votes -> update signer states`
555611
)
556612
} else {
557-
// Drop signer
558-
activeSigners = activeSigners.filter((signer) => !signer.equals(beneficiary))
559-
// Discard votes from removed signer
560-
this._cliqueLatestVotes = this._cliqueLatestVotes.filter(
561-
(vote) => !vote[1][0].equals(beneficiary)
613+
debug(
614+
`[Block ${header.number.toNumber()}] Clique majority consensus on new vote -> update signer states`
562615
)
563616
}
564617
const newSignerState: CliqueSignerState = [header.number, activeSigners]
565618
await this.cliqueUpdateSignerStates(newSignerState)
619+
return
566620
}
567621
}
568622
}
@@ -823,11 +877,7 @@ export default class Blockchain implements BlockchainInterface {
823877

824878
if (this._validateBlocks && !isGenesis) {
825879
// this calls into `getBlock`, which is why we cannot lock yet
826-
if (item instanceof BlockHeader) {
827-
await block.header.validate(this)
828-
} else {
829-
await block.validate(this)
830-
}
880+
await block.validate(this)
831881
}
832882

833883
if (this._validateConsensus) {

packages/blockchain/test/clique.spec.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,61 +44,61 @@ tape('Clique: Initialization', (t) => {
4444
}
4545

4646
const B: Signer = {
47-
address: new Address(Buffer.from('dc7bc81ddf67d037d7439f8e6ff12f3d2a100f71', 'hex')),
47+
address: new Address(Buffer.from('6f62d8382bf2587361db73ceca28be91b2acb6df', 'hex')),
4848
privateKey: Buffer.from(
49-
'86b0ff7b6cf70786f29f297c57562905ab0b6c32d69e177a46491e56da9e486e',
49+
'2a6e9ad5a6a8e4f17149b8bc7128bf090566a11dbd63c30e5a0ee9f161309cd6',
5050
'hex'
5151
),
5252
publicKey: Buffer.from(
53-
'd3e3d2b722e325bfc085ff5638a112b4e7e88ff13f92fc7f6cfc14b5a25e8d1545a2f27d8537b96e8919949d5f8c139ae7fc81aea7cf7fe5d43d7faaa038e35b',
53+
'ca0a55f6e81cb897aee6a1c390aa83435c41048faa0564b226cfc9f3df48b73e846377fb0fd606df073addc7bd851f22547afbbdd5c3b028c91399df802083a2',
5454
'hex'
5555
),
5656
}
5757

5858
const C: Signer = {
59-
address: new Address(Buffer.from('8458f408106c4875c96679f3f556a511beabe138', 'hex')),
59+
address: new Address(Buffer.from('83c30730d1972baa09765a1ac72a43db27fedce5', 'hex')),
6060
privateKey: Buffer.from(
61-
'159e95d07a6c64ddbafa6036cdb7b8114e6e8cdc449ca4b0468a6d0c955f991b',
61+
'f216ddcf276079043c52b5dd144aa073e6b272ad4bfeaf4fbbc044aa478d1927',
6262
'hex'
6363
),
6464
publicKey: Buffer.from(
65-
'f02724341e2df54cf53515f079b1354fa8d437e79c5b091b8d8cc7cbcca00fd8ad854cb3b3a85b06c44ecb7269404a67be88b561f2224c94d133e5fc21be915c',
65+
'555b19a5cbe6dd082a4a1e1e0520dd52a82ba24fd5598ea31f0f31666c40905ed319314c5fb06d887b760229e1c0e616294e7b1cb5dfefb71507c9112132ce56',
6666
'hex'
6767
),
6868
}
6969

7070
const D: Signer = {
71-
address: new Address(Buffer.from('83c30730d1972baa09765a1ac72a43db27fedce5', 'hex')),
71+
address: new Address(Buffer.from('8458f408106c4875c96679f3f556a511beabe138', 'hex')),
7272
privateKey: Buffer.from(
73-
'f216ddcf276079043c52b5dd144aa073e6b272ad4bfeaf4fbbc044aa478d1927',
73+
'159e95d07a6c64ddbafa6036cdb7b8114e6e8cdc449ca4b0468a6d0c955f991b',
7474
'hex'
7575
),
7676
publicKey: Buffer.from(
77-
'555b19a5cbe6dd082a4a1e1e0520dd52a82ba24fd5598ea31f0f31666c40905ed319314c5fb06d887b760229e1c0e616294e7b1cb5dfefb71507c9112132ce56',
77+
'f02724341e2df54cf53515f079b1354fa8d437e79c5b091b8d8cc7cbcca00fd8ad854cb3b3a85b06c44ecb7269404a67be88b561f2224c94d133e5fc21be915c',
7878
'hex'
7979
),
8080
}
8181

8282
const E: Signer = {
83-
address: new Address(Buffer.from('6f62d8382bf2587361db73ceca28be91b2acb6df', 'hex')),
83+
address: new Address(Buffer.from('ab80a948c661aa32d09952d2a6c4ad77a4c947be', 'hex')),
8484
privateKey: Buffer.from(
85-
'2a6e9ad5a6a8e4f17149b8bc7128bf090566a11dbd63c30e5a0ee9f161309cd6',
85+
'48ec5a6c4a7fc67b10a9d4c8a8f594a81ae42e41ed061fa5218d96abb6012344',
8686
'hex'
8787
),
8888
publicKey: Buffer.from(
89-
'ca0a55f6e81cb897aee6a1c390aa83435c41048faa0564b226cfc9f3df48b73e846377fb0fd606df073addc7bd851f22547afbbdd5c3b028c91399df802083a2',
89+
'adefb82b9f54e80aa3532263e4478739de16fcca6828f4ae842f8a07941c347fa59d2da1300569237009f0f122dc1fd6abb0db8fcb534280aa94948a5cc95f94',
9090
'hex'
9191
),
9292
}
9393

9494
const F: Signer = {
95-
address: new Address(Buffer.from('ab80a948c661aa32d09952d2a6c4ad77a4c947be', 'hex')),
95+
address: new Address(Buffer.from('dc7bc81ddf67d037d7439f8e6ff12f3d2a100f71', 'hex')),
9696
privateKey: Buffer.from(
97-
'48ec5a6c4a7fc67b10a9d4c8a8f594a81ae42e41ed061fa5218d96abb6012344',
97+
'86b0ff7b6cf70786f29f297c57562905ab0b6c32d69e177a46491e56da9e486e',
9898
'hex'
9999
),
100100
publicKey: Buffer.from(
101-
'adefb82b9f54e80aa3532263e4478739de16fcca6828f4ae842f8a07941c347fa59d2da1300569237009f0f122dc1fd6abb0db8fcb534280aa94948a5cc95f94',
101+
'd3e3d2b722e325bfc085ff5638a112b4e7e88ff13f92fc7f6cfc14b5a25e8d1545a2f27d8537b96e8919949d5f8c139ae7fc81aea7cf7fe5d43d7faaa038e35b',
102102
'hex'
103103
),
104104
}
@@ -384,7 +384,7 @@ tape('Clique: Initialization', (t) => {
384384
await addNextBlock(blockchain, blocks, A)
385385
await addNextBlock(blockchain, blocks, B, [C, true])
386386

387-
st.deepEqual(blockchain.cliqueActiveSigners(), [A.address, B.address, D.address, C.address])
387+
st.deepEqual(blockchain.cliqueActiveSigners(), [A.address, B.address, C.address, D.address])
388388
st.end()
389389
})
390390

packages/blockchain/test/reorg.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ tape('reorg tests', (t) => {
177177

178178
let signerStates = (blockchain as any)._cliqueLatestSignerStates
179179
t.ok(
180-
!signerStates.find((s: any) => s[0].eqn(2) && s[1][1].equals(beneficiary1)),
180+
!signerStates.find(
181+
(s: any) => s[0].eqn(2) && s[1].find((a: Address) => a.equals(beneficiary1))
182+
),
181183
'should not find reorged signer state'
182184
)
183185

@@ -203,7 +205,9 @@ tape('reorg tests', (t) => {
203205

204206
signerStates = (blockchain as any)._cliqueLatestSignerStates
205207
t.ok(
206-
!!signerStates.find((s: any) => s[0].eqn(2) && s[1][1].equals(beneficiary2)),
208+
!!signerStates.find(
209+
(s: any) => s[0].eqn(2) && s[1].find((a: Address) => a.equals(beneficiary2))
210+
),
207211
'should find reorged signer state'
208212
)
209213

packages/client/lib/service/ethereumservice.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface EthereumServiceOptions extends ServiceOptions {
1717
/* Sync retry interval in ms (default: 8000) */
1818
interval?: number
1919

20-
/* Protocol timeout in ms (default: 4000) */
20+
/* Protocol timeout in ms (default: 6000) */
2121
timeout?: number
2222
}
2323

@@ -42,7 +42,7 @@ export class EthereumService extends Service {
4242
this.flow = new FlowControl()
4343
this.chain = options.chain ?? new Chain(options)
4444
this.interval = options.interval ?? 8000
45-
this.timeout = options.timeout ?? 4000
45+
this.timeout = options.timeout ?? 6000
4646
}
4747

4848
/**

packages/client/lib/sync/execution/vmexecution.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,10 @@ export class VMExecution extends Execution {
115115
this.hardfork = this.config.execCommon.setHardforkByBlockNumber(blockNumber)
116116
this.vm._updateOpcodes()
117117
}
118-
await this.vm.runBlock({ block, root: parentState })
118+
// Block validation is redundant here and leads to consistency problems
119+
// on PoA clique along blockchain-including validation checks
120+
// (signer states might have moved on when sync is ahead)
121+
await this.vm.runBlock({ block, root: parentState, skipBlockValidation: true })
119122
txCounter += block.transactions.length
120123
// set as new head block
121124
headBlock = block

packages/vm/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ The following loggers are currently available:
177177

178178
| Logger | Description |
179179
| - | - |
180-
| `vm:block` | Block operations (run txs, generating receipts, block rewards,...)
180+
| `vm:block` | Block operations (run txs, generating receipts, block rewards,...) |
181181
| `vm:tx` | Transaction operations (account updates, checkpointing,...) |
182182
| `vm:tx:gas` | Transaction gas logger |
183183
| `vm:evm` | EVM control flow, CALL or CREATE message execution |

0 commit comments

Comments
 (0)