diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index cdce6e4189..8aa2c7f24f 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -590,10 +590,13 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade // Verify this is truely a v2 block first - quorumCert, _, _, err := x.getExtraFields(header) + quorumCert, round, _, err := x.getExtraFields(header) if err != nil { return utils.ErrInvalidV2Extra } + if round <= quorumCert.ProposedBlockInfo.Round { + return utils.ErrRoundInvalid + } err = x.verifyQC(chain, quorumCert) if err != nil { @@ -613,6 +616,10 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade return utils.ErrInvalidUncleHash } + if header.Difficulty.Cmp(big.NewInt(1)) != 0 { + return utils.ErrInvalidDifficulty + } + isEpochSwitch, _, err := x.IsEpochSwitch(header) // Verify v2 block that is on the epoch switch if err != nil { log.Error("[verifyHeader] error when checking if header is epoch switch header", "Hash", header.Hash(), "Number", header.Number, "Error", err) @@ -628,6 +635,7 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade if len(header.Validators)%common.AddressLength != 0 { return utils.ErrInvalidCheckpointSigners } + // TODO: Add checkMasternodesOnEpochSwitch } else { if len(header.Validators) != 0 { log.Warn("[verifyHeader] Validators shall not have values in non-epochSwitch block", "Hash", header.Hash(), "Number", header.Number, "Validators", header.Validators) @@ -652,47 +660,54 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { return consensus.ErrUnknownAncestor } - if parent.Time.Uint64()+uint64(x.config.V2.MinePeriod) > header.Time.Uint64() { + if parent.Number.Uint64() > x.config.V2.SwitchBlock.Uint64() && parent.Time.Uint64()+uint64(x.config.V2.MinePeriod) > header.Time.Uint64() { return utils.ErrInvalidTimestamp } - // TODO: verifySeal XIN-135 + // TODO: item 9. check validator + + _, 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()) + return err + } + + if !utils.CompareSignersLists(common.ExtractAddressFromBytes(header.Penalties), penalties) { + return utils.ErrPenaltyListDoesNotMatch + } x.verifiedHeaders.Add(header.Hash(), true) return nil } -// Utils for test to get current Pool size -func (x *XDPoS_v2) GetVotePoolSize(vote *utils.Vote) int { - return x.votePool.Size(vote) -} - -// Utils for test to get Timeout Pool Size -func (x *XDPoS_v2) GetTimeoutPoolSize(timeout *utils.Timeout) int { - return x.timeoutPool.Size(timeout) -} - /* SyncInfo workflow */ // Verify syncInfo and trigger process QC or TC if successful -func (x *XDPoS_v2) VerifySyncInfoMessage(chain consensus.ChainReader, syncInfo *utils.SyncInfo) error { +func (x *XDPoS_v2) VerifySyncInfoMessage(chain consensus.ChainReader, syncInfo *utils.SyncInfo) (bool, error) { /* - 1. Verify items including: + 1. Check QC and TC against highest QC TC. Skip if none of them need to be updated + 2. Verify items including: - verifyQC - verifyTC - 2. Broadcast(Not part of consensus) + 3. Broadcast(Not part of consensus) */ + + if (x.highestQuorumCert.ProposedBlockInfo.Round >= syncInfo.HighestQuorumCert.ProposedBlockInfo.Round) && (x.highestTimeoutCert.Round >= syncInfo.HighestTimeoutCert.Round) { + log.Warn("[VerifySyncInfoMessage] Round from incoming syncInfo message is no longer qualified", "Highest QC Round", x.highestQuorumCert.ProposedBlockInfo.Round, "Incoming SyncInfo QC Round", syncInfo.HighestQuorumCert.ProposedBlockInfo.Round, "highestTimeoutCert Round", x.highestTimeoutCert.Round, "Incoming syncInfo TC Round", syncInfo.HighestTimeoutCert.Round) + return false, nil + } + err := x.verifyQC(chain, syncInfo.HighestQuorumCert) if err != nil { log.Warn("SyncInfo message verification failed due to QC", err) - return err + return false, err } err = x.verifyTC(chain, syncInfo.HighestTimeoutCert) if err != nil { log.Warn("SyncInfo message verification failed due to TC", err) - return err + return false, err } - return nil + return true, nil } func (x *XDPoS_v2) SyncInfoHandler(chain consensus.ChainReader, syncInfo *utils.SyncInfo) error { @@ -714,17 +729,19 @@ func (x *XDPoS_v2) SyncInfoHandler(chain consensus.ChainReader, syncInfo *utils. */ func (x *XDPoS_v2) VerifyVoteMessage(chain consensus.ChainReader, vote *utils.Vote) (bool, error) { /* - 1. Get masterNode list from snapshot - 2. Check signature: + 1. Check vote round with current round for fast fail(disqualifed) + 2. Get masterNode list from snapshot + 3. Check signature: - Use ecRecover to get the public key - Use the above public key to find out the xdc address - Use the above xdc address to check against the master node list from step 1(For the running epoch) 4. Broadcast(Not part of consensus) */ - err := x.VerifyBlockInfo(chain, vote.ProposedBlockInfo) - if err != nil { - return false, err + if vote.ProposedBlockInfo.Round < x.currentRound { + log.Warn("[VerifyVoteMessage] Disqualified vote message as the proposed round does not match currentRound", "vote.ProposedBlockInfo.Round", vote.ProposedBlockInfo.Round, "currentRound", x.currentRound) + return false, nil } + snapshot, err := x.getSnapshot(chain, vote.ProposedBlockInfo.Number.Uint64(), false) if err != nil { log.Error("[VerifyVoteMessage] fail to get snapshot for a vote message", "BlockNum", vote.ProposedBlockInfo.Number, "Hash", vote.ProposedBlockInfo.Hash, "Error", err.Error()) @@ -770,7 +787,13 @@ func (x *XDPoS_v2) voteHandler(chain consensus.ChainReader, voteMsg *utils.Vote) return nil } - err := x.onVotePoolThresholdReached(chain, pooledVotes, voteMsg, proposedBlockHeader) + err := x.VerifyBlockInfo(chain, voteMsg.ProposedBlockInfo) + if err != nil { + x.votePool.ClearPoolKeyByObj(voteMsg) + return err + } + + err = x.onVotePoolThresholdReached(chain, pooledVotes, voteMsg, proposedBlockHeader) if err != nil { return err } @@ -1487,41 +1510,6 @@ func (x *XDPoS_v2) isExtendingFromAncestor(blockChainReader consensus.ChainReade return false, nil } -/* - Testing tools -*/ - -func (x *XDPoS_v2) SetNewRoundFaker(blockChainReader consensus.ChainReader, newRound utils.Round, resetTimer bool) { - x.lock.Lock() - defer x.lock.Unlock() - // Reset a bunch of things - if resetTimer { - x.timeoutWorker.Reset(blockChainReader) - } - x.currentRound = newRound -} - -// for test only -func (x *XDPoS_v2) ProcessQC(chain consensus.ChainReader, qc *utils.QuorumCert) error { - x.lock.Lock() - defer x.lock.Unlock() - return x.processQC(chain, qc) -} - -// Utils for test to check currentRound value -func (x *XDPoS_v2) GetCurrentRound() utils.Round { - x.lock.RLock() - defer x.lock.RUnlock() - return x.currentRound -} - -// Utils for test to check currentRound value -func (x *XDPoS_v2) GetProperties() (utils.Round, *utils.QuorumCert, *utils.QuorumCert, utils.Round, *utils.BlockInfo) { - x.lock.RLock() - defer x.lock.RUnlock() - return x.currentRound, x.lockQuorumCert, x.highestQuorumCert, x.highestVotedRound, x.highestCommitBlock -} - // Get master nodes over extra data of epoch switch block. func (x *XDPoS_v2) GetMasternodesFromEpochSwitchHeader(epochSwitchHeader *types.Header) []common.Address { if epochSwitchHeader == nil { @@ -1539,7 +1527,7 @@ func (x *XDPoS_v2) GetMasternodesFromEpochSwitchHeader(epochSwitchHeader *types. func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) { // Return true directly if we are examing the last v1 block. This could happen if the calling function is examing parent block if header.Number.Cmp(x.config.V2.SwitchBlock) == 0 { - log.Info("[IsEpochSwitch] examing last v1 block 👯‍♂️") + log.Info("[IsEpochSwitch] examing last v1 block") return true, header.Number.Uint64() / x.config.Epoch, nil } diff --git a/consensus/XDPoS/engines/engine_v2/testing_utils.go b/consensus/XDPoS/engines/engine_v2/testing_utils.go new file mode 100644 index 0000000000..f3edef19b1 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2/testing_utils.go @@ -0,0 +1,59 @@ +package engine_v2 + +import ( + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" +) + +/* + Testing tools +*/ + +func (x *XDPoS_v2) SetNewRoundFaker(blockChainReader consensus.ChainReader, newRound utils.Round, resetTimer bool) { + x.lock.Lock() + defer x.lock.Unlock() + // Reset a bunch of things + if resetTimer { + x.timeoutWorker.Reset(blockChainReader) + } + x.currentRound = newRound +} + +// for test only +func (x *XDPoS_v2) ProcessQCFaker(chain consensus.ChainReader, qc *utils.QuorumCert) error { + x.lock.Lock() + defer x.lock.Unlock() + return x.processQC(chain, qc) +} + +// Utils for test to check currentRound value +func (x *XDPoS_v2) GetCurrentRoundFaker() utils.Round { + x.lock.RLock() + defer x.lock.RUnlock() + return x.currentRound +} + +// Utils for test to get current Pool size +func (x *XDPoS_v2) GetVotePoolSizeFaker(vote *utils.Vote) int { + return x.votePool.Size(vote) +} + +// Utils for test to get Timeout Pool Size +func (x *XDPoS_v2) GetTimeoutPoolSizeFaker(timeout *utils.Timeout) int { + return x.timeoutPool.Size(timeout) +} + +// WARN: This function is designed for testing purpose only! +// Utils for test to check currentRound values +func (x *XDPoS_v2) GetPropertiesFaker() (utils.Round, *utils.QuorumCert, *utils.QuorumCert, *utils.TimeoutCert, utils.Round, *utils.BlockInfo) { + x.lock.RLock() + defer x.lock.RUnlock() + return x.currentRound, x.lockQuorumCert, x.highestQuorumCert, x.highestTimeoutCert, x.highestVotedRound, x.highestCommitBlock +} + +// WARN: This function is designed for testing purpose only! +// Utils for tests to set engine specific values +func (x *XDPoS_v2) SetPropertiesFaker(highestQC *utils.QuorumCert, highestTC *utils.TimeoutCert) { + x.highestQuorumCert = highestQC + x.highestTimeoutCert = highestTC +} diff --git a/consensus/XDPoS/utils/errors.go b/consensus/XDPoS/utils/errors.go index f643cd992a..34e1f06af4 100644 --- a/consensus/XDPoS/utils/errors.go +++ b/consensus/XDPoS/utils/errors.go @@ -85,6 +85,9 @@ var ( ErrInvalidTC = errors.New("Invalid TC content") ErrEmptyBlockInfoHash = errors.New("BlockInfo hash is empty") ErrInvalidFieldInNonEpochSwitch = errors.New("Invalid field exist in a non-epoch swtich block") + + ErrPenaltyListDoesNotMatch = errors.New("Incoming block penalty list does not match") + ErrRoundInvalid = errors.New("Invalid Round, it shall be bigger than QC round") ) type ErrIncomingMessageRoundNotEqualCurrentRound struct { diff --git a/consensus/tests/initial_test.go b/consensus/tests/initial_test.go index 7f6c733f0f..9e114d3fdd 100644 --- a/consensus/tests/initial_test.go +++ b/consensus/tests/initial_test.go @@ -24,7 +24,7 @@ func TestInitialFirstV2Blcok(t *testing.T) { err := adaptor.EngineV2.Initial(blockchain, header) assert.Nil(t, err) - round, _, highQC, _, _ := adaptor.EngineV2.GetProperties() + round, _, highQC, _, _, _ := adaptor.EngineV2.GetPropertiesFaker() blockInfo := &utils.BlockInfo{ Hash: header.Hash(), Round: utils.Round(0), @@ -98,7 +98,7 @@ func TestInitialOtherV2Block(t *testing.T) { err = adaptor.EngineV2.Initial(blockchain, block.Header()) assert.Nil(t, err) - round, _, highQC, _, _ := adaptor.EngineV2.GetProperties() + round, _, highQC, _, _, _ := adaptor.EngineV2.GetPropertiesFaker() expectedQuorumCert := &utils.QuorumCert{ ProposedBlockInfo: blockInfo, Signatures: []utils.Signature{}, diff --git a/consensus/tests/mine_test.go b/consensus/tests/mine_test.go index a49590858a..bdb9e4c19b 100644 --- a/consensus/tests/mine_test.go +++ b/consensus/tests/mine_test.go @@ -47,7 +47,7 @@ func TestYourTurnInitialV2(t *testing.T) { assert.Nil(t, err) // round=1, so masternode[1] has YourTurn = True assert.True(t, b) - assert.Equal(t, adaptor.EngineV2.GetCurrentRound(), utils.Round(1)) + assert.Equal(t, adaptor.EngineV2.GetCurrentRoundFaker(), utils.Round(1)) snap, err := adaptor.EngineV2.GetSnapshot(blockchain, block900.Header()) assert.Nil(t, err) diff --git a/consensus/tests/penalty_test.go b/consensus/tests/penalty_test.go index 504dc4f1dd..4c0cecba16 100644 --- a/consensus/tests/penalty_test.go +++ b/consensus/tests/penalty_test.go @@ -36,7 +36,7 @@ func TestHookPenaltyV2Mining(t *testing.T) { // set adaptor round/qc to that of 6299 err = utils.DecodeBytesExtraFields(header6300.Extra, &extraField) assert.Nil(t, err) - err = adaptor.EngineV2.ProcessQC(blockchain, extraField.QuorumCert) + err = adaptor.EngineV2.ProcessQCFaker(blockchain, extraField.QuorumCert) assert.Nil(t, err) headerMining := &types.Header{ ParentHash: header6300.ParentHash, diff --git a/consensus/tests/proposed_block_test.go b/consensus/tests/proposed_block_test.go index 67d49f01ee..a723983c29 100644 --- a/consensus/tests/proposed_block_test.go +++ b/consensus/tests/proposed_block_test.go @@ -29,13 +29,13 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) { } voteMsg := <-engineV2.BroadcastCh - poolSize := engineV2.GetVotePoolSize(voteMsg.(*utils.Vote)) + poolSize := engineV2.GetVotePoolSizeFaker(voteMsg.(*utils.Vote)) assert.Equal(t, poolSize, 1) assert.NotNil(t, voteMsg) assert.Equal(t, currentBlock.Hash(), voteMsg.(*utils.Vote).ProposedBlockInfo.Hash) - round, _, highestQC, _, _ := engineV2.GetProperties() + round, _, highestQC, _, _, _ := engineV2.GetPropertiesFaker() // Shoud trigger setNewRound assert.Equal(t, utils.Round(1), round) // Should not update the highestQC @@ -53,7 +53,7 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) { // Trigger send vote again but for a new round voteMsg = <-engineV2.BroadcastCh assert.NotNil(t, voteMsg) - round, _, highestQC, _, _ = engineV2.GetProperties() + round, _, highestQC, _, _, _ = engineV2.GetPropertiesFaker() // Shoud trigger setNewRound assert.Equal(t, utils.Round(2), round) assert.Equal(t, utils.Round(1), highestQC.ProposedBlockInfo.Round) @@ -70,7 +70,7 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) { // Trigger send vote again but for a new round voteMsg = <-engineV2.BroadcastCh assert.NotNil(t, voteMsg) - round, _, highestQC, _, highestCommitBlock := engineV2.GetProperties() + round, _, highestQC, _, _, highestCommitBlock := engineV2.GetPropertiesFaker() // Shoud NOT trigger setNewRound as the new block parent QC is round 1 but the currentRound is already 2 assert.Equal(t, utils.Round(3), round) assert.Equal(t, utils.Round(2), highestQC.ProposedBlockInfo.Round) @@ -88,7 +88,7 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) { // Trigger send vote again but for a new round voteMsg = <-engineV2.BroadcastCh assert.NotNil(t, voteMsg) - round, _, highestQC, _, highestCommitBlock = engineV2.GetProperties() + round, _, highestQC, _, _, highestCommitBlock = engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(4), round) assert.Equal(t, utils.Round(3), highestQC.ProposedBlockInfo.Round) @@ -117,7 +117,7 @@ func TestShouldNotCommitIfRoundsNotContinousFor3Rounds(t *testing.T) { assert.NotNil(t, voteMsg) assert.Equal(t, currentBlock.Hash(), voteMsg.(*utils.Vote).ProposedBlockInfo.Hash) - round, _, highestQC, _, highestCommitBlock := engineV2.GetProperties() + round, _, highestQC, _, _, highestCommitBlock := engineV2.GetPropertiesFaker() grandGrandParentBlock := blockchain.GetBlockByNumber(902) // Shoud trigger setNewRound @@ -139,7 +139,7 @@ func TestShouldNotCommitIfRoundsNotContinousFor3Rounds(t *testing.T) { // Trigger send vote again but for a new round voteMsg = <-engineV2.BroadcastCh assert.NotNil(t, voteMsg) - round, _, highestQC, _, highestCommitBlock = engineV2.GetProperties() + round, _, highestQC, _, _, highestCommitBlock = engineV2.GetPropertiesFaker() grandGrandParentBlock = blockchain.GetBlockByNumber(903) assert.Equal(t, utils.Round(6), round) @@ -160,7 +160,7 @@ func TestShouldNotCommitIfRoundsNotContinousFor3Rounds(t *testing.T) { // Trigger send vote again but for a new round voteMsg = <-engineV2.BroadcastCh assert.NotNil(t, voteMsg) - round, _, highestQC, _, highestCommitBlock = engineV2.GetProperties() + round, _, highestQC, _, _, highestCommitBlock = engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(8), round) assert.Equal(t, utils.Round(7), highestQC.ProposedBlockInfo.Round) @@ -193,7 +193,7 @@ func TestProposedBlockMessageHandlerSuccessfullyGenerateVote(t *testing.T) { assert.NotNil(t, voteMsg) assert.Equal(t, currentBlock.Hash(), voteMsg.(*utils.Vote).ProposedBlockInfo.Hash) - round, _, highestQC, _, _ := engineV2.GetProperties() + round, _, highestQC, _, _, _ := engineV2.GetPropertiesFaker() // Shoud trigger setNewRound assert.Equal(t, utils.Round(6), round) assert.Equal(t, extraField.QuorumCert.Signatures, highestQC.Signatures) @@ -219,7 +219,7 @@ func TestShouldNotSetNewRound(t *testing.T) { t.Fatal("Fail propose proposedBlock handler", err) } - round, _, highestQC, _, _ := engineV2.GetProperties() + round, _, highestQC, _, _, _ := engineV2.GetPropertiesFaker() // Shoud not trigger setNewRound assert.Equal(t, utils.Round(6), round) assert.Equal(t, extraField.QuorumCert.Signatures, highestQC.Signatures) @@ -241,7 +241,7 @@ func TestShouldNotSendVoteMessageIfAlreadyVoteForThisRound(t *testing.T) { assert.NotNil(t, voteMsg) assert.Equal(t, currentBlock.Hash(), voteMsg.(*utils.Vote).ProposedBlockInfo.Hash) - round, _, _, highestVotedRound, _ := engineV2.GetProperties() + round, _, _, _, highestVotedRound, _ := engineV2.GetPropertiesFaker() // Shoud trigger setNewRound assert.Equal(t, utils.Round(6), round) assert.Equal(t, utils.Round(6), highestVotedRound) @@ -257,7 +257,7 @@ func TestShouldNotSendVoteMessageIfAlreadyVoteForThisRound(t *testing.T) { t.Fatal("Should not trigger vote") case <-time.After(5 * time.Second): // Shoud not trigger setNewRound - round, _, _, highestVotedRound, _ = engineV2.GetProperties() + round, _, _, _, highestVotedRound, _ = engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(6), round) assert.Equal(t, utils.Round(6), highestVotedRound) } @@ -286,7 +286,7 @@ func TestShouldNotSendVoteMsgIfBlockInfoRoundNotEqualCurrentRound(t *testing.T) t.Fatal("Should not trigger vote") case <-time.After(5 * time.Second): // Shoud not trigger setNewRound - round, _, _, _, _ := engineV2.GetProperties() + round, _, _, _, _, _ := engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(8), round) } } @@ -328,7 +328,7 @@ func TestShouldNotSendVoteMsgIfBlockNotExtendedFromAncestor(t *testing.T) { t.Fatal("Should not trigger vote") case <-time.After(5 * time.Second): // Shoud not trigger setNewRound - round, _, _, _, _ := engineV2.GetProperties() + round, _, _, _, _, _ := engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(7), round) } } @@ -345,7 +345,7 @@ func TestShouldSendVoteMsg(t *testing.T) { if err != nil { t.Fatal(err) } - round, _, _, _, _ := engineV2.GetProperties() + round, _, _, _, _, _ := engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(i-900), round) vote := <-engineV2.BroadcastCh assert.Equal(t, round, vote.(*utils.Vote).ProposedBlockInfo.Round) diff --git a/consensus/tests/sync_info_test.go b/consensus/tests/sync_info_test.go index 892d026aba..e2cebb1c5f 100644 --- a/consensus/tests/sync_info_test.go +++ b/consensus/tests/sync_info_test.go @@ -33,7 +33,7 @@ func TestSyncInfoShouldSuccessfullyUpdateByQC(t *testing.T) { if err != nil { t.Fatal(err) } - round, _, highestQuorumCert, _, highestCommitBlock := engineV2.GetProperties() + round, _, highestQuorumCert, _, _, highestCommitBlock := engineV2.GetPropertiesFaker() // QC is parent block's qc, which is pointing at round 4, hence 4 + 1 = 5 assert.Equal(t, utils.Round(5), round) assert.Equal(t, extraField.QuorumCert, highestQuorumCert) @@ -66,7 +66,36 @@ func TestSyncInfoShouldSuccessfullyUpdateByTC(t *testing.T) { if err != nil { t.Fatal(err) } - round, _, highestQuorumCert, _, _ := engineV2.GetProperties() + round, _, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(7), round) assert.Equal(t, extraField.QuorumCert, highestQuorumCert) } + +func TestSkipVerifySyncInfoIfBothQcTcNotQualified(t *testing.T) { + blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, 0) + engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 + + // Make the Highest QC in syncInfo point to an old block to simulate it's no longer qualified + parentBlock := blockchain.GetBlockByNumber(903) + var extraField utils.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(parentBlock.Extra(), &extraField) + if err != nil { + t.Fatal("Fail to decode extra data", err) + } + + highestTC := &utils.TimeoutCert{ + Round: utils.Round(5), + Signatures: []utils.Signature{}, + } + + syncInfoMsg := &utils.SyncInfo{ + HighestQuorumCert: extraField.QuorumCert, + HighestTimeoutCert: highestTC, + } + + engineV2.SetPropertiesFaker(syncInfoMsg.HighestQuorumCert, syncInfoMsg.HighestTimeoutCert) + + verified, err := engineV2.VerifySyncInfoMessage(blockchain, syncInfoMsg) + assert.False(t, verified) + assert.Nil(t, err) +} diff --git a/consensus/tests/timeout_test.go b/consensus/tests/timeout_test.go index baba42f861..6eca5f723a 100644 --- a/consensus/tests/timeout_test.go +++ b/consensus/tests/timeout_test.go @@ -19,7 +19,7 @@ func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) { engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 timeoutMsg := <-engineV2.BroadcastCh - poolSize := engineV2.GetTimeoutPoolSize(timeoutMsg.(*utils.Timeout)) + poolSize := engineV2.GetTimeoutPoolSizeFaker(timeoutMsg.(*utils.Timeout)) assert.Equal(t, poolSize, 1) assert.NotNil(t, timeoutMsg) assert.Equal(t, uint64(1350), timeoutMsg.(*utils.Timeout).GapNumber) @@ -99,7 +99,7 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) { err := engineV2.TimeoutHandler(blockchain, timeoutMsg) assert.Nil(t, err) - currentRound, _, _, _, _ := engineV2.GetProperties() + currentRound, _, _, _, _, _ := engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(1), currentRound) timeoutMsg = &utils.Timeout{ Round: utils.Round(1), @@ -108,7 +108,7 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) { } err = engineV2.TimeoutHandler(blockchain, timeoutMsg) assert.Nil(t, err) - currentRound, _, _, _, _ = engineV2.GetProperties() + currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(1), currentRound) // Send a timeout with different gap number, it shall not trigger timeout pool hook @@ -119,7 +119,7 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) { } err = engineV2.TimeoutHandler(blockchain, timeoutMsg) assert.Nil(t, err) - currentRound, _, _, _, _ = engineV2.GetProperties() + currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(1), currentRound) // Create a timeout message that should trigger timeout pool hook @@ -134,7 +134,7 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) { syncInfoMsg := <-engineV2.BroadcastCh - currentRound, _, _, _, _ = engineV2.GetProperties() + currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker() assert.NotNil(t, syncInfoMsg) diff --git a/consensus/tests/verify_block_test.go b/consensus/tests/verify_block_test.go deleted file mode 100644 index d916aa518f..0000000000 --- a/consensus/tests/verify_block_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package tests - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/XinFinOrg/XDPoSChain/accounts" - "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" - "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" - "github.com/XinFinOrg/XDPoSChain/params" - "github.com/stretchr/testify/assert" -) - -func TestShouldVerifyBlock(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) - - // Happy path - err = adaptor.VerifyHeader(blockchain, blockchain.GetBlockByNumber(901).Header(), true) - assert.Nil(t, err) - - // Verify non-epoch switch block - err = adaptor.VerifyHeader(blockchain, blockchain.GetBlockByNumber(902).Header(), true) - assert.Nil(t, err) - - nonEpochSwitchWithValidators := blockchain.GetBlockByNumber(902).Header() - nonEpochSwitchWithValidators.Validators = acc1Addr.Bytes() - err = adaptor.VerifyHeader(blockchain, nonEpochSwitchWithValidators, true) - assert.Equal(t, utils.ErrInvalidFieldInNonEpochSwitch, err) -} - -func TestShouldFailIfNotEnoughQCSignatures(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, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 902, &config, 0) - adaptor := blockchain.Engine().(*XDPoS.XDPoS) - - parentBlock := blockchain.GetBlockByNumber(901) - proposedBlockInfo := &utils.BlockInfo{ - Hash: parentBlock.Hash(), - Round: utils.Round(1), - Number: parentBlock.Number(), - } - signedHash, err := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(proposedBlockInfo).Bytes()) - assert.Nil(t, err) - var signatures []utils.Signature - // Duplicate the signatures - signatures = append(signatures, signedHash, signedHash, signedHash, signedHash, signedHash, signedHash) - quorumCert := &utils.QuorumCert{ - ProposedBlockInfo: proposedBlockInfo, - Signatures: signatures, - } - - extra := utils.ExtraFields_v2{ - Round: utils.Round(2), - QuorumCert: quorumCert, - } - extraInBytes, err := extra.EncodeToBytes() - if err != nil { - panic(fmt.Errorf("Error encode extra into bytes: %v", err)) - } - headerWithDuplicatedSignatures := currentBlock.Header() - headerWithDuplicatedSignatures.Extra = extraInBytes - // Happy path - err = adaptor.VerifyHeader(blockchain, headerWithDuplicatedSignatures, true) - assert.Equal(t, utils.ErrInvalidQC, err) - -} diff --git a/consensus/tests/verify_header_test.go b/consensus/tests/verify_header_test.go new file mode 100644 index 0000000000..c9132532a3 --- /dev/null +++ b/consensus/tests/verify_header_test.go @@ -0,0 +1,193 @@ +package tests + +import ( + "encoding/json" + "fmt" + "math/big" + "testing" + "time" + + "github.com/XinFinOrg/XDPoSChain/accounts" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/stretchr/testify/assert" +) + +func TestShouldVerifyBlock(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, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, &config, 0) + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + + // Happy path + err = adaptor.VerifyHeader(blockchain, blockchain.GetBlockByNumber(901).Header(), true) + assert.Nil(t, err) + + // Unhappy path + + // Verify non-epoch switch block + err = adaptor.VerifyHeader(blockchain, blockchain.GetBlockByNumber(902).Header(), true) + assert.Nil(t, err) + + nonEpochSwitchWithValidators := blockchain.GetBlockByNumber(902).Header() + nonEpochSwitchWithValidators.Validators = acc1Addr.Bytes() + err = adaptor.VerifyHeader(blockchain, nonEpochSwitchWithValidators, true) + assert.Equal(t, utils.ErrInvalidFieldInNonEpochSwitch, err) + + noValidatorBlock := blockchain.GetBlockByNumber(902).Header() + noValidatorBlock.Validator = []byte{} + err = adaptor.VerifyHeader(blockchain, noValidatorBlock, true) + assert.Equal(t, consensus.ErrNoValidatorSignature, err) + + blockFromFuture := blockchain.GetBlockByNumber(902).Header() + blockFromFuture.Time = big.NewInt(time.Now().Unix() + 10000) + err = adaptor.VerifyHeader(blockchain, blockFromFuture, true) + assert.Equal(t, consensus.ErrFutureBlock, err) + + invalidQcBlock := blockchain.GetBlockByNumber(902).Header() + invalidQcBlock.Extra = []byte{} + err = adaptor.VerifyHeader(blockchain, invalidQcBlock, true) + assert.Equal(t, utils.ErrInvalidV2Extra, err) + + // Epoch switch + invalidAuthNonceBlock := blockchain.GetBlockByNumber(901).Header() + invalidAuthNonceBlock.Nonce = types.BlockNonce{123} + err = adaptor.VerifyHeader(blockchain, invalidAuthNonceBlock, true) + assert.Equal(t, utils.ErrInvalidVote, err) + + emptyValidatorsBlock := blockchain.GetBlockByNumber(901).Header() + emptyValidatorsBlock.Validators = []byte{} + err = adaptor.VerifyHeader(blockchain, emptyValidatorsBlock, true) + assert.Equal(t, utils.ErrEmptyEpochSwitchValidators, err) + + invalidValidatorsSignerBlock := blockchain.GetBlockByNumber(901).Header() + invalidValidatorsSignerBlock.Validators = []byte{123} + err = adaptor.VerifyHeader(blockchain, invalidValidatorsSignerBlock, true) + assert.Equal(t, utils.ErrInvalidCheckpointSigners, err) + + // non-epoch switch + invalidValidatorsExistBlock := blockchain.GetBlockByNumber(902).Header() + invalidValidatorsExistBlock.Validators = []byte{123} + err = adaptor.VerifyHeader(blockchain, invalidValidatorsExistBlock, true) + assert.Equal(t, utils.ErrInvalidFieldInNonEpochSwitch, err) + + merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb123" + parentNotExistBlock := blockchain.GetBlockByNumber(901).Header() + parentNotExistBlock.ParentHash = common.HexToHash(merkleRoot) + err = adaptor.VerifyHeader(blockchain, parentNotExistBlock, true) + assert.Equal(t, consensus.ErrUnknownAncestor, err) + + tooFastMinedBlock := blockchain.GetBlockByNumber(902).Header() + tooFastMinedBlock.Time = big.NewInt(time.Now().Unix() - 2) + err = adaptor.VerifyHeader(blockchain, tooFastMinedBlock, true) + assert.Equal(t, utils.ErrInvalidTimestamp, err) + + invalidDifficultyBlock := blockchain.GetBlockByNumber(902).Header() + invalidDifficultyBlock.Difficulty = big.NewInt(2) + err = adaptor.VerifyHeader(blockchain, invalidDifficultyBlock, true) + assert.Equal(t, utils.ErrInvalidDifficulty, err) + + // Creat an invalid QC round + proposedBlockInfo := &utils.BlockInfo{ + Hash: blockchain.GetBlockByNumber(902).Hash(), + Round: utils.Round(2), + Number: blockchain.GetBlockByNumber(902).Number(), + } + // Genrate QC + signedHash, err := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(proposedBlockInfo).Bytes()) + if err != nil { + panic(fmt.Errorf("Error generate QC by creating signedHash: %v", err)) + } + // Sign from acc 1, 2, 3 + acc1SignedHash := SignHashByPK(acc1Key, utils.VoteSigHash(proposedBlockInfo).Bytes()) + acc2SignedHash := SignHashByPK(acc2Key, utils.VoteSigHash(proposedBlockInfo).Bytes()) + acc3SignedHash := SignHashByPK(acc3Key, utils.VoteSigHash(proposedBlockInfo).Bytes()) + var signatures []utils.Signature + signatures = append(signatures, signedHash, acc1SignedHash, acc2SignedHash, acc3SignedHash) + quorumCert := &utils.QuorumCert{ + ProposedBlockInfo: proposedBlockInfo, + Signatures: signatures, + } + + extra := utils.ExtraFields_v2{ + Round: utils.Round(2), + QuorumCert: quorumCert, + } + extraInBytes, err := extra.EncodeToBytes() + if err != nil { + panic(fmt.Errorf("Error encode extra into bytes: %v", err)) + } + + invalidRoundBlock := blockchain.GetBlockByNumber(902).Header() + invalidRoundBlock.Extra = extraInBytes + err = adaptor.VerifyHeader(blockchain, invalidRoundBlock, true) + assert.Equal(t, utils.ErrRoundInvalid, err) + + invalidPenaltiesExistBlock := blockchain.GetBlockByNumber(902).Header() + invalidPenaltiesExistBlock.Penalties = common.Hex2BytesFixed("123131231", 20) + err = adaptor.VerifyHeader(blockchain, invalidPenaltiesExistBlock, true) + assert.Equal(t, utils.ErrPenaltyListDoesNotMatch, err) + +} + +func TestShouldFailIfNotEnoughQCSignatures(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, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 902, &config, 0) + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + + parentBlock := blockchain.GetBlockByNumber(901) + proposedBlockInfo := &utils.BlockInfo{ + Hash: parentBlock.Hash(), + Round: utils.Round(1), + Number: parentBlock.Number(), + } + signedHash, err := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(proposedBlockInfo).Bytes()) + assert.Nil(t, err) + var signatures []utils.Signature + // Duplicate the signatures + signatures = append(signatures, signedHash, signedHash, signedHash, signedHash, signedHash, signedHash) + quorumCert := &utils.QuorumCert{ + ProposedBlockInfo: proposedBlockInfo, + Signatures: signatures, + } + + extra := utils.ExtraFields_v2{ + Round: utils.Round(2), + QuorumCert: quorumCert, + } + extraInBytes, err := extra.EncodeToBytes() + if err != nil { + panic(fmt.Errorf("Error encode extra into bytes: %v", err)) + } + headerWithDuplicatedSignatures := currentBlock.Header() + headerWithDuplicatedSignatures.Extra = extraInBytes + // Happy path + err = adaptor.VerifyHeader(blockchain, headerWithDuplicatedSignatures, true) + assert.Equal(t, utils.ErrInvalidQC, err) + +} diff --git a/consensus/tests/vote_test.go b/consensus/tests/vote_test.go index 404fcfcfa9..77de5f0e4b 100644 --- a/consensus/tests/vote_test.go +++ b/consensus/tests/vote_test.go @@ -38,7 +38,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQCForFistV2Round(t *te err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, _ := engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker() // initialised with nil and 0 round assert.Nil(t, lockQuorumCert) assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) @@ -52,7 +52,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQCForFistV2Round(t *te } err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, _ = engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker() // Still using the initlised value because we did not yet go to the next round assert.Nil(t, lockQuorumCert) assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) @@ -68,7 +68,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQCForFistV2Round(t *te err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, _ = engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker() // The lockQC shall be the parent's QC round number assert.Equal(t, utils.Round(0), lockQuorumCert.ProposedBlockInfo.Round) // The highestQC proposedBlockInfo shall be the same as the one from its votes @@ -100,7 +100,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) { err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, _ := engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker() // initialised with nil and 0 round assert.Nil(t, lockQuorumCert) assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) @@ -112,7 +112,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) { } err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, _ = engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker() // Still using the initlised value because we did not yet go to the next round assert.Nil(t, lockQuorumCert) assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) @@ -130,7 +130,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) { } err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, _ = engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker() // Still using the initlised value because we did not yet go to the next round assert.Nil(t, lockQuorumCert) assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) @@ -145,7 +145,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) { err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, highestCommitBlock := engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, highestCommitBlock := engineV2.GetPropertiesFaker() // The lockQC shall be the parent's QC round number assert.Equal(t, utils.Round(4), lockQuorumCert.ProposedBlockInfo.Round) // The highestQC proposedBlockInfo shall be the same as the one from its votes @@ -202,7 +202,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { blockInfo := &utils.BlockInfo{ Hash: currentBlock.Hash(), Round: utils.Round(5), - Number: big.NewInt(901), + Number: big.NewInt(905), } voteSigningHash := utils.VoteSigHash(blockInfo) // Create two vote message which will not reach vote pool threshold @@ -214,7 +214,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { err := engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, _ := engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker() // initialised with nil and 0 round assert.Nil(t, lockQuorumCert) assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) @@ -226,7 +226,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { } err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, _, _, _, _ = engineV2.GetProperties() + currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(5), currentRound) // Create a vote message that should trigger vote pool hook @@ -238,7 +238,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) // Check round has now changed from 5 to 6 - currentRound, lockQuorumCert, highestQuorumCert, _, _ = engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker() // The lockQC shall be the parent's QC round number assert.Equal(t, utils.Round(4), lockQuorumCert.ProposedBlockInfo.Round) // The highestQC proposedBlockInfo shall be the same as the one from its votes @@ -266,7 +266,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { err = engineV2.TimeoutHandler(blockchain, timeoutMsg) assert.Nil(t, err) - currentRound, _, _, _, _ = engineV2.GetProperties() + currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(6), currentRound) timeoutMsg = &utils.Timeout{ Round: utils.Round(6), @@ -274,7 +274,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { } err = engineV2.TimeoutHandler(blockchain, timeoutMsg) assert.Nil(t, err) - currentRound, _, _, _, _ = engineV2.GetProperties() + currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(6), currentRound) // Create a timeout message that should trigger timeout pool hook @@ -300,7 +300,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) { sigatures := []utils.Signature{[]byte{1}, []byte{2}, []byte{3}} assert.ElementsMatch(t, tc.Signatures, sigatures) // Round shall be +1 now - currentRound, _, _, _, _ = engineV2.GetProperties() + currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker() assert.Equal(t, utils.Round(7), currentRound) } @@ -346,7 +346,7 @@ func TestVoteMessageShallNotThrowErrorIfBlockNotYetExist(t *testing.T) { err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, _ := engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker() // Still using the initlised value because we did not yet go to the next round assert.Nil(t, lockQuorumCert) assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) @@ -364,7 +364,7 @@ func TestVoteMessageShallNotThrowErrorIfBlockNotYetExist(t *testing.T) { err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) - currentRound, lockQuorumCert, highestQuorumCert, _, highestCommitBlock := engineV2.GetProperties() + currentRound, lockQuorumCert, highestQuorumCert, _, _, highestCommitBlock := engineV2.GetPropertiesFaker() // The lockQC shall be the parent's QC round number assert.Equal(t, utils.Round(5), lockQuorumCert.ProposedBlockInfo.Round) // The highestQC proposedBlockInfo shall be the same as the one from its votes @@ -375,25 +375,80 @@ func TestVoteMessageShallNotThrowErrorIfBlockNotYetExist(t *testing.T) { assert.Equal(t, big.NewInt(904), highestCommitBlock.Number) } +func TestProcessVoteMsgFailIfVerifyBlockInfoFail(t *testing.T) { + blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, 0) + engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 + + // Set round to 5 + engineV2.SetNewRoundFaker(blockchain, utils.Round(5), false) + + // Start with vote messages + blockInfo := &utils.BlockInfo{ + Hash: currentBlock.ParentHash(), + Round: utils.Round(5), + Number: big.NewInt(905), + } + voteSigningHash := utils.VoteSigHash(blockInfo) + // Create two vote message which will not reach vote pool threshold + signedHash := SignHashByPK(acc1Key, voteSigningHash.Bytes()) + voteMsg := &utils.Vote{ + ProposedBlockInfo: blockInfo, + Signature: signedHash, + } + + err := engineV2.VoteHandler(blockchain, voteMsg) + assert.Nil(t, err) + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker() + // initialised with nil and 0 round + assert.Nil(t, lockQuorumCert) + assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round) + + assert.Equal(t, utils.Round(5), currentRound) + voteMsg = &utils.Vote{ + ProposedBlockInfo: blockInfo, + Signature: SignHashByPK(acc2Key, voteSigningHash.Bytes()), + } + err = engineV2.VoteHandler(blockchain, voteMsg) + assert.Nil(t, err) + currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker() + assert.Equal(t, utils.Round(5), currentRound) + + // Create a vote message that should trigger vote pool hook + voteMsg = &utils.Vote{ + ProposedBlockInfo: blockInfo, + Signature: SignHashByPK(acc3Key, voteSigningHash.Bytes()), + } + + err = engineV2.VoteHandler(blockchain, voteMsg) + expectedError := fmt.Errorf("[VerifyBlockInfo] chain header number does not match for the received blockInfo at hash: %v", blockInfo.Hash.Hex()) + assert.Equal(t, expectedError, err) +} + func TestVerifyVoteMsg(t *testing.T) { blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, 0) engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 blockInfo := &utils.BlockInfo{ Hash: currentBlock.Hash(), - Round: utils.Round(15), + Round: utils.Round(14), Number: big.NewInt(915), } - // Invalid vote msg + // Valid message but disqualified as the round does not match voteMsg := &utils.Vote{ ProposedBlockInfo: blockInfo, Signature: []byte{1}, } - + engineV2.SetNewRoundFaker(blockchain, utils.Round(15), false) verified, err := engineV2.VerifyVoteMessage(blockchain, voteMsg) assert.False(t, verified) - assert.NotNil(t, err) + assert.Nil(t, err) + + // Invalid vote message with wrong signature + engineV2.SetNewRoundFaker(blockchain, utils.Round(14), false) + verified, err = engineV2.VerifyVoteMessage(blockchain, voteMsg) + assert.False(t, verified) + assert.Equal(t, "Error while verifying message: invalid signature length", err.Error()) // Valid vote message from a master node signHash, _ := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(blockInfo).Bytes()) diff --git a/eth/bft/bft_hander_test.go b/eth/bft/bft_hander_test.go index 07f0edd8fe..dbd8b3a9e4 100644 --- a/eth/bft/bft_hander_test.go +++ b/eth/bft/bft_hander_test.go @@ -145,6 +145,87 @@ func TestNotBoardcastInvalidVote(t *testing.T) { } } +func TestBoardcastButNotProcessDisqualifiedVotes(t *testing.T) { + tester := newTester() + handlerCounter := uint32(0) + broadcastCounter := uint32(0) + targetVotes := 0 + + tester.bfter.consensus.verifyVote = func(chain consensus.ChainReader, vote *utils.Vote) (bool, error) { + return false, nil // return false but with nil in error means the message is valid but disqualified + } + + tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *utils.Vote) error { + atomic.AddUint32(&handlerCounter, 1) + return nil + } + tester.bfter.broadcast.Vote = func(*utils.Vote) { + atomic.AddUint32(&broadcastCounter, 1) + } + + vote := utils.Vote{ProposedBlockInfo: &utils.BlockInfo{}} + tester.bfter.Vote(&vote) + + time.Sleep(50 * time.Millisecond) + if int(handlerCounter) != targetVotes || int(broadcastCounter) != 1 { + t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetVotes) + } +} + +func TestBoardcastButNotProcessDisqualifiedTimeout(t *testing.T) { + tester := newTester() + handlerCounter := uint32(0) + broadcastCounter := uint32(0) + targetTimeout := 0 + + tester.bfter.consensus.verifyTimeout = func(chain consensus.ChainReader, timeout *utils.Timeout) (bool, error) { + return false, nil // return false but with nil in error means the message is valid but disqualified + } + + tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *utils.Timeout) error { + atomic.AddUint32(&handlerCounter, 1) + return nil + } + tester.bfter.broadcast.Timeout = func(*utils.Timeout) { + atomic.AddUint32(&broadcastCounter, 1) + } + + timeout := utils.Timeout{} + tester.bfter.Timeout(&timeout) + + time.Sleep(50 * time.Millisecond) + if int(handlerCounter) != targetTimeout || int(broadcastCounter) != 1 { + t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetTimeout) + } +} + +func TestBoardcastButNotProcessDisqualifiedSyncInfo(t *testing.T) { + tester := newTester() + handlerCounter := uint32(0) + broadcastCounter := uint32(0) + targetSyncInfo := 0 + + tester.bfter.consensus.verifySyncInfo = func(chain consensus.ChainReader, syncInfo *utils.SyncInfo) (bool, error) { + return false, nil // return false but with nil in error means the message is valid but disqualified + } + + tester.bfter.consensus.syncInfoHandler = func(chain consensus.ChainReader, syncInfo *utils.SyncInfo) error { + atomic.AddUint32(&handlerCounter, 1) + return nil + } + tester.bfter.broadcast.SyncInfo = func(*utils.SyncInfo) { + atomic.AddUint32(&broadcastCounter, 1) + } + + syncInfo := utils.SyncInfo{} + tester.bfter.SyncInfo(&syncInfo) + + time.Sleep(50 * time.Millisecond) + if int(handlerCounter) != targetSyncInfo || int(broadcastCounter) != 1 { + t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetSyncInfo) + } +} + // TODO: SyncInfo and Timeout Test, should be same as Vote. // Once all test on vote covered, then duplicate to others @@ -198,9 +279,7 @@ func TestTimeoutHandlerRoundNotEqual(t *testing.T) { } } - tester.bfter.broadcast.Timeout = func(*utils.Timeout) { - return - } + tester.bfter.broadcast.Timeout = func(*utils.Timeout) {} timeoutMsg := &utils.Timeout{} diff --git a/eth/bft/bft_handler.go b/eth/bft/bft_handler.go index ce1ce899df..5977efefb9 100644 --- a/eth/bft/bft_handler.go +++ b/eth/bft/bft_handler.go @@ -1,8 +1,6 @@ package bft import ( - "fmt" - "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" @@ -40,7 +38,7 @@ type ConsensusFns struct { verifyTimeout func(consensus.ChainReader, *utils.Timeout) (bool, error) timeoutHandler func(consensus.ChainReader, *utils.Timeout) error - verifySyncInfo func(consensus.ChainReader, *utils.SyncInfo) error + verifySyncInfo func(consensus.ChainReader, *utils.SyncInfo) (bool, error) syncInfoHandler func(consensus.ChainReader, *utils.SyncInfo) error } @@ -88,25 +86,25 @@ func (b *Bfter) Vote(vote *utils.Vote) error { verified, err := b.consensus.verifyVote(b.blockChainReader, vote) - if err != nil || !verified { - log.Error("Verify BFT Vote", "error", err, "verified", verified) - if !verified { - return fmt.Errorf("Fail to verify vote") - } + if err != nil { + log.Error("Verify BFT Vote", "error", err) return err } b.broadcastCh <- vote - err = b.consensus.voteHandler(b.blockChainReader, vote) - if err != nil { - if _, ok := err.(*utils.ErrIncomingMessageRoundTooFarFromCurrentRound); ok { - log.Warn("vote round not equal", "error", err, "vote", vote.Hash()) + if verified { + err = b.consensus.voteHandler(b.blockChainReader, vote) + if err != nil { + if _, ok := err.(*utils.ErrIncomingMessageRoundTooFarFromCurrentRound); ok { + log.Warn("vote round not equal", "error", err, "vote", vote.Hash()) + return err + } + log.Error("handle BFT Vote", "error", err) return err } - log.Error("handle BFT Vote", "error", err) - return err } + return nil } func (b *Bfter) Timeout(timeout *utils.Timeout) error { @@ -117,24 +115,23 @@ func (b *Bfter) Timeout(timeout *utils.Timeout) error { } verified, err := b.consensus.verifyTimeout(b.blockChainReader, timeout) if err != nil { - log.Error("Verify BFT Timeout", "error", err) + log.Error("Verify BFT Timeout", "timeoutRound", timeout.Round, "timeoutGapNum", timeout.GapNumber, "error", err) return err } - if !verified { - log.Warn("Timeout message failed to verify", "timeoutRound", timeout.Round, "timeoutGapNum", timeout.GapNumber) - return fmt.Errorf("Fail to verify the received timeout message") - } - b.broadcastCh <- timeout - err = b.consensus.timeoutHandler(b.blockChainReader, timeout) - if err != nil { - if _, ok := err.(*utils.ErrIncomingMessageRoundNotEqualCurrentRound); ok { - log.Warn("timeout round not equal", "error", err) + b.broadcastCh <- timeout + if verified { + err = b.consensus.timeoutHandler(b.blockChainReader, timeout) + if err != nil { + if _, ok := err.(*utils.ErrIncomingMessageRoundNotEqualCurrentRound); ok { + log.Warn("timeout round not equal", "error", err) + return err + } + log.Error("handle BFT Timeout", "error", err) return err } - log.Error("handle BFT Timeout", "error", err) - return err } + return nil } func (b *Bfter) SyncInfo(syncInfo *utils.SyncInfo) error { @@ -143,18 +140,20 @@ func (b *Bfter) SyncInfo(syncInfo *utils.SyncInfo) error { log.Trace("Discarded SyncInfo, known SyncInfo", "hash", syncInfo.Hash()) return nil } - err := b.consensus.verifySyncInfo(b.blockChainReader, syncInfo) + verified, err := b.consensus.verifySyncInfo(b.blockChainReader, syncInfo) if err != nil { log.Error("Verify BFT SyncInfo", "error", err) return err } b.broadcastCh <- syncInfo - - err = b.consensus.syncInfoHandler(b.blockChainReader, syncInfo) - if err != nil { - log.Error("handle BFT SyncInfo", "error", err) - return err + // Process only if verified and qualified + if verified { + err = b.consensus.syncInfoHandler(b.blockChainReader, syncInfo) + if err != nil { + log.Error("handle BFT SyncInfo", "error", err) + return err + } } return nil }