XIN-159, 160 and 161 (#69)

* XIN-159, 160 and 161

* update the bft handler to make sure we don't process dis-qualified messages

* add verify header missing checks and its tests
This commit is contained in:
Jerome 2022-03-13 22:00:26 +11:00 committed by GitHub
parent a4b362ae9a
commit 9bb1a6e1b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 547 additions and 233 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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{},

View file

@ -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)

View file

@ -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,

View file

@ -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)

View file

@ -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)
}

View file

@ -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)

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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())

View file

@ -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{}

View file

@ -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
}