add ProcessQC tests

This commit is contained in:
Jianrong 2021-11-26 20:26:39 +11:00
parent bd60e1b0cf
commit 163ed0fab3
10 changed files with 321 additions and 262 deletions

View file

@ -59,9 +59,6 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
}
// Add callback to the timer
timer.OnTimeoutFn = engine.onCountdownTimeout
// Attach vote & timeout pool callback function when it reached threshold
votePool.SetOnThresholdFn(engine.onVotePoolThresholdReached)
timeoutPool.SetOnThresholdFn(engine.onTimeoutPoolThresholdReached)
return engine
}
@ -70,6 +67,8 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
Testing tools
*/
func (x *XDPoS_v2) SetNewRoundFaker(newRound utils.Round, resetTimer bool) {
x.lock.Lock()
defer x.lock.Unlock()
// Reset a bunch of things
if resetTimer {
x.timeoutWorker.Reset()
@ -78,10 +77,10 @@ func (x *XDPoS_v2) SetNewRoundFaker(newRound utils.Round, resetTimer bool) {
}
// Utils for test to check currentRound value
func (x *XDPoS_v2) GetCurrentRound() utils.Round {
func (x *XDPoS_v2) GetProperties() (utils.Round, *utils.QuorumCert, *utils.QuorumCert) {
x.lock.Lock()
defer x.lock.Unlock()
return x.currentRound
return x.currentRound, x.lockQuorumCert, x.highestQuorumCert
}
// Authorize injects a private key into the consensus engine to mint new blocks with.
@ -149,7 +148,7 @@ func (x *XDPoS_v2) VerifyVoteMessage(vote *utils.Vote) (bool, error) {
}
// Consensus entry point for processing vote message to produce QC
func (x *XDPoS_v2) VoteHandler(voteMsg *utils.Vote) error {
func (x *XDPoS_v2) VoteHandler(chain consensus.ChainReader, voteMsg *utils.Vote) error {
x.lock.Lock()
defer x.lock.Unlock()
@ -159,13 +158,15 @@ func (x *XDPoS_v2) VoteHandler(voteMsg *utils.Vote) error {
}
// Collect vote
thresholdReached, numberOfVotesInPool, hookError := x.votePool.Add(voteMsg)
if hookError != nil {
log.Error("Error while adding vote message to the pool, ", hookError)
return hookError
thresholdReached, numberOfVotesInPool, pooledVotes := x.votePool.Add(voteMsg)
if thresholdReached {
log.Debug("Vote pool threashold reached: %v, number of items in the pool: %v", thresholdReached, numberOfVotesInPool)
err := x.onVotePoolThresholdReached(chain, pooledVotes, voteMsg)
if err != nil {
return nil
}
}
log.Debug("Vote pool threashold reached: %v, number of items in the pool: %v", thresholdReached, numberOfVotesInPool)
return nil
}
@ -173,7 +174,7 @@ func (x *XDPoS_v2) VoteHandler(voteMsg *utils.Vote) error {
Function that will be called by votePool when it reached threshold.
In the engine v2, we will need to generate and process QC
*/
func (x *XDPoS_v2) onVotePoolThresholdReached(pooledVotes map[common.Hash]utils.PoolObj, currentVoteMsg utils.PoolObj) error {
func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, pooledVotes map[common.Hash]utils.PoolObj, currentVoteMsg utils.PoolObj) error {
signatures := []utils.Signature{}
for _, v := range pooledVotes {
signatures = append(signatures, v.(*utils.Vote).Signature)
@ -183,7 +184,7 @@ func (x *XDPoS_v2) onVotePoolThresholdReached(pooledVotes map[common.Hash]utils.
ProposedBlockInfo: currentVoteMsg.(*utils.Vote).ProposedBlockInfo,
Signatures: signatures,
}
err := x.processQC(quorumCert)
err := x.processQC(chain, quorumCert)
if err != nil {
log.Error("Error while processing QC in the Vote handler after reaching pool threshold, ", err)
return err
@ -211,7 +212,7 @@ func (x *XDPoS_v2) VerifyTimeoutMessage(timeoutMsg *utils.Timeout) (bool, error)
Entry point for handling timeout message to process below:
1. checkRoundNumber()
2. Collect timeout
Once timeout pool reached threshold, it will trigger the call to the hook function "onTimeoutPoolThresholdReached"
3. Once timeout pool reached threshold, it will trigger the call to the function "onTimeoutPoolThresholdReached"
*/
func (x *XDPoS_v2) TimeoutHandler(timeout *utils.Timeout) error {
x.lock.Lock()
@ -222,12 +223,15 @@ func (x *XDPoS_v2) TimeoutHandler(timeout *utils.Timeout) error {
return fmt.Errorf("Timeout message round number: %v does not match currentRound: %v", timeout.Round, x.currentRound)
}
// Collect timeout, generate TC
isThresholdReached, numberOfTimeoutsInPool, hookError := x.timeoutPool.Add(timeout)
if hookError != nil {
log.Error("Error adding timeout to the pool, ", hookError.Error())
return hookError
isThresholdReached, numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout)
// Threshold reached
if isThresholdReached {
log.Debug("Timeout pool threashold reached: %v, number of items in the pool: %v", isThresholdReached, numberOfTimeoutsInPool)
err := x.onTimeoutPoolThresholdReached(pooledTimeouts, timeout)
if err != nil {
return err
}
}
log.Debug("Timeout pool threashold reached: %v, number of items in the pool: %v", isThresholdReached, numberOfTimeoutsInPool)
return nil
}
@ -309,22 +313,28 @@ func (x *XDPoS_v2) verifyTC(timeoutCert *utils.TimeoutCert) error {
return nil
}
// Update local QC variables including highestQC & lockQuorumCert, as well as update commit blockInfo before call
// Update local QC variables including highestQC & lockQuorumCert, as well as commit the blocks that satisfy the algorithm requirements
func (x *XDPoS_v2) processQC(blockCahinReader consensus.ChainReader, quorumCert *utils.QuorumCert) error {
// 1. Update HighestQC
if x.highestQuorumCert == nil || quorumCert.ProposedBlockInfo.Round > x.highestQuorumCert.ProposedBlockInfo.Round {
if x.highestQuorumCert == nil || (quorumCert.ProposedBlockInfo.Round > x.highestQuorumCert.ProposedBlockInfo.Round) {
//TODO: do I need a clone?
x.highestQuorumCert = quorumCert
}
// 2. Get QC from header and update lockQuorumCert
// 2. Get QC from header and update lockQuorumCert(lockQuorumCert is the parent of highestQC)
proposedBlockHeader := blockCahinReader.GetHeaderByHash(quorumCert.ProposedBlockInfo.Hash)
var decodedExtraField utils.ExtraFields_v2
utils.DecodeBytesExtraFields(proposedBlockHeader.Extra, &decodedExtraField)
err := utils.DecodeBytesExtraFields(proposedBlockHeader.Extra, &decodedExtraField)
if err != nil {
return err
}
x.lockQuorumCert = &decodedExtraField.QuorumCert
proposedBlockRound := &decodedExtraField.Round
// 3. Update commit block info
//TODO: find parent and grandparent and grandgrandparent block, check round number, if so, commit grandgrandparent
_, err = x.commitBlocks(blockCahinReader, proposedBlockHeader, proposedBlockRound)
if err != nil {
return err
}
if quorumCert.ProposedBlockInfo.Round >= x.currentRound {
err := x.setNewRound(quorumCert.ProposedBlockInfo.Round + 1)
if err != nil {
@ -481,3 +491,31 @@ func (x *XDPoS_v2) getSyncInfo() utils.SyncInfo {
HighestTimeoutCert: x.highestTimeoutCert,
}
}
//TODO: find parent and grandparent and grandgrandparent block, check round number, if so, commit grandgrandparent
func (x *XDPoS_v2) commitBlocks(blockCahinReader consensus.ChainReader, proposedBlockHeader *types.Header, proposedBlockRound *utils.Round) (bool, error) {
// Find the last two parent block and check their rounds are the continous
parentBlock := blockCahinReader.GetHeaderByHash(proposedBlockHeader.ParentHash)
var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(parentBlock.Extra, &decodedExtraField)
if err != nil {
return false, err
}
if *proposedBlockRound-1 != decodedExtraField.Round {
return false, nil
}
// If parent round is continous, we check grandparent
grandParentBlock := blockCahinReader.GetHeaderByHash(parentBlock.ParentHash)
err = utils.DecodeBytesExtraFields(grandParentBlock.Extra, &decodedExtraField)
if err != nil {
return false, err
}
if *proposedBlockRound-2 != decodedExtraField.Round {
return false, nil
}
// TODO: Commit the grandParent block
return true, nil
}

View file

@ -1,8 +1,6 @@
package utils
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/common"
)
@ -11,9 +9,8 @@ type PoolObj interface {
PoolKey() string
}
type Pool struct {
objList map[string]map[common.Hash]PoolObj
threshold int
onThresholdFn func(objsInPool map[common.Hash]PoolObj, currentObj PoolObj) error
objList map[string]map[common.Hash]PoolObj
threshold int
}
func NewPool(threshold int) *Pool {
@ -23,8 +20,8 @@ func NewPool(threshold int) *Pool {
}
}
// call the hook function onThresholdFn if reached threshold and return boolean to indicate whether pool has reached threshold
func (p *Pool) Add(obj PoolObj) (bool, int, error) {
// return true if it has reached threshold
func (p *Pool) Add(obj PoolObj) (bool, int, map[common.Hash]PoolObj) {
poolKey := obj.PoolKey()
objListKeyed, ok := p.objList[poolKey]
if !ok {
@ -35,13 +32,9 @@ func (p *Pool) Add(obj PoolObj) (bool, int, error) {
numOfItems := len(objListKeyed)
if numOfItems >= p.threshold {
delete(p.objList, poolKey)
if p.onThresholdFn != nil {
return true, numOfItems, p.onThresholdFn(objListKeyed, obj)
} else {
return true, numOfItems, fmt.Errorf("no call back function for pool")
}
return true, numOfItems, objListKeyed
}
return false, numOfItems, nil
return false, numOfItems, objListKeyed
}
func (p *Pool) Clear() {
@ -51,7 +44,3 @@ func (p *Pool) Clear() {
func (p *Pool) SetThreshold(t int) {
p.threshold = t
}
func (p *Pool) SetOnThresholdFn(f func(objsInPool map[common.Hash]PoolObj, currentObj PoolObj) error) {
p.onThresholdFn = f
}

View file

@ -1,144 +1,55 @@
package utils
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/stretchr/testify/assert"
)
func TestPoolWithTimeout(t *testing.T) {
func TestPoolAdd(t *testing.T) {
assert := assert.New(t)
var ret int
onThresholdFn := func(po map[common.Hash]PoolObj, currentPoolObj PoolObj) error {
for _, m := range po {
if _, ok := m.(*Timeout); ok {
ret += 1
} else {
t.Fatalf("wrong type passed into pool: %v", m)
}
}
return nil
}
pool := NewPool(2) // 2 is the cert threshold
ret = 0
pool.SetOnThresholdFn(onThresholdFn)
timeout1 := Timeout{Round: 1, Signature: []byte{1}}
timeout2 := Timeout{Round: 1, Signature: []byte{2}}
timeout3 := Timeout{Round: 1, Signature: []byte{3}}
_, numOfItems, err := pool.Add(&timeout1)
assert.Nil(err)
thresholdReached, numOfItems, pooledTimeouts := pool.Add(&timeout1)
assert.NotNil(pooledTimeouts)
assert.Equal(1, numOfItems)
_, numOfItems, err = pool.Add(&timeout1)
assert.Nil(err)
assert.False(thresholdReached)
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout1)
assert.NotNil(pooledTimeouts)
assert.False(thresholdReached)
// Duplicates should not be added
assert.Equal(1, numOfItems)
assert.Equal(0, ret)
_, numOfItems, err = pool.Add(&timeout2)
assert.Nil(err)
assert.Equal(2, ret)
_, numOfItems, err = pool.Add(&timeout3)
assert.Nil(err)
assert.Equal(2, ret)
// Should add the one that is not a duplicates
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout2)
assert.True(thresholdReached)
assert.NotNil(pooledTimeouts)
assert.Equal(2, numOfItems)
// Try to add one more to the same round, but that round threshold has already been reached, hence deleted
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout3)
assert.False(thresholdReached)
assert.NotNil(pooledTimeouts)
assert.Equal(1, numOfItems)
pool = NewPool(3) // 3 is the cert size
ret = 0
pool.SetOnThresholdFn(onThresholdFn)
_, numOfItems, err = pool.Add(&timeout1)
assert.Nil(err)
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout1)
assert.False(thresholdReached)
assert.NotNil(pooledTimeouts)
assert.Equal(1, numOfItems)
_, numOfItems, err = pool.Add(&timeout2)
assert.Nil(err)
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout2)
assert.False(thresholdReached)
assert.Equal(2, numOfItems)
assert.Equal(ret, 0)
assert.NotNil(pooledTimeouts)
pool.Clear()
_, numOfItems, err = pool.Add(&timeout3)
assert.Nil(err)
// Pool has been cleared. Start from 0 again
thresholdReached, numOfItems, pooledTimeouts = pool.Add(&timeout3)
assert.False(thresholdReached)
assert.Equal(1, numOfItems)
assert.Equal(0, ret)
}
func TestPoolWithVote(t *testing.T) {
assert := assert.New(t)
var ret int
onThresholdFn := func(po map[common.Hash]PoolObj, currentPoolObj PoolObj) error {
for _, m := range po {
if _, ok := m.(*Vote); ok {
ret += 1
} else {
t.Fatalf("wrong type passed into pool: %v", m)
}
}
return nil
}
pool := NewPool(2) // 2 is the cert threshold
ret = 0
pool.SetOnThresholdFn(onThresholdFn)
blockInfo1 := BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: 1, Number: big.NewInt(1)}
blockInfo2 := BlockInfo{Hash: common.BigToHash(big.NewInt(4095)), Round: 1, Number: big.NewInt(1)}
vote1 := Vote{ProposedBlockInfo: blockInfo1, Signature: []byte{1}}
vote2 := Vote{ProposedBlockInfo: blockInfo2, Signature: []byte{2}}
vote3 := Vote{ProposedBlockInfo: blockInfo1, Signature: []byte{3}}
_, numOfItems, err := pool.Add(&vote1)
assert.Nil(err)
assert.Equal(1, numOfItems)
// Duplicates should not be added
_, numOfItems, err = pool.Add(&vote1)
assert.Nil(err)
assert.Equal(1, numOfItems)
assert.Equal(ret, 0)
_, numOfItems, err = pool.Add(&vote2)
assert.Nil(err)
// vote2 is on a different blockInfo to vote1
assert.Equal(1, numOfItems)
assert.Equal(0, ret)
_, numOfItems, err = pool.Add(&vote3)
assert.Nil(err)
assert.Equal(2, numOfItems)
assert.Equal(2, ret)
pool = NewPool(3) // 3 is the cert size
ret = 0
pool.SetOnThresholdFn(onThresholdFn)
_, numOfItems, err = pool.Add(&vote1)
assert.Nil(err)
assert.Equal(1, numOfItems)
// vote2 is on a different blockInfo to vote1
_, numOfItems, err = pool.Add(&vote2)
assert.Nil(err)
assert.Equal(1, numOfItems)
_, numOfItems, err = pool.Add(&vote3)
assert.Nil(err)
assert.Equal(2, numOfItems)
assert.Equal(0, ret)
pool.Clear()
assert.Empty(pool.objList)
pool = NewPool(2) // 2 is the cert size
ret = 0
pool.SetOnThresholdFn(onThresholdFn)
_, numOfItems, err = pool.Add(&vote1)
assert.Nil(err)
assert.Equal(1, numOfItems)
// vote2 is on a different blockInfo to vote1
_, numOfItems, err = pool.Add(&vote2)
assert.Nil(err)
assert.Equal(1, numOfItems)
_, numOfItems, err = pool.Add(&vote3)
assert.Nil(err)
assert.Equal(2, numOfItems)
assert.Equal(1, len(pool.objList)) //vote for one hash is cleared, but another remains
pool.Clear()
assert.Empty(pool.objList)
assert.NotNil(pooledTimeouts)
}

View file

@ -19,7 +19,7 @@ type broadcastTimeoutFn func(*utils.Timeout)
type broadcastSyncInfoFn func(*utils.SyncInfo)
type Bfter struct {
blockCahinReader *core.BlockChain
blockCahinReader consensus.ChainReader
broadcastCh chan interface{}
quit chan struct{}
consensus ConsensusFns
@ -33,7 +33,7 @@ type Bfter struct {
type ConsensusFns struct {
verifyVote func(*utils.Vote) error
voteHandler func(*utils.Vote) error
voteHandler func(consensus.ChainReader, *utils.Vote) error
verifyTimeout func(*utils.Timeout) error
timeoutHandler func(*utils.Timeout) error
@ -78,63 +78,70 @@ func (b *Bfter) SetConsensusFuns(engine consensus.Engine) {
}
// TODO: rename
func (b *Bfter) Vote(vote *utils.Vote) {
func (b *Bfter) Vote(vote *utils.Vote) error {
log.Trace("Receive Vote", "vote", vote)
if b.knownVotes.Contains(vote.Hash()) {
log.Trace("Discarded vote, known vote", "Signature", vote.Signature, "hash", vote.Hash())
return
return nil
}
err := b.consensus.verifyVote(vote)
if err != nil {
log.Error("Verify BFT Vote", "error", err)
return
}
err = b.consensus.voteHandler(vote)
if err != nil {
log.Error("handle BFT Vote", "error", err)
return
return err
}
b.knownVotes.Add(vote.Hash(), true)
b.broadcastCh <- vote
err = b.consensus.voteHandler(b.blockCahinReader, vote)
if err != nil {
log.Error("handle BFT Vote", "error", err)
return err
}
return nil
}
func (b *Bfter) Timeout(timeout *utils.Timeout) {
func (b *Bfter) Timeout(timeout *utils.Timeout) error {
log.Trace("Receive Timeout", "timeout", timeout)
if b.knownVotes.Contains(timeout.Hash()) {
log.Trace("Discarded Timeout, known Timeout", "Signature", timeout.Signature, "hash", timeout.Hash(), "round", timeout.Round)
return
return nil
}
err := b.consensus.verifyTimeout(timeout)
if err != nil {
log.Error("Verify BFT Timeout", "error", err)
return
}
err = b.consensus.timeoutHandler(timeout)
if err != nil {
log.Error("handle BFT Timeout", "error", err)
return
return err
}
b.knownTimeouts.Add(timeout.Hash(), true)
b.broadcastCh <- timeout
err = b.consensus.timeoutHandler(timeout)
if err != nil {
log.Error("handle BFT Timeout", "error", err)
return err
}
return nil
}
func (b *Bfter) SyncInfo(syncInfo *utils.SyncInfo) {
func (b *Bfter) SyncInfo(syncInfo *utils.SyncInfo) error {
log.Trace("Receive SyncInfo", "syncInfo", syncInfo)
if b.knownVotes.Contains(syncInfo.Hash()) {
log.Trace("Discarded SyncInfo, known SyncInfo", "hash", syncInfo.Hash())
return
return nil
}
err := b.consensus.verifySyncInfo(syncInfo)
if err != nil {
log.Error("Verify BFT SyncInfo", "error", err)
return
return err
}
b.knownSyncInfos.Add(syncInfo.Hash(), true)
b.broadcastCh <- syncInfo
err = b.consensus.syncInfoHandler(syncInfo)
if err != nil {
log.Error("handle BFT SyncInfo", "error", err)
return
return err
}
b.knownSyncInfos.Add(syncInfo.Hash(), true)
b.broadcastCh <- syncInfo
return nil
}
// Start Bft receiver

View file

@ -6,9 +6,11 @@ import (
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core"
)
// make different votes based on Signatures
@ -29,9 +31,10 @@ type bfterTester struct {
func newTester() *bfterTester {
testConsensus := &XDPoS.XDPoS{EngineV2: &engine_v2.XDPoS_v2{}}
broadcasts := BroadcastFns{}
blockChain := &core.BlockChain{}
tester := &bfterTester{}
tester.bfter = New(broadcasts)
tester.bfter = New(broadcasts, blockChain)
tester.bfter.SetConsensusFuns(testConsensus)
tester.bfter.broadcastCh = make(chan interface{})
tester.bfter.Start()
@ -52,7 +55,7 @@ func TestSequentialVotes(t *testing.T) {
return nil
}
tester.bfter.consensus.voteHandler = func(vote *utils.Vote) error {
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *utils.Vote) error {
atomic.AddUint32(&handlerCounter, 1)
return nil
}
@ -63,7 +66,10 @@ func TestSequentialVotes(t *testing.T) {
votes := makeVotes(targetVotes)
for _, vote := range votes {
tester.bfter.Vote(&vote)
err := tester.bfter.Vote(&vote)
if err != nil {
t.Fatal(err)
}
}
time.Sleep(100 * time.Millisecond)
@ -86,7 +92,7 @@ func TestDuplicateVotes(t *testing.T) {
return nil
}
tester.bfter.consensus.voteHandler = func(vote *utils.Vote) error {
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *utils.Vote) error {
atomic.AddUint32(&handlerCounter, 1)
return nil
}
@ -118,7 +124,7 @@ func TestNotBoardcastInvalidVote(t *testing.T) {
return fmt.Errorf("This is invalid vote")
}
tester.bfter.consensus.voteHandler = func(vote *utils.Vote) error {
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *utils.Vote) error {
atomic.AddUint32(&handlerCounter, 1)
return nil
}

View file

@ -28,7 +28,7 @@ func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) {
// Insert block 11
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 11)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block11, err := insertBlock(blockchain, 11, blockCoinBase, currentBlock, merkleRoot, 1)
block11, err := insertBlock(blockchain, 11, blockCoinBase, currentBlock, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
}

View file

@ -54,7 +54,7 @@ func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
// Insert block 450
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block, err := insertBlock(blockchain, 450, blockCoinBase, parentBlock, merkleRoot, 1)
block, err := insertBlock(blockchain, 450, blockCoinBase, parentBlock, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
}
@ -113,7 +113,7 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
// Now, let's mine another block to trigger the GAP block signerList update
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
block450, err := insertBlock(blockchain, 450, block450CoinbaseAddress, parentBlock, merkleRoot, 1)
block450, err := insertBlock(blockchain, 450, block450CoinbaseAddress, parentBlock, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
}
@ -240,7 +240,7 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
@ -378,7 +378,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
@ -440,7 +440,7 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
// Insert normal blocks 450 A
blockCoinBase450A := "0xaaa0000000000000000000000000000000000450"
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block450A, err := insertBlock(blockchain, 450, blockCoinBase450A, parentBlock, merkleRoot, 1)
block450A, err := insertBlock(blockchain, 450, blockCoinBase450A, parentBlock, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
}
@ -476,21 +476,21 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
// Insert forked Block 450 B
blockCoinBase450B := "0xbbb0000000000000000000000000000000000450"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block450B, err := insertBlock(blockchain, 450, blockCoinBase450B, parentBlock, merkleRoot, 1)
block450B, err := insertBlock(blockchain, 450, blockCoinBase450B, parentBlock, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
}
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
}
blockCoinBase452B := "0xbbb0000000000000000000000000000000000452"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block452B, err := insertBlock(blockchain, 452, blockCoinBase452B, block451B, merkleRoot, 1)
block452B, err := insertBlock(blockchain, 452, blockCoinBase452B, block451B, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
}

View file

@ -11,7 +11,7 @@ import (
// Snapshot try to read before blockchain is written
func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, _ := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
state, err := blockchain.State()
if err != nil {
@ -104,7 +104,7 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 3)
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, nil, 3)
if err != nil {
t.Fatal(err)

View file

@ -10,10 +10,12 @@ import (
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
"github.com/XinFinOrg/XDPoSChain/core"
. "github.com/XinFinOrg/XDPoSChain/core"
@ -243,7 +245,71 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params
for i := 1; i <= numOfBlocks; i++ {
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, 1)
block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, nil, 1)
if err != nil {
t.Fatal(err)
}
currentBlock = block
}
// Update Signer as there is no previous signer assigned
err = UpdateSigner(blockchain)
if err != nil {
t.Fatal(err)
}
return blockchain, backend, currentBlock, signer
}
func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address) {
// Preparation
var err error
backend := getCommonBackend(t, chainConfig)
blockchain := backend.GetBlockChain()
blockchain.Client = backend
// Authorise
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
if err != nil {
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
}
blockchain.Engine().(*XDPoS.XDPoS).Authorize(signer, signFn)
currentBlock := blockchain.Genesis()
// Insert initial blocks
for i := 1; i <= numOfBlocks; i++ {
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
// Build engine v2 compatible extra data field
proposedBlockInfo := utils.BlockInfo{
Hash: currentBlock.Hash(),
Round: utils.Round(i),
Number: big.NewInt(int64(i)),
}
// 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))
}
var signatures []utils.Signature
signatures = append(signatures, signedHash)
quorumCert := utils.QuorumCert{
ProposedBlockInfo: proposedBlockInfo,
Signatures: signatures,
}
extra := utils.ExtraFields_v2{
Round: utils.Round(i),
QuorumCert: quorumCert,
}
extraInBytes, err := extra.EncodeToBytes()
if err != nil {
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
}
block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, extraInBytes, 1)
if err != nil {
t.Fatal(err)
}
@ -259,13 +325,14 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params
}
// insert Block without transcation attached
func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, root string, difficulty int64) (*types.Block, error) {
func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, root string, customExtra []byte, difficulty int64) (*types.Block, error) {
block, err := createXDPoSTestBlock(
blockchain,
parentBlock.Hash().Hex(),
blockCoinBase, blockNum, nil,
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
common.HexToHash(root),
customExtra,
difficulty,
)
if err != nil {
@ -287,6 +354,7 @@ func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string,
blockCoinBase, blockNum, txs,
"0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c",
common.HexToHash(root),
nil,
difficulty,
)
if err != nil {
@ -300,11 +368,14 @@ func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string,
return block, nil
}
func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash, difficulty int64) (*types.Block, error) {
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
//ReceiptHash = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
//Root := "0xc99c095e53ff1afe3b86750affd13c7550a2d24d51fb8e41b3c3ef2ea8274bcc"
extraByte, _ := hex.DecodeString(extraSubstring)
func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash, customExtra []byte, difficulty int64) (*types.Block, error) {
if customExtra == nil {
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
//ReceiptHash = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
//Root := "0xc99c095e53ff1afe3b86750affd13c7550a2d24d51fb8e41b3c3ef2ea8274bcc"
customExtra, _ = hex.DecodeString(extraSubstring)
}
header := types.Header{
ParentHash: common.HexToHash(parentHash),
UncleHash: types.EmptyUncleHash,
@ -317,14 +388,13 @@ func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number in
Number: big.NewInt(int64(number)),
GasLimit: 1200000000,
Time: big.NewInt(int64(number * 10)),
Extra: extraByte,
Extra: customExtra,
}
var block *types.Block
if len(txs) == 0 {
block = types.NewBlockWithHeader(&header)
} else {
// Prepare Receipt
statedb, err := bc.StateAt(bc.GetBlockByNumber(uint64(number - 1)).Root()) //Get parent root
if err != nil {

View file

@ -42,14 +42,16 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
err := engineV2.TimeoutHandler(timeoutMsg)
assert.Nil(t, err)
assert.Equal(t, utils.Round(1), engineV2.GetCurrentRound())
currentRound, _, _ := engineV2.GetProperties()
assert.Equal(t, utils.Round(1), currentRound)
timeoutMsg = &utils.Timeout{
Round: utils.Round(1),
Signature: []byte{2},
}
err = engineV2.TimeoutHandler(timeoutMsg)
assert.Nil(t, err)
assert.Equal(t, utils.Round(1), engineV2.GetCurrentRound())
currentRound, _, _ = engineV2.GetProperties()
assert.Equal(t, utils.Round(1), currentRound)
// Create a timeout message that should trigger timeout pool hook
timeoutMsg = &utils.Timeout{
Round: utils.Round(1),
@ -60,6 +62,9 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
assert.Nil(t, err)
syncInfoMsg := <-engineV2.BroadcastCh
currentRound, _, _ = engineV2.GetProperties()
assert.NotNil(t, syncInfoMsg)
// Should have QC, however, we did not inilise it, hence will show default empty value
@ -71,7 +76,7 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
assert.Equal(t, tc.Round, utils.Round(1))
sigatures := []utils.Signature{[]byte{1}, []byte{2}, []byte{3}}
assert.ElementsMatch(t, tc.Signatures, sigatures)
assert.Equal(t, utils.Round(2), engineV2.GetCurrentRound())
assert.Equal(t, utils.Round(2), currentRound)
}
func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) {
@ -99,106 +104,129 @@ func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) {
}
// VoteHandler
func TestVoteMessageHandlerSuccessfullyGeneratedQC(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) {
blockchain, _, currentBlock, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// parentBlock := blockchain.GetBlockByHash(currentBlock.ParentHash())
// grandParentBlock := blockchain.GetBlockByHash(parentBlock.ParentHash())
blockInfo := &utils.BlockInfo{
Hash: common.HexToHash("0x1"),
Round: utils.Round(1),
Number: big.NewInt(999),
Hash: currentBlock.Hash(),
Round: utils.Round(11),
Number: big.NewInt(11),
}
// Set round to 1
engineV2.SetNewRoundFaker(utils.Round(1), false)
// Set round to 11
engineV2.SetNewRoundFaker(utils.Round(11), false)
// Create two timeout message which will not reach vote pool threshold
voteMsg := &utils.Vote{
ProposedBlockInfo: *blockInfo,
Signature: []byte{1},
}
err := engineV2.VoteHandler(voteMsg)
err := engineV2.VoteHandler(blockchain, voteMsg)
assert.Nil(t, err)
assert.Equal(t, utils.Round(1), engineV2.GetCurrentRound())
currentRound, lockQuorumCert, highestQuorumCert := engineV2.GetProperties()
// Inilised with nil and 0 round
assert.Nil(t, lockQuorumCert)
assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, utils.Round(11), currentRound)
voteMsg = &utils.Vote{
ProposedBlockInfo: *blockInfo,
Signature: []byte{2},
}
err = engineV2.VoteHandler(voteMsg)
err = engineV2.VoteHandler(blockchain, voteMsg)
assert.Nil(t, err)
assert.Equal(t, utils.Round(1), engineV2.GetCurrentRound())
currentRound, lockQuorumCert, highestQuorumCert = engineV2.GetProperties()
// 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)
// Create a vote message that should trigger vote pool hook
assert.Equal(t, utils.Round(11), currentRound)
// Create a vote message that should trigger vote pool hook and increment the round to 12
voteMsg = &utils.Vote{
ProposedBlockInfo: *blockInfo,
Signature: []byte{3},
}
err = engineV2.VoteHandler(voteMsg)
err = engineV2.VoteHandler(blockchain, voteMsg)
assert.Nil(t, err)
// Check round has now changed from 1 to 2
assert.Equal(t, utils.Round(2), engineV2.GetCurrentRound())
currentRound, lockQuorumCert, highestQuorumCert = engineV2.GetProperties()
// The lockQC shall be the parent's QC round number
assert.Equal(t, utils.Round(11), lockQuorumCert.ProposedBlockInfo.Round)
// The highestQC proposedBlockInfo shall be the same as the one from its votes
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
// Check round has now changed from 11 to 12
assert.Equal(t, utils.Round(12), currentRound)
}
func TestThrowErrorIfVoteMsgRoundNotEqualToCurrentRound(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
blockchain, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
blockInfo := &utils.BlockInfo{
Hash: common.HexToHash("0x1"),
Round: utils.Round(2),
Round: utils.Round(12),
Number: big.NewInt(999),
}
// Set round to 3
engineV2.SetNewRoundFaker(utils.Round(3), false)
// Set round to 13
engineV2.SetNewRoundFaker(utils.Round(13), false)
voteMsg := &utils.Vote{
ProposedBlockInfo: *blockInfo,
Signature: []byte{1},
}
// voteRound > currentRound
err := engineV2.VoteHandler(voteMsg)
err := engineV2.VoteHandler(blockchain, voteMsg)
assert.NotNil(t, err)
assert.Equal(t, "Vote message round number: 2 does not match currentRound: 3", err.Error())
assert.Equal(t, "Vote message round number: 12 does not match currentRound: 13", err.Error())
// Set round to 1
engineV2.SetNewRoundFaker(utils.Round(1), false)
err = engineV2.VoteHandler(voteMsg)
// Set round to 11
engineV2.SetNewRoundFaker(utils.Round(11), false)
err = engineV2.VoteHandler(blockchain, voteMsg)
assert.NotNil(t, err)
// voteRound < currentRound
assert.Equal(t, "Vote message round number: 2 does not match currentRound: 1", err.Error())
assert.Equal(t, "Vote message round number: 12 does not match currentRound: 11", err.Error())
}
func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
blockchain, _, currentBlock, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 1
engineV2.SetNewRoundFaker(utils.Round(1), false)
engineV2.SetNewRoundFaker(utils.Round(11), false)
// Start with vote messages
blockInfo := &utils.BlockInfo{
Hash: common.HexToHash("0x1"),
Round: utils.Round(1),
Number: big.NewInt(999),
Hash: currentBlock.Hash(),
Round: utils.Round(11),
Number: big.NewInt(11),
}
// Create two timeout message which will not reach vote pool threshold
// Create two vote message which will not reach vote pool threshold
voteMsg := &utils.Vote{
ProposedBlockInfo: *blockInfo,
Signature: []byte{1},
}
err := engineV2.VoteHandler(voteMsg)
err := engineV2.VoteHandler(blockchain, voteMsg)
assert.Nil(t, err)
assert.Equal(t, utils.Round(1), engineV2.GetCurrentRound())
currentRound, lockQuorumCert, highestQuorumCert := engineV2.GetProperties()
// Inilised with nil and 0 round
assert.Nil(t, lockQuorumCert)
assert.Equal(t, utils.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, utils.Round(11), currentRound)
voteMsg = &utils.Vote{
ProposedBlockInfo: *blockInfo,
Signature: []byte{2},
}
err = engineV2.VoteHandler(voteMsg)
err = engineV2.VoteHandler(blockchain, voteMsg)
assert.Nil(t, err)
assert.Equal(t, utils.Round(1), engineV2.GetCurrentRound())
currentRound, _, _ = engineV2.GetProperties()
assert.Equal(t, utils.Round(11), currentRound)
// Create a vote message that should trigger vote pool hook
voteMsg = &utils.Vote{
@ -206,42 +234,51 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
Signature: []byte{3},
}
err = engineV2.VoteHandler(voteMsg)
err = engineV2.VoteHandler(blockchain, voteMsg)
assert.Nil(t, err)
// Check round has now changed from 1 to 2
assert.Equal(t, utils.Round(2), engineV2.GetCurrentRound())
// Check round has now changed from 11 to 12
currentRound, lockQuorumCert, highestQuorumCert = engineV2.GetProperties()
// The lockQC shall be the parent's QC round number
assert.Equal(t, utils.Round(11), lockQuorumCert.ProposedBlockInfo.Round)
// The highestQC proposedBlockInfo shall be the same as the one from its votes
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
assert.Equal(t, utils.Round(12), currentRound)
// We shall have highestQuorumCert in engine now, let's do timeout msg to see if we can broadcast SyncInfo which contains both highestQuorumCert and HighestTimeoutCert
// First, all incoming old timeout msg shall not be processed
timeoutMsg := &utils.Timeout{
Round: utils.Round(1),
Round: utils.Round(11),
Signature: []byte{1},
}
err = engineV2.TimeoutHandler(timeoutMsg)
assert.NotNil(t, err)
assert.Equal(t, "Timeout message round number: 1 does not match currentRound: 2", err.Error())
assert.Equal(t, "Timeout message round number: 11 does not match currentRound: 12", err.Error())
// Ok, let's do the timeout msg which is on the same round as the current round by creating two timeout message which will not reach timeout pool threshold
timeoutMsg = &utils.Timeout{
Round: utils.Round(2),
Round: utils.Round(12),
Signature: []byte{1},
}
err = engineV2.TimeoutHandler(timeoutMsg)
assert.Nil(t, err)
assert.Equal(t, utils.Round(2), engineV2.GetCurrentRound())
currentRound, _, _ = engineV2.GetProperties()
assert.Equal(t, utils.Round(12), currentRound)
timeoutMsg = &utils.Timeout{
Round: utils.Round(2),
Round: utils.Round(12),
Signature: []byte{2},
}
err = engineV2.TimeoutHandler(timeoutMsg)
assert.Nil(t, err)
assert.Equal(t, utils.Round(2), engineV2.GetCurrentRound())
currentRound, _, _ = engineV2.GetProperties()
assert.Equal(t, utils.Round(12), currentRound)
// Create a timeout message that should trigger timeout pool hook
timeoutMsg = &utils.Timeout{
Round: utils.Round(2),
Round: utils.Round(12),
Signature: []byte{3},
}
@ -254,13 +291,14 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
// Should have HighestQuorumCert from previous round votes
qc := syncInfoMsg.(utils.SyncInfo).HighestQuorumCert
assert.NotNil(t, qc)
assert.Equal(t, utils.Round(1), qc.ProposedBlockInfo.Round)
assert.Equal(t, utils.Round(11), qc.ProposedBlockInfo.Round)
tc := syncInfoMsg.(utils.SyncInfo).HighestTimeoutCert
assert.NotNil(t, tc)
assert.Equal(t, tc.Round, utils.Round(2))
assert.Equal(t, utils.Round(12), tc.Round)
sigatures := []utils.Signature{[]byte{1}, []byte{2}, []byte{3}}
assert.ElementsMatch(t, tc.Signatures, sigatures)
// Round shall be +1 now
assert.Equal(t, utils.Round(3), engineV2.GetCurrentRound())
currentRound, _, _ = engineV2.GetProperties()
assert.Equal(t, utils.Round(13), currentRound)
}