1
+ import { debug as createDebugLogger } from 'debug'
1
2
import Semaphore from 'semaphore-async-await'
2
3
import { Address , BN , rlp } from 'ethereumjs-util'
3
4
import { Block , BlockData , BlockHeader } from '@ethereumjs/block'
@@ -17,6 +18,8 @@ import {
17
18
CLIQUE_NONCE_DROP ,
18
19
} from './clique'
19
20
21
+ const debug = createDebugLogger ( 'blockchain:clique' )
22
+
20
23
import type { LevelUp } from 'levelup'
21
24
const level = require ( 'level-mem' )
22
25
@@ -445,6 +448,7 @@ export default class Blockchain implements BlockchainInterface {
445
448
genesisBlock . header . cliqueEpochTransitionSigners ( ) ,
446
449
]
447
450
await this . cliqueUpdateSignerStates ( genesisSignerState )
451
+ debug ( `[Block 0] Genesis block -> update signer states` )
448
452
await this . cliqueUpdateVotes ( )
449
453
}
450
454
@@ -483,6 +487,12 @@ export default class Blockchain implements BlockchainInterface {
483
487
dbOps . push ( DBOp . set ( DBTarget . CliqueSignerStates , rlp . encode ( formatted ) ) )
484
488
485
489
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
+ }
486
496
}
487
497
488
498
/**
@@ -498,71 +508,115 @@ export default class Blockchain implements BlockchainInterface {
498
508
const nonce = header . nonce
499
509
const latestVote : CliqueVote = [ header . number , [ signer , beneficiary , nonce ] ]
500
510
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
527
515
const lastEpochBlockNumber = header . number . sub (
528
516
header . number . mod ( new BN ( this . _common . consensusConfig ( ) . epoch ) )
529
517
)
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 (
532
525
vote [ 0 ] . gte ( lastEpochBlockNumber ) &&
526
+ ! vote [ 1 ] [ 0 ] . equals ( signer ) &&
533
527
vote [ 1 ] [ 1 ] . equals ( beneficiary ) &&
534
528
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 (
538
564
vote [ 0 ] . gte ( lastEpochBlockNumber ) &&
565
+ ! vote [ 1 ] [ 0 ] . equals ( signer ) &&
539
566
vote [ 1 ] [ 1 ] . equals ( beneficiary ) &&
540
567
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
+ }
546
584
// 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
+ }
547
607
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`
555
611
)
556
612
} 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`
562
615
)
563
616
}
564
617
const newSignerState : CliqueSignerState = [ header . number , activeSigners ]
565
618
await this . cliqueUpdateSignerStates ( newSignerState )
619
+ return
566
620
}
567
621
}
568
622
}
@@ -823,11 +877,7 @@ export default class Blockchain implements BlockchainInterface {
823
877
824
878
if ( this . _validateBlocks && ! isGenesis ) {
825
879
// 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 )
831
881
}
832
882
833
883
if ( this . _validateConsensus ) {
0 commit comments