From b98005a8dd0b04bd8d92bad815bf4e3306d8c84d Mon Sep 17 00:00:00 2001 From: Jerome Date: Sun, 27 Mar 2022 20:39:40 +1100 Subject: [PATCH] Xin 166 (#75) * typo and checkYourturnWithinFinalisedMasternodes func name to yourturn * remove redundant code from verifyQC * Verify QC to optionally pass parent header. This is used to help verifyHeaders * move difficulty into its own file --- .../XDPoS/engines/engine_v2/difficulty.go | 14 ++++++++ consensus/XDPoS/engines/engine_v2/engine.go | 28 +++++---------- consensus/XDPoS/engines/engine_v2/mining.go | 18 +++++----- .../XDPoS/engines/engine_v2/verifyHeader.go | 34 +++++++++---------- .../engine_v2_tests/verify_header_test.go | 34 +++++++++++++++++++ 5 files changed, 82 insertions(+), 46 deletions(-) create mode 100644 consensus/XDPoS/engines/engine_v2/difficulty.go diff --git a/consensus/XDPoS/engines/engine_v2/difficulty.go b/consensus/XDPoS/engines/engine_v2/difficulty.go new file mode 100644 index 0000000000..342afa7219 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2/difficulty.go @@ -0,0 +1,14 @@ +package engine_v2 + +import ( + "math/big" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/core/types" +) + +// TODO: what should be new difficulty +func (x *XDPoS_v2) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int { + return big.NewInt(1) +} diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 6a01b89eba..bd905e6b9e 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -143,7 +143,7 @@ func (x *XDPoS_v2) Initial(chain consensus.ChainReader, header *types.Header) er quorumCert = &utils.QuorumCert{ ProposedBlockInfo: blockInfo, Signatures: nil, - GapNumber: header.Number.Uint64()-x.config.Gap, + GapNumber: header.Number.Uint64() - x.config.Gap, } // can not call processQC because round is equal to default @@ -201,7 +201,7 @@ func (x *XDPoS_v2) Initial(chain consensus.ChainReader, header *types.Header) er return nil } -// Check if it's my turm to mine a block. Note: The second return value `preIndex` is useless in V2 engine +// Check if it's my turn to mine a block. Note: The second return value `preIndex` is useless in V2 engine func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) { x.lock.RLock() defer x.lock.RUnlock() @@ -222,7 +222,7 @@ func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, s } round := x.currentRound - isMyTurn, err := x.checkYourturnWithinFinalisedMasternodes(chain, round, parent, signer) + 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) } @@ -269,7 +269,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er signer := x.signer x.signLock.RUnlock() - isMyTurn, err := x.checkYourturnWithinFinalisedMasternodes(chain, currentRound, parent, signer) + 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) return err @@ -404,12 +404,6 @@ func (x *XDPoS_v2) CalcDifficulty(chain consensus.ChainReader, time uint64, pare return x.calcDifficulty(chain, parent, x.signer) } -// TODO: what should be new difficulty -func (x *XDPoS_v2) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int { - // TODO: The difference of round number between parent round and current round - return big.NewInt(1) -} - func (x *XDPoS_v2) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool { x.lock.RLock() defer x.lock.RUnlock() @@ -523,7 +517,7 @@ func (x *XDPoS_v2) VerifySyncInfoMessage(chain consensus.ChainReader, syncInfo * return false, nil } - err := x.verifyQC(chain, syncInfo.HighestQuorumCert) + err := x.verifyQC(chain, syncInfo.HighestQuorumCert, nil) if err != nil { log.Warn("SyncInfo message verification failed due to QC", "error", err) return false, err @@ -653,7 +647,7 @@ func (x *XDPoS_v2) ProposedBlockHandler(chain consensus.ChainReader, blockHeader return err } - err = x.verifyQC(chain, quorumCert) + err = x.verifyQC(chain, quorumCert, nil) if err != nil { log.Error("[ProposedBlockHandler] Fail to verify QC", "Extra round", round, "QC proposed BlockInfo Hash", quorumCert.ProposedBlockInfo.Hash) return err @@ -732,7 +726,7 @@ func (x *XDPoS_v2) VerifyBlockInfo(blockChainReader consensus.ChainReader, block return nil } -func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *utils.QuorumCert) error { +func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *utils.QuorumCert, parentHeader *types.Header) error { /* 1. Check if num of QC signatures is >= x.config.v2.CertThreshold 2. Get epoch master node list by hash @@ -743,7 +737,7 @@ func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert * 4. Verify gapNumber = epochSwitchNumber - epochSwitchNumber%Epoch - Gap 5. Verify blockInfo */ - epochInfo, err := x.getEpochSwitchInfo(blockChainReader, nil, quorumCert.ProposedBlockInfo.Hash) + epochInfo, err := x.getEpochSwitchInfo(blockChainReader, parentHeader, quorumCert.ProposedBlockInfo.Hash) if err != nil { log.Error("[verifyQC] Error when getting epoch switch Info to verify QC", "Error", err) return fmt.Errorf("Fail to verify QC due to failure in getting epoch switch info") @@ -764,12 +758,6 @@ func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert * return utils.ErrInvalidQC } - epochInfo, err = x.getEpochSwitchInfo(blockChainReader, nil, quorumCert.ProposedBlockInfo.Hash) - if err != nil { - log.Error("[verifyQC] Error when getting epoch switch Info to verify QC", "Error", err) - return fmt.Errorf("Fail to verify QC due to failure in getting epoch switch info") - } - var wg sync.WaitGroup wg.Add(len(signatures)) var haveError error diff --git a/consensus/XDPoS/engines/engine_v2/mining.go b/consensus/XDPoS/engines/engine_v2/mining.go index 3ec944aefa..abb1d36cd0 100644 --- a/consensus/XDPoS/engines/engine_v2/mining.go +++ b/consensus/XDPoS/engines/engine_v2/mining.go @@ -12,10 +12,10 @@ import ( ) // Using parent and current round to find the finalised master node list(with penalties applied from last epoch) -func (x *XDPoS_v2) checkYourturnWithinFinalisedMasternodes(chain consensus.ChainReader, round utils.Round, parent *types.Header, signer common.Address) (bool, error) { +func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round utils.Round, parent *types.Header, signer common.Address) (bool, error) { isEpochSwitch, _, err := x.isEpochSwitchAtRound(round, parent) if err != nil { - log.Error("[checkYourturnWithinFinalisedMasternodes] check epoch switch at round failed", "Error", err) + log.Error("[yourturn] check epoch switch at round failed", "Error", err) return false, err } var masterNodes []common.Address @@ -24,13 +24,13 @@ func (x *XDPoS_v2) checkYourturnWithinFinalisedMasternodes(chain consensus.Chain // the initial master nodes of v1->v2 switch contains penalties node _, _, masterNodes, err = x.getExtraFields(parent) if err != nil { - log.Error("[checkYourturnWithinFinalisedMasternodes] Cannot find snapshot at gap num of last V1", "err", err, "number", x.config.V2.SwitchBlock.Uint64()) + log.Error("[yourturn] Cannot find snapshot at gap num of last V1", "err", err, "number", x.config.V2.SwitchBlock.Uint64()) return false, err } } else { masterNodes, _, err = x.calcMasternodes(chain, big.NewInt(0).Add(parent.Number, big.NewInt(1)), parent.Hash()) if err != nil { - log.Error("[checkYourturnWithinFinalisedMasternodes] Cannot calcMasternodes at gap num ", "err", err, "parent number", parent.Number) + log.Error("[yourturn] Cannot calcMasternodes at gap num ", "err", err, "parent number", parent.Number) return false, err } } @@ -40,26 +40,26 @@ func (x *XDPoS_v2) checkYourturnWithinFinalisedMasternodes(chain consensus.Chain } if len(masterNodes) == 0 { - log.Error("[checkYourturnWithinFinalisedMasternodes] Fail to find any master nodes from current block round epoch", "Hash", parent.Hash(), "CurrentRound", round, "Number", parent.Number) + log.Error("[yourturn] Fail to find any master nodes from current block round epoch", "Hash", parent.Hash(), "CurrentRound", round, "Number", parent.Number) return false, errors.New("masternodes not found") } curIndex := utils.Position(masterNodes, signer) if curIndex == -1 { - log.Debug("[checkYourturnWithinFinalisedMasternodes] Not authorised signer", "MN", masterNodes, "Hash", parent.Hash(), "signer", signer) + log.Debug("[yourturn] Not authorised signer", "MN", masterNodes, "Hash", parent.Hash(), "signer", signer) return false, nil } for i, s := range masterNodes { - log.Debug("[checkYourturnWithinFinalisedMasternodes] Masternode:", "index", i, "address", s.String(), "parentBlockNum", parent.Number) + log.Debug("[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("[checkYourturnWithinFinalisedMasternodes] Not my turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", parent.Hash().Hex(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "signer", signer) + log.Debug("[yourturn] Not my turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", parent.Hash().Hex(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "signer", signer) return false, nil } - log.Debug("[checkYourturnWithinFinalisedMasternodes] Yes, it's my turn based on parent block", "ParentHash", parent.Hash().Hex(), "ParentBlockNumber", parent.Number.Uint64()) + log.Debug("[yourturn] Yes, it's my turn based on parent block", "ParentHash", parent.Hash().Hex(), "ParentBlockNumber", parent.Number.Uint64()) return true, nil } diff --git a/consensus/XDPoS/engines/engine_v2/verifyHeader.go b/consensus/XDPoS/engines/engine_v2/verifyHeader.go index 23097c177e..819712c7ac 100644 --- a/consensus/XDPoS/engines/engine_v2/verifyHeader.go +++ b/consensus/XDPoS/engines/engine_v2/verifyHeader.go @@ -39,6 +39,22 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade } } + // Ensure that the block's timestamp isn't too close to it's parent + var parent *types.Header + number := header.Number.Uint64() + + if len(parents) > 0 { + parent = parents[len(parents)-1] + } else { + parent = chain.GetHeader(header.ParentHash, number-1) + } + if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { + return consensus.ErrUnknownAncestor + } + if parent.Number.Uint64() > x.config.V2.SwitchBlock.Uint64() && parent.Time.Uint64()+uint64(x.config.V2.MinePeriod) > header.Time.Uint64() { + return utils.ErrInvalidTimestamp + } + // Verify this is truely a v2 block first quorumCert, round, _, err := x.getExtraFields(header) if err != nil { @@ -48,7 +64,7 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade return utils.ErrRoundInvalid } - err = x.verifyQC(chain, quorumCert) + err = x.verifyQC(chain, quorumCert, parent) if err != nil { log.Warn("[verifyHeader] fail to verify QC", "QCNumber", quorumCert.ProposedBlockInfo.Number, "QCsigLength", len(quorumCert.Signatures)) return err @@ -105,22 +121,6 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade return err } - // Ensure that the block's timestamp isn't too close to it's parent - var parent *types.Header - number := header.Number.Uint64() - - if len(parents) > 0 { - parent = parents[len(parents)-1] - } else { - parent = chain.GetHeader(header.ParentHash, number-1) - } - if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { - return consensus.ErrUnknownAncestor - } - if parent.Number.Uint64() > x.config.V2.SwitchBlock.Uint64() && parent.Time.Uint64()+uint64(x.config.V2.MinePeriod) > header.Time.Uint64() { - return utils.ErrInvalidTimestamp - } - _, penalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash) if err != nil { log.Error("[verifyHeader] Fail to calculate master nodes list with penalty", "Number", header.Number, "Hash", header.Hash()) diff --git a/consensus/tests/engine_v2_tests/verify_header_test.go b/consensus/tests/engine_v2_tests/verify_header_test.go index 3411a504ef..2c0665ca6c 100644 --- a/consensus/tests/engine_v2_tests/verify_header_test.go +++ b/consensus/tests/engine_v2_tests/verify_header_test.go @@ -218,3 +218,37 @@ func TestShouldFailIfNotEnoughQCSignatures(t *testing.T) { assert.Equal(t, utils.ErrInvalidQC, err) } + +func TestShouldVerifyHeaders(t *testing.T) { + b, err := json.Marshal(params.TestXDPoSMockChainConfig) + assert.Nil(t, err) + configString := string(b) + + var config params.ChainConfig + err = json.Unmarshal([]byte(configString), &config) + assert.Nil(t, err) + // Enable verify + config.XDPoS.V2.SkipV2Validation = false + // Skip the mining time validation by set mine time to 0 + config.XDPoS.V2.MinePeriod = 0 + // Block 901 is the first v2 block with round of 1 + blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, &config, 0) + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + + // var results <-chan error + // var abort <-chan struct{} + + // Happy path + var happyPathHeaders []*types.Header + happyPathHeaders = append(happyPathHeaders, blockchain.GetBlockByNumber(899).Header(), blockchain.GetBlockByNumber(900).Header(), blockchain.GetBlockByNumber(901).Header(), blockchain.GetBlockByNumber(902).Header()) + // Randomly set full verify + var fullVerifies []bool + fullVerifies = append(fullVerifies, false, true, true, false) + _, results := adaptor.VerifyHeaders(blockchain, happyPathHeaders, fullVerifies) + select { + case result := <-results: + assert.Nil(t, result) + case <-time.After(time.Duration(2) * time.Second): // It should be very fast to verify headers + t.Fatalf("Taking too long to verify headers") + } +}