From 0ded664f0c5403efc453853a096fad206bca0e45 Mon Sep 17 00:00:00 2001 From: Jerome Date: Sun, 3 Apr 2022 12:45:25 +1000 Subject: [PATCH] add highestSelfMinedRound to make sure we mine once per round (#79) --- consensus/XDPoS/engines/engine_v2/engine.go | 28 ++++++++++++++------ consensus/XDPoS/engines/engine_v2/mining.go | 11 +++++--- consensus/XDPoS/utils/errors.go | 2 ++ consensus/tests/engine_v2_tests/mine_test.go | 15 +++++++++++ 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index eab5ea2d7a..adf6d02a50 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -44,11 +44,12 @@ type XDPoS_v2 struct { timeoutWorker *countdown.CountdownTimer // Timer to generate broadcast timeout msg if threashold reached timeoutCount int // number of timeout being sent - timeoutPool *utils.Pool - votePool *utils.Pool - currentRound utils.Round - highestVotedRound utils.Round - highestQuorumCert *utils.QuorumCert + timeoutPool *utils.Pool + votePool *utils.Pool + currentRound utils.Round + highestSelfMinedRound utils.Round + highestVotedRound utils.Round + highestQuorumCert *utils.QuorumCert // lockQuorumCert in XDPoS Consensus 2.0, used in voting rule lockQuorumCert *utils.QuorumCert highestTimeoutCert *utils.TimeoutCert @@ -87,6 +88,8 @@ func New(config *params.XDPoSConfig, db ethdb.Database, waitPeriodCh chan int) * timeoutPool: timeoutPool, votePool: votePool, + highestSelfMinedRound: utils.Round(0), + highestTimeoutCert: &utils.TimeoutCert{ Round: utils.Round(0), Signatures: []utils.Signature{}, @@ -225,10 +228,10 @@ func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, s round := x.currentRound isMyTurn, err := x.yourturn(chain, round, parent, signer) if err != nil { - log.Error("[Yourturn] Error while checking if i am qualified to mine", "round", round, "error", err) + log.Warn("[Yourturn] Error while checking if i am qualified to mine", "round", round, "error", err) } - return isMyTurn, nil + return isMyTurn, err } // Prepare implements consensus.Engine, preparing all the consensus fields of the @@ -272,7 +275,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er isMyTurn, err := x.yourturn(chain, currentRound, parent, signer) if err != nil { - log.Error("[Prepare] Error while checking if it's still my turn to mine", "round", currentRound, "ParentHash", parent.Hash().Hex(), "ParentNumber", parent.Number.Uint64(), "error", err) + log.Error("[Prepare] Error while checking if it's still my turn to mine", "currentRound", currentRound, "ParentHash", parent.Hash().Hex(), "ParentNumber", parent.Number.Uint64(), "error", err) return err } if !isMyTurn { @@ -395,6 +398,15 @@ func (x *XDPoS_v2) Seal(chain consensus.ChainReader, block *types.Block, stop <- } header.Validator = signature + // Mark the highestSelfMinedRound to make sure we only mine once per round + var decodedExtraField utils.ExtraFields_v2 + err = utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField) + if err != nil { + log.Error("[Seal] Error when decode extra field to get the round number from v2 block during sealing", "Hash", header.Hash().Hex(), "Number", header.Number.Uint64(), "Error", err) + return nil, err + } + x.highestSelfMinedRound = decodedExtraField.Round + return block.WithSeal(header), nil } diff --git a/consensus/XDPoS/engines/engine_v2/mining.go b/consensus/XDPoS/engines/engine_v2/mining.go index abb1d36cd0..b672780ce2 100644 --- a/consensus/XDPoS/engines/engine_v2/mining.go +++ b/consensus/XDPoS/engines/engine_v2/mining.go @@ -13,6 +13,11 @@ import ( // Using parent and current round to find the finalised master node list(with penalties applied from last epoch) func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round utils.Round, parent *types.Header, signer common.Address) (bool, error) { + if round <= x.highestSelfMinedRound { + log.Warn("[yourturn] Already mined on this round", "Round", round, "highestSelfMinedRound", x.highestSelfMinedRound, "ParentHash", parent.Hash().Hex(), "ParentNumber", parent.Number) + return false, utils.ErrAlreadyMined + } + isEpochSwitch, _, err := x.isEpochSwitchAtRound(round, parent) if err != nil { log.Error("[yourturn] check epoch switch at round failed", "Error", err) @@ -46,17 +51,17 @@ func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round utils.Round, pare curIndex := utils.Position(masterNodes, signer) if curIndex == -1 { - log.Debug("[yourturn] Not authorised signer", "MN", masterNodes, "Hash", parent.Hash(), "signer", signer) + log.Warn("[yourturn] Not authorised signer", "MN", masterNodes, "Hash", parent.Hash(), "signer", signer) return false, nil } for i, s := range masterNodes { - log.Debug("[yourturn] Masternode:", "index", i, "address", s.String(), "parentBlockNum", parent.Number) + log.Warn("[yourturn] Masternode:", "index", i, "address", s.String(), "parentBlockNum", parent.Number) } leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes)) if masterNodes[leaderIndex] != signer { - log.Debug("[yourturn] Not my turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", parent.Hash().Hex(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "signer", signer) + log.Warn("[yourturn] Not my turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", parent.Hash().Hex(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "signer", signer) return false, nil } diff --git a/consensus/XDPoS/utils/errors.go b/consensus/XDPoS/utils/errors.go index 0aa3d7e70e..8ebd5438fd 100644 --- a/consensus/XDPoS/utils/errors.go +++ b/consensus/XDPoS/utils/errors.go @@ -93,6 +93,8 @@ var ( ErrPenaltyListDoesNotMatch = errors.New("Incoming block penalty list does not match") ErrRoundInvalid = errors.New("Invalid Round, it shall be bigger than QC round") + + ErrAlreadyMined = errors.New("Already mined") ) type ErrIncomingMessageRoundNotEqualCurrentRound struct { diff --git a/consensus/tests/engine_v2_tests/mine_test.go b/consensus/tests/engine_v2_tests/mine_test.go index 55ab880b8b..902c1a3900 100644 --- a/consensus/tests/engine_v2_tests/mine_test.go +++ b/consensus/tests/engine_v2_tests/mine_test.go @@ -60,6 +60,21 @@ func TestYourTurnInitialV2(t *testing.T) { } } +func TestShouldMineOncePerRound(t *testing.T) { + config := params.TestXDPoSMockChainConfig + blockchain, _, block910, signer, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, config, 0) + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + minePeriod := config.XDPoS.V2.MinePeriod + + // Make sure we seal the parentBlock 910 + _, err := adaptor.Seal(blockchain, block910, nil) + assert.Nil(t, err) + time.Sleep(time.Duration(minePeriod) * time.Second) + b, err := adaptor.YourTurn(blockchain, block910.Header(), signer) + assert.False(t, b) + assert.Equal(t, utils.ErrAlreadyMined, err) +} + func TestUpdateMasterNodes(t *testing.T) { config := params.TestXDPoSMockChainConfig blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch+config.XDPoS.Gap)-1, config, 0)