* add GapNumber

* fix broken countdown test

* add gapNumber to existing tests
This commit is contained in:
Jerome 2022-02-28 18:51:42 +11:00 committed by GitHub
parent 97985fda85
commit d773e15ca8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 153 additions and 86 deletions

View file

@ -15,7 +15,7 @@ type CountdownTimer struct {
initilised bool initilised bool
timeoutDuration time.Duration timeoutDuration time.Duration
// Triggered when the countdown timer timeout for the `timeoutDuration` period, it will pass current timestamp to the callback function // Triggered when the countdown timer timeout for the `timeoutDuration` period, it will pass current timestamp to the callback function
OnTimeoutFn func(time time.Time) error OnTimeoutFn func(time time.Time, i interface{}) error
} }
func NewCountDown(duration time.Duration) *CountdownTimer { func NewCountDown(duration time.Duration) *CountdownTimer {
@ -35,17 +35,17 @@ func (t *CountdownTimer) StopTimer() {
} }
// Reset will start the countdown timer if it's already stopped, or simply reset the countdown time back to the defual `duration` // Reset will start the countdown timer if it's already stopped, or simply reset the countdown time back to the defual `duration`
func (t *CountdownTimer) Reset() { func (t *CountdownTimer) Reset(i interface{}) {
if !t.isInitilised() { if !t.isInitilised() {
t.setInitilised(true) t.setInitilised(true)
go t.startTimer() go t.startTimer(i)
} else { } else {
t.resetc <- 0 t.resetc <- 0
} }
} }
// A long running process that // A long running process that
func (t *CountdownTimer) startTimer() { func (t *CountdownTimer) startTimer(i interface{}) {
// Make sure we mark Initilised to false when we quit the countdown // Make sure we mark Initilised to false when we quit the countdown
defer t.setInitilised(false) defer t.setInitilised(false)
timer := time.NewTimer(t.timeoutDuration) timer := time.NewTimer(t.timeoutDuration)
@ -58,7 +58,7 @@ func (t *CountdownTimer) startTimer() {
return return
case <-timer.C: case <-timer.C:
log.Debug("Countdown time reached!") log.Debug("Countdown time reached!")
err := t.OnTimeoutFn(time.Now()) err := t.OnTimeoutFn(time.Now(), i)
if err != nil { if err != nil {
log.Error("OnTimeoutFn error", err) log.Error("OnTimeoutFn error", err)
} }

View file

@ -9,23 +9,24 @@ import (
) )
func TestCountdownWillCallback(t *testing.T) { func TestCountdownWillCallback(t *testing.T) {
var fakeI interface{}
called := make(chan int) called := make(chan int)
OnTimeoutFn := func(time time.Time) error { OnTimeoutFn := func(time.Time, interface{}) error {
called <- 1 called <- 1
return nil return nil
} }
countdown := NewCountDown(1000 * time.Millisecond) countdown := NewCountDown(1000 * time.Millisecond)
countdown.OnTimeoutFn = OnTimeoutFn countdown.OnTimeoutFn = OnTimeoutFn
countdown.Reset() countdown.Reset(fakeI)
<-called <-called
t.Log("Times up, successfully called OnTimeoutFn") t.Log("Times up, successfully called OnTimeoutFn")
} }
func TestCountdownShouldReset(t *testing.T) { func TestCountdownShouldReset(t *testing.T) {
var fakeI interface{}
called := make(chan int) called := make(chan int)
OnTimeoutFn := func(time time.Time) error { OnTimeoutFn := func(time.Time, interface{}) error {
called <- 1 called <- 1
return nil return nil
} }
@ -34,7 +35,7 @@ func TestCountdownShouldReset(t *testing.T) {
countdown.OnTimeoutFn = OnTimeoutFn countdown.OnTimeoutFn = OnTimeoutFn
// Check countdown did not start // Check countdown did not start
assert.False(t, countdown.isInitilised()) assert.False(t, countdown.isInitilised())
countdown.Reset() countdown.Reset(fakeI)
// Now the countdown should already started // Now the countdown should already started
assert.True(t, countdown.isInitilised()) assert.True(t, countdown.isInitilised())
expectedCalledTime := time.Now().Add(9000 * time.Millisecond) expectedCalledTime := time.Now().Add(9000 * time.Millisecond)
@ -53,7 +54,7 @@ firstReset:
} }
break firstReset break firstReset
case <-resetTimer.C: case <-resetTimer.C:
countdown.Reset() countdown.Reset(fakeI)
} }
} }
@ -71,8 +72,9 @@ firstReset:
} }
func TestCountdownShouldResetEvenIfErrored(t *testing.T) { func TestCountdownShouldResetEvenIfErrored(t *testing.T) {
var fakeI interface{}
called := make(chan int) called := make(chan int)
OnTimeoutFn := func(time time.Time) error { OnTimeoutFn := func(time.Time, interface{}) error {
called <- 1 called <- 1
return fmt.Errorf("ERROR!") return fmt.Errorf("ERROR!")
} }
@ -81,7 +83,7 @@ func TestCountdownShouldResetEvenIfErrored(t *testing.T) {
countdown.OnTimeoutFn = OnTimeoutFn countdown.OnTimeoutFn = OnTimeoutFn
// Check countdown did not start // Check countdown did not start
assert.False(t, countdown.isInitilised()) assert.False(t, countdown.isInitilised())
countdown.Reset() countdown.Reset(fakeI)
// Now the countdown should already started // Now the countdown should already started
assert.True(t, countdown.isInitilised()) assert.True(t, countdown.isInitilised())
expectedCalledTime := time.Now().Add(9000 * time.Millisecond) expectedCalledTime := time.Now().Add(9000 * time.Millisecond)
@ -100,7 +102,7 @@ firstReset:
} }
break firstReset break firstReset
case <-resetTimer.C: case <-resetTimer.C:
countdown.Reset() countdown.Reset(fakeI)
} }
} }
@ -118,8 +120,9 @@ firstReset:
} }
func TestCountdownShouldBeAbleToStop(t *testing.T) { func TestCountdownShouldBeAbleToStop(t *testing.T) {
var fakeI interface{}
called := make(chan int) called := make(chan int)
OnTimeoutFn := func(time time.Time) error { OnTimeoutFn := func(time.Time, interface{}) error {
called <- 1 called <- 1
return nil return nil
} }
@ -128,7 +131,7 @@ func TestCountdownShouldBeAbleToStop(t *testing.T) {
countdown.OnTimeoutFn = OnTimeoutFn countdown.OnTimeoutFn = OnTimeoutFn
// Check countdown did not start // Check countdown did not start
assert.False(t, countdown.isInitilised()) assert.False(t, countdown.isInitilised())
countdown.Reset() countdown.Reset(fakeI)
// Now the countdown should already started // Now the countdown should already started
assert.True(t, countdown.isInitilised()) assert.True(t, countdown.isInitilised())
// Try manually stop the timer before it triggers the callback // Try manually stop the timer before it triggers the callback

View file

@ -169,7 +169,7 @@ func (x *XDPoS_v2) Initial(chain consensus.ChainReader, masternodes []common.Add
}() }()
// Kick-off the countdown timer // Kick-off the countdown timer
x.timeoutWorker.Reset() x.timeoutWorker.Reset(chain)
log.Info("[Initial] finish initialisation") log.Info("[Initial] finish initialisation")
return nil return nil
@ -682,7 +682,7 @@ func (x *XDPoS_v2) SyncInfoHandler(chain consensus.ChainReader, syncInfo *utils.
if err != nil { if err != nil {
return err return err
} }
return x.processTC(syncInfo.HighestTimeoutCert) return x.processTC(chain, syncInfo.HighestTimeoutCert)
} }
/* /*
@ -801,7 +801,7 @@ func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, poole
log.Error("Error while processing QC in the Vote handler after reaching pool threshold, ", err) log.Error("Error while processing QC in the Vote handler after reaching pool threshold, ", err)
return err return err
} }
log.Info("🗳 Successfully processed the vote and produced QC!") log.Info("Successfully processed the vote and produced QC!", "QcRound", quorumCert.ProposedBlockInfo.Round, "QcNumOfSig", len(quorumCert.Signatures), "QcHash", quorumCert.ProposedBlockInfo.Hash, "QcNumber", quorumCert.ProposedBlockInfo.Number.Uint64())
// clean up vote at the same poolKey. and pookKey is proposed block hash // clean up vote at the same poolKey. and pookKey is proposed block hash
x.votePool.ClearPoolKeyByObj(currentVoteMsg) x.votePool.ClearPoolKeyByObj(currentVoteMsg)
return nil return nil
@ -822,7 +822,10 @@ func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, poole
func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg *utils.Timeout) (bool, error) { func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg *utils.Timeout) (bool, error) {
masternodes := x.GetMasternodesAtRound(chain, timeoutMsg.Round, chain.CurrentHeader()) masternodes := x.GetMasternodesAtRound(chain, timeoutMsg.Round, chain.CurrentHeader())
return x.verifyMsgSignature(utils.TimeoutSigHash(&timeoutMsg.Round), timeoutMsg.Signature, masternodes) return x.verifyMsgSignature(utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: timeoutMsg.Round,
GapNumber: timeoutMsg.GapNumber,
}), timeoutMsg.Signature, masternodes)
} }
/* /*
@ -831,13 +834,13 @@ func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg
2. Collect timeout 2. Collect timeout
3. Once timeout pool reached threshold, it will trigger the call to the 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 { func (x *XDPoS_v2) TimeoutHandler(blockChainReader consensus.ChainReader, timeout *utils.Timeout) error {
x.lock.Lock() x.lock.Lock()
defer x.lock.Unlock() defer x.lock.Unlock()
return x.timeoutHandler(timeout) return x.timeoutHandler(blockChainReader, timeout)
} }
func (x *XDPoS_v2) timeoutHandler(timeout *utils.Timeout) error { func (x *XDPoS_v2) timeoutHandler(blockChainReader consensus.ChainReader, timeout *utils.Timeout) error {
// 1. checkRoundNumber // 1. checkRoundNumber
if timeout.Round != x.currentRound { if timeout.Round != x.currentRound {
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{ return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{
@ -851,12 +854,12 @@ func (x *XDPoS_v2) timeoutHandler(timeout *utils.Timeout) error {
// Threshold reached // Threshold reached
if isThresholdReached { if isThresholdReached {
log.Info(fmt.Sprintf("Timeout pool threashold reached: %v, number of items in the pool: %v", isThresholdReached, numberOfTimeoutsInPool)) log.Info(fmt.Sprintf("Timeout pool threashold reached: %v, number of items in the pool: %v", isThresholdReached, numberOfTimeoutsInPool))
err := x.onTimeoutPoolThresholdReached(pooledTimeouts, timeout) err := x.onTimeoutPoolThresholdReached(blockChainReader, pooledTimeouts, timeout, timeout.GapNumber)
if err != nil { if err != nil {
return err return err
} }
// clean up timeout message at the same poolKey. and pookKey is proposed block hash // clean up timeout message, regardless its GapNumber or round
x.timeoutPool.ClearPoolKeyByObj(timeout) x.timeoutPool.Clear()
} }
return nil return nil
} }
@ -868,7 +871,7 @@ func (x *XDPoS_v2) timeoutHandler(timeout *utils.Timeout) error {
2. processTC() 2. processTC()
3. generateSyncInfo() 3. generateSyncInfo()
*/ */
func (x *XDPoS_v2) onTimeoutPoolThresholdReached(pooledTimeouts map[common.Hash]utils.PoolObj, currentTimeoutMsg utils.PoolObj) error { func (x *XDPoS_v2) onTimeoutPoolThresholdReached(blockChainReader consensus.ChainReader, pooledTimeouts map[common.Hash]utils.PoolObj, currentTimeoutMsg utils.PoolObj, gapNumber uint64) error {
signatures := []utils.Signature{} signatures := []utils.Signature{}
for _, v := range pooledTimeouts { for _, v := range pooledTimeouts {
signatures = append(signatures, v.(*utils.Timeout).Signature) signatures = append(signatures, v.(*utils.Timeout).Signature)
@ -877,18 +880,19 @@ func (x *XDPoS_v2) onTimeoutPoolThresholdReached(pooledTimeouts map[common.Hash]
timeoutCert := &utils.TimeoutCert{ timeoutCert := &utils.TimeoutCert{
Round: currentTimeoutMsg.(*utils.Timeout).Round, Round: currentTimeoutMsg.(*utils.Timeout).Round,
Signatures: signatures, Signatures: signatures,
GapNumber: gapNumber,
} }
// Process TC // Process TC
err := x.processTC(timeoutCert) err := x.processTC(blockChainReader, timeoutCert)
if err != nil { if err != nil {
log.Error("Error while processing TC in the Timeout handler after reaching pool threshold, ", err.Error()) log.Error("Error while processing TC in the Timeout handler after reaching pool threshold", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures), "GapNumber", gapNumber, "Error", err)
return err return err
} }
// Generate and broadcast syncInfo // Generate and broadcast syncInfo
syncInfo := x.getSyncInfo() syncInfo := x.getSyncInfo()
x.broadcastToBftChannel(syncInfo) x.broadcastToBftChannel(syncInfo)
log.Info("Successfully processed the timeout message and produced TC & SyncInfo!") log.Info("Successfully processed the timeout message and produced TC & SyncInfo!", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures))
return nil return nil
} }
@ -1041,10 +1045,10 @@ func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *
return x.VerifyBlockInfo(blockChainReader, quorumCert.ProposedBlockInfo) return x.VerifyBlockInfo(blockChainReader, quorumCert.ProposedBlockInfo)
} }
// TODO: Unhold, wait till proposal finalise
func (x *XDPoS_v2) verifyTC(timeoutCert *utils.TimeoutCert) error { func (x *XDPoS_v2) verifyTC(timeoutCert *utils.TimeoutCert) error {
/* /*
1. Get epoch master node list by round/number with chain's current header 1. Get epoch master node list by gapNumber
2. Check number of signatures > threshold, as well as it's format. (Same as verifyQC)
2. Verify signer signature: (List of signatures) 2. Verify signer signature: (List of signatures)
- Use ecRecover to get the public key - Use ecRecover to get the public key
- Use the above public key to find out the xdc address - Use the above public key to find out the xdc address
@ -1087,7 +1091,7 @@ func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, quorumCert
} }
// 4. Set new round // 4. Set new round
if quorumCert.ProposedBlockInfo.Round >= x.currentRound { if quorumCert.ProposedBlockInfo.Round >= x.currentRound {
err := x.setNewRound(quorumCert.ProposedBlockInfo.Round + 1) err := x.setNewRound(blockChainReader, quorumCert.ProposedBlockInfo.Round+1)
if err != nil { if err != nil {
log.Error("[processQC] Fail to setNewRound", "new round to set", quorumCert.ProposedBlockInfo.Round+1) log.Error("[processQC] Fail to setNewRound", "new round to set", quorumCert.ProposedBlockInfo.Round+1)
return err return err
@ -1101,12 +1105,12 @@ func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, quorumCert
1. Update highestTC 1. Update highestTC
2. Check TC round >= node's currentRound. If yes, call setNewRound 2. Check TC round >= node's currentRound. If yes, call setNewRound
*/ */
func (x *XDPoS_v2) processTC(timeoutCert *utils.TimeoutCert) error { func (x *XDPoS_v2) processTC(blockChainReader consensus.ChainReader, timeoutCert *utils.TimeoutCert) error {
if timeoutCert.Round > x.highestTimeoutCert.Round { if timeoutCert.Round > x.highestTimeoutCert.Round {
x.highestTimeoutCert = timeoutCert x.highestTimeoutCert = timeoutCert
} }
if timeoutCert.Round >= x.currentRound { if timeoutCert.Round >= x.currentRound {
err := x.setNewRound(timeoutCert.Round + 1) err := x.setNewRound(blockChainReader, timeoutCert.Round+1)
if err != nil { if err != nil {
return err return err
} }
@ -1119,10 +1123,10 @@ func (x *XDPoS_v2) processTC(timeoutCert *utils.TimeoutCert) error {
2. Reset timer 2. Reset timer
3. Reset vote and timeout Pools 3. Reset vote and timeout Pools
*/ */
func (x *XDPoS_v2) setNewRound(round utils.Round) error { func (x *XDPoS_v2) setNewRound(blockChainReader consensus.ChainReader, round utils.Round) error {
x.currentRound = round x.currentRound = round
//TODO: tell miner now it's a new round and start mine if it's leader //TODO: tell miner now it's a new round and start mine if it's leader
x.timeoutWorker.Reset() x.timeoutWorker.Reset(blockChainReader)
//TODO: vote pools //TODO: vote pools
x.timeoutPool.Clear() x.timeoutPool.Clear()
return nil return nil
@ -1197,18 +1201,44 @@ func (x *XDPoS_v2) sendVote(chainReader consensus.ChainReader, blockInfo *utils.
2. Sign the signature 2. Sign the signature
3. send to broadcast channel 3. send to broadcast channel
*/ */
func (x *XDPoS_v2) sendTimeout() error { func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error {
signedHash, err := x.signSignature(utils.TimeoutSigHash(&x.currentRound)) // Construct the gapNumber
var gapNumber uint64
currentBlockHeader := chain.CurrentHeader()
isEpochSwitch, epochNum, err := x.IsEpochSwitchAtRound(x.currentRound, currentBlockHeader)
if err != nil { if err != nil {
log.Error("signSignature when sending out TC", "Error", err) log.Error("[sendTimeout] Error while checking if the currentBlock is epoch switch", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
return err
}
if isEpochSwitch {
// Notice this +1 is because we expect a block whos is the child of currentHeader
currentNumber := currentBlockHeader.Number.Uint64() + 1
gapNumber := currentNumber - currentNumber%x.config.Epoch - x.config.Gap
log.Debug("[sendTimeout] is epoch switch when sending out timeout message", "currentNumber", currentNumber, "gapNumber", gapNumber)
} else {
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentBlockHeader, currentBlockHeader.Hash())
if err != nil {
log.Error("[sendTimeout] Error when trying to get current epoch switch info for a non-epoch block", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
}
gapNumber := epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64() - epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()%x.config.Epoch - x.config.Gap
log.Debug("[sendTimeout] non-epoch-switch block found its epoch block and calculated the gapNumber", "epochSwitchInfo.EpochSwitchBlockInfo.Number", epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64(), "gapNumber", gapNumber)
}
signedHash, err := x.signSignature(utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: x.currentRound,
GapNumber: gapNumber,
}))
if err != nil {
log.Error("[sendTimeout] signSignature when sending out TC", "Error", err)
return err return err
} }
timeoutMsg := &utils.Timeout{ timeoutMsg := &utils.Timeout{
Round: x.currentRound, Round: x.currentRound,
Signature: signedHash, Signature: signedHash,
GapNumber: gapNumber,
} }
log.Info("[sendTimeout] Timeout message generated, ready to send!", "timeoutMsgRound", timeoutMsg.Round, "timeoutMsgGapNumber", timeoutMsg.GapNumber)
err = x.timeoutHandler(timeoutMsg) err = x.timeoutHandler(chain, timeoutMsg)
if err != nil { if err != nil {
log.Error("TimeoutHandler error", "TimeoutRound", timeoutMsg.Round, "Error", err) log.Error("TimeoutHandler error", "TimeoutRound", timeoutMsg.Round, "Error", err)
return err return err
@ -1254,11 +1284,11 @@ func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signat
Function that will be called by timer when countdown reaches its threshold. Function that will be called by timer when countdown reaches its threshold.
In the engine v2, we would need to broadcast timeout messages to other peers In the engine v2, we would need to broadcast timeout messages to other peers
*/ */
func (x *XDPoS_v2) OnCountdownTimeout(time time.Time) error { func (x *XDPoS_v2) OnCountdownTimeout(time time.Time, chain interface{}) error {
x.lock.Lock() x.lock.Lock()
defer x.lock.Unlock() defer x.lock.Unlock()
err := x.sendTimeout() err := x.sendTimeout(chain.(consensus.ChainReader))
if err != nil { if err != nil {
log.Error("Error while sending out timeout message at time: ", time) log.Error("Error while sending out timeout message at time: ", time)
return err return err
@ -1321,7 +1351,7 @@ func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposed
Hash: grandParentBlock.Hash(), Hash: grandParentBlock.Hash(),
Round: decodedExtraField.Round, Round: decodedExtraField.Round,
} }
log.Debug("👴 Successfully committed block", "Committed block Hash", x.highestCommitBlock.Hash, "Committed round", x.highestCommitBlock.Round) log.Debug("Successfully committed block", "Committed block Hash", x.highestCommitBlock.Hash, "Committed round", x.highestCommitBlock.Round)
return true, nil return true, nil
} }
// Everything else, fail to commit // Everything else, fail to commit
@ -1352,12 +1382,12 @@ func (x *XDPoS_v2) isExtendingFromAncestor(blockChainReader consensus.ChainReade
Testing tools Testing tools
*/ */
func (x *XDPoS_v2) SetNewRoundFaker(newRound utils.Round, resetTimer bool) { func (x *XDPoS_v2) SetNewRoundFaker(blockChainReader consensus.ChainReader, newRound utils.Round, resetTimer bool) {
x.lock.Lock() x.lock.Lock()
defer x.lock.Unlock() defer x.lock.Unlock()
// Reset a bunch of things // Reset a bunch of things
if resetTimer { if resetTimer {
x.timeoutWorker.Reset() x.timeoutWorker.Reset(blockChainReader)
} }
x.currentRound = newRound x.currentRound = newRound
} }

View file

@ -35,6 +35,7 @@ func (p *Pool) Add(obj PoolObj) (bool, int, map[common.Hash]PoolObj) {
} }
return false, numOfItems, objListKeyed return false, numOfItems, objListKeyed
} }
func (p *Pool) Size(obj PoolObj) int { func (p *Pool) Size(obj PoolObj) int {
poolKey := obj.PoolKey() poolKey := obj.PoolKey()
objListKeyed, ok := p.objList[poolKey] objListKeyed, ok := p.objList[poolKey]

View file

@ -1,6 +1,7 @@
package utils package utils
import ( import (
"fmt"
"math/big" "math/big"
"time" "time"
@ -81,6 +82,7 @@ type Vote struct {
type Timeout struct { type Timeout struct {
Round Round Round Round
Signature Signature Signature Signature
GapNumber uint64
} }
// BFT Sync Info message in XDPoS 2.0 // BFT Sync Info message in XDPoS 2.0
@ -99,6 +101,7 @@ type QuorumCert struct {
type TimeoutCert struct { type TimeoutCert struct {
Round Round Round Round
Signatures []Signature Signatures []Signature
GapNumber uint64
} }
// The parsed extra fields in block header in XDPoS 2.0 (excluding the version byte) // The parsed extra fields in block header in XDPoS 2.0 (excluding the version byte)
@ -147,7 +150,12 @@ func VoteSigHash(m *BlockInfo) common.Hash {
return rlpHash(m) return rlpHash(m)
} }
func TimeoutSigHash(m *Round) common.Hash { type TimeoutForSign struct {
Round Round
GapNumber uint64
}
func TimeoutSigHash(m *TimeoutForSign) common.Hash {
return rlpHash(m) return rlpHash(m)
} }
@ -157,6 +165,6 @@ func (m *Vote) PoolKey() string {
} }
func (m *Timeout) PoolKey() string { func (m *Timeout) PoolKey() string {
// return a default pool key string // timeout pool key is round:gapNumber
return "0" return fmt.Sprint(m.Round, ":", m.GapNumber)
} }

View file

@ -62,7 +62,13 @@ func TestHashAndSigHash(t *testing.T) {
t.Fatalf("SigHash of two block info shouldn't equal") t.Fatalf("SigHash of two block info shouldn't equal")
} }
round2 := Round(999) round2 := Round(999)
if TimeoutSigHash(&round) == TimeoutSigHash(&round2) { if TimeoutSigHash(&TimeoutForSign{
Round: round,
GapNumber: 450,
}) == TimeoutSigHash(&TimeoutForSign{
Round: round2,
GapNumber: 450,
}) {
t.Fatalf("SigHash of two round shouldn't equal") t.Fatalf("SigHash of two round shouldn't equal")
} }
} }

View file

@ -127,7 +127,7 @@ func TestIsYourTurnConsensusV2(t *testing.T) {
blockchain.InsertBlock(currentBlock) blockchain.InsertBlock(currentBlock)
time.Sleep(time.Duration(minePeriod) * time.Second) time.Sleep(time.Duration(minePeriod) * time.Second)
adaptor.EngineV2.SetNewRoundFaker(2, false) adaptor.EngineV2.SetNewRoundFaker(blockchain, 2, false)
isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e")) isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
assert.False(t, isYourTurn) assert.False(t, isYourTurn)

View file

@ -10,15 +10,17 @@ import (
) )
func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) { func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) {
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfig, 0) blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, 0)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
engineV2.SetNewRoundFaker(utils.Round(1), true) engineV2.SetNewRoundFaker(blockchain, utils.Round(1), true)
timeoutMsg := <-engineV2.BroadcastCh timeoutMsg := <-engineV2.BroadcastCh
poolSize := engineV2.GetTimeoutPoolSize(timeoutMsg.(*utils.Timeout)) poolSize := engineV2.GetTimeoutPoolSize(timeoutMsg.(*utils.Timeout))
assert.Equal(t, poolSize, 1) assert.Equal(t, poolSize, 1)
assert.NotNil(t, timeoutMsg) assert.NotNil(t, timeoutMsg)
assert.Equal(t, uint64(0), timeoutMsg.(*utils.Timeout).GapNumber)
assert.Equal(t, utils.Round(1), timeoutMsg.(*utils.Timeout).Round)
valid, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg.(*utils.Timeout)) valid, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg.(*utils.Timeout))
// We can only test valid = false for now as the implementation for getCurrentRoundMasterNodes is not complete // We can only test valid = false for now as the implementation for getCurrentRoundMasterNodes is not complete

View file

@ -94,7 +94,7 @@ func TestHookPenaltyV2Jump(t *testing.T) {
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901) masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
assert.Equal(t, 4, len(masternodes)) assert.Equal(t, 4, len(masternodes))
header6285 := blockchain.GetHeaderByNumber(uint64(end)) header6285 := blockchain.GetHeaderByNumber(uint64(end))
adaptor.EngineV2.SetNewRoundFaker(utils.Round(config.XDPoS.Epoch*7), false) adaptor.EngineV2.SetNewRoundFaker(blockchain, utils.Round(config.XDPoS.Epoch*7), false)
// round 6285-6300 miss blocks, penalty should work as usual // round 6285-6300 miss blocks, penalty should work as usual
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, header6285.Number, header6285.ParentHash, masternodes) penalty, err := adaptor.EngineV2.HookPenalty(blockchain, header6285.Number, header6285.ParentHash, masternodes)
assert.Nil(t, err) assert.Nil(t, err)

View file

@ -175,7 +175,7 @@ func TestProposedBlockMessageHandlerSuccessfullyGenerateVote(t *testing.T) {
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set current round to 5 // Set current round to 5
engineV2.SetNewRoundFaker(utils.Round(5), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(5), false)
var extraField utils.ExtraFields_v2 var extraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField) err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
@ -205,7 +205,7 @@ func TestShouldNotSetNewRound(t *testing.T) {
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set current round to 6 // Set current round to 6
engineV2.SetNewRoundFaker(utils.Round(6), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(6), false)
var extraField utils.ExtraFields_v2 var extraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField) err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
@ -229,7 +229,7 @@ func TestShouldNotSendVoteMessageIfAlreadyVoteForThisRound(t *testing.T) {
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set current round to 5 // Set current round to 5
engineV2.SetNewRoundFaker(utils.Round(5), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(5), false)
err := engineV2.ProposedBlockHandler(blockchain, currentBlock.Header()) err := engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
if err != nil { if err != nil {
@ -267,7 +267,7 @@ func TestShouldNotSendVoteMsgIfBlockInfoRoundNotEqualCurrentRound(t *testing.T)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set current round to 8 // Set current round to 8
engineV2.SetNewRoundFaker(utils.Round(8), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(8), false)
var extraField utils.ExtraFields_v2 var extraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField) err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
@ -316,7 +316,7 @@ func TestShouldNotSendVoteMsgIfBlockNotExtendedFromAncestor(t *testing.T) {
// Find the first forked block at block 14th // Find the first forked block at block 14th
firstForkedBlock := blockchain.GetBlockByHash(blockchain.GetBlockByHash(forkedBlock.ParentHash()).ParentHash()) firstForkedBlock := blockchain.GetBlockByHash(blockchain.GetBlockByHash(forkedBlock.ParentHash()).ParentHash())
engineV2.SetNewRoundFaker(utils.Round(7), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(7), false)
err = engineV2.ProposedBlockHandler(blockchain, firstForkedBlock.Header()) err = engineV2.ProposedBlockHandler(blockchain, firstForkedBlock.Header())
if err != nil { if err != nil {
t.Fatal("Fail propose proposedBlock handler", err) t.Fatal("Fail propose proposedBlock handler", err)

View file

@ -15,32 +15,47 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 1 // Set round to 1
engineV2.SetNewRoundFaker(utils.Round(1), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(1), false)
// Create two timeout message which will not reach timeout pool threshold // Create two timeout message which will not reach timeout pool threshold
timeoutMsg := &utils.Timeout{ timeoutMsg := &utils.Timeout{
Round: utils.Round(1), Round: utils.Round(1),
Signature: []byte{1}, Signature: []byte{1},
GapNumber: 450,
} }
err := engineV2.TimeoutHandler(timeoutMsg) err := engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.Nil(t, err) assert.Nil(t, err)
currentRound, _, _, _, _ := engineV2.GetProperties() currentRound, _, _, _, _ := engineV2.GetProperties()
assert.Equal(t, utils.Round(1), currentRound) assert.Equal(t, utils.Round(1), currentRound)
timeoutMsg = &utils.Timeout{ timeoutMsg = &utils.Timeout{
Round: utils.Round(1), Round: utils.Round(1),
Signature: []byte{2}, Signature: []byte{2},
GapNumber: 450,
} }
err = engineV2.TimeoutHandler(timeoutMsg) err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.Nil(t, err) assert.Nil(t, err)
currentRound, _, _, _, _ = engineV2.GetProperties() currentRound, _, _, _, _ = engineV2.GetProperties()
assert.Equal(t, utils.Round(1), currentRound) assert.Equal(t, utils.Round(1), currentRound)
// Create a timeout message that should trigger timeout pool hook
// Send a timeout with different gap number, it shall not trigger timeout pool hook
timeoutMsg = &utils.Timeout{ timeoutMsg = &utils.Timeout{
Round: utils.Round(1), Round: utils.Round(1),
Signature: []byte{3}, Signature: []byte{3},
GapNumber: 1350,
}
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.Nil(t, err)
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),
Signature: []byte{4},
GapNumber: 450,
} }
err = engineV2.TimeoutHandler(timeoutMsg) err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.Nil(t, err) assert.Nil(t, err)
syncInfoMsg := <-engineV2.BroadcastCh syncInfoMsg := <-engineV2.BroadcastCh
@ -56,7 +71,9 @@ func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
tc := syncInfoMsg.(*utils.SyncInfo).HighestTimeoutCert tc := syncInfoMsg.(*utils.SyncInfo).HighestTimeoutCert
assert.NotNil(t, tc) assert.NotNil(t, tc)
assert.Equal(t, tc.Round, utils.Round(1)) assert.Equal(t, tc.Round, utils.Round(1))
sigatures := []utils.Signature{[]byte{1}, []byte{2}, []byte{3}} assert.Equal(t, uint64(450), tc.GapNumber)
// The signatures shall not include the byte{3} from a different gap number
sigatures := []utils.Signature{[]byte{1}, []byte{2}, []byte{4}}
assert.ElementsMatch(t, tc.Signatures, sigatures) assert.ElementsMatch(t, tc.Signatures, sigatures)
assert.Equal(t, utils.Round(2), currentRound) assert.Equal(t, utils.Round(2), currentRound)
} }
@ -66,20 +83,20 @@ func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) {
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 3 // Set round to 3
engineV2.SetNewRoundFaker(utils.Round(3), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(3), false)
timeoutMsg := &utils.Timeout{ timeoutMsg := &utils.Timeout{
Round: utils.Round(2), Round: utils.Round(2),
Signature: []byte{1}, Signature: []byte{1},
} }
err := engineV2.TimeoutHandler(timeoutMsg) err := engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.NotNil(t, err) assert.NotNil(t, err)
// Timeout msg round > currentRound // Timeout msg round > currentRound
assert.Equal(t, "timeout message round number: 2 does not match currentRound: 3", err.Error()) assert.Equal(t, "timeout message round number: 2 does not match currentRound: 3", err.Error())
// Set round to 1 // Set round to 1
engineV2.SetNewRoundFaker(utils.Round(1), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(1), false)
err = engineV2.TimeoutHandler(timeoutMsg) err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.NotNil(t, err) assert.NotNil(t, err)
// Timeout msg round < currentRound // Timeout msg round < currentRound
assert.Equal(t, "timeout message round number: 2 does not match currentRound: 1", err.Error()) assert.Equal(t, "timeout message round number: 2 does not match currentRound: 1", err.Error())

View file

@ -27,7 +27,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQCForFistV2Round(t *te
voteSigningHash := utils.VoteSigHash(blockInfo) voteSigningHash := utils.VoteSigHash(blockInfo)
// Set round to 5 // Set round to 5
engineV2.SetNewRoundFaker(utils.Round(1), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(1), false)
// Create two vote messages which will not reach vote pool threshold // Create two vote messages which will not reach vote pool threshold
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes()) signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
assert.Nil(t, err) assert.Nil(t, err)
@ -89,7 +89,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) {
voteSigningHash := utils.VoteSigHash(blockInfo) voteSigningHash := utils.VoteSigHash(blockInfo)
// Set round to 5 // Set round to 5
engineV2.SetNewRoundFaker(utils.Round(5), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(5), false)
// Create two vote messages which will not reach vote pool threshold // Create two vote messages which will not reach vote pool threshold
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes()) signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
assert.Nil(t, err) assert.Nil(t, err)
@ -168,7 +168,7 @@ func TestThrowErrorIfVoteMsgRoundIsMoreThanOneRoundAwayFromCurrentRound(t *testi
} }
// Set round to 7 // Set round to 7
engineV2.SetNewRoundFaker(utils.Round(7), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(7), false)
voteMsg := &utils.Vote{ voteMsg := &utils.Vote{
ProposedBlockInfo: blockInfo, ProposedBlockInfo: blockInfo,
Signature: []byte{1}, Signature: []byte{1},
@ -180,11 +180,11 @@ func TestThrowErrorIfVoteMsgRoundIsMoreThanOneRoundAwayFromCurrentRound(t *testi
assert.Equal(t, "vote message round number: 6 is too far away from currentRound: 7", err.Error()) assert.Equal(t, "vote message round number: 6 is too far away from currentRound: 7", err.Error())
// Set round to 5, it's 1 round away, should not trigger failure // Set round to 5, it's 1 round away, should not trigger failure
engineV2.SetNewRoundFaker(utils.Round(5), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(5), false)
err = engineV2.VoteHandler(blockchain, voteMsg) err = engineV2.VoteHandler(blockchain, voteMsg)
assert.Nil(t, err) assert.Nil(t, err)
engineV2.SetNewRoundFaker(utils.Round(4), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(4), false)
err = engineV2.VoteHandler(blockchain, voteMsg) err = engineV2.VoteHandler(blockchain, voteMsg)
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Equal(t, "vote message round number: 6 is too far away from currentRound: 4", err.Error()) assert.Equal(t, "vote message round number: 6 is too far away from currentRound: 4", err.Error())
@ -196,7 +196,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 5 // Set round to 5
engineV2.SetNewRoundFaker(utils.Round(5), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(5), false)
// Start with vote messages // Start with vote messages
blockInfo := &utils.BlockInfo{ blockInfo := &utils.BlockInfo{
@ -254,7 +254,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
Signature: []byte{1}, Signature: []byte{1},
} }
err = engineV2.TimeoutHandler(timeoutMsg) err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.NotNil(t, err) assert.NotNil(t, err)
assert.Equal(t, "timeout message round number: 5 does not match currentRound: 6", err.Error()) assert.Equal(t, "timeout message round number: 5 does not match currentRound: 6", err.Error())
@ -264,7 +264,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
Signature: []byte{1}, Signature: []byte{1},
} }
err = engineV2.TimeoutHandler(timeoutMsg) err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.Nil(t, err) assert.Nil(t, err)
currentRound, _, _, _, _ = engineV2.GetProperties() currentRound, _, _, _, _ = engineV2.GetProperties()
assert.Equal(t, utils.Round(6), currentRound) assert.Equal(t, utils.Round(6), currentRound)
@ -272,7 +272,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
Round: utils.Round(6), Round: utils.Round(6),
Signature: []byte{2}, Signature: []byte{2},
} }
err = engineV2.TimeoutHandler(timeoutMsg) err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.Nil(t, err) assert.Nil(t, err)
currentRound, _, _, _, _ = engineV2.GetProperties() currentRound, _, _, _, _ = engineV2.GetProperties()
assert.Equal(t, utils.Round(6), currentRound) assert.Equal(t, utils.Round(6), currentRound)
@ -283,7 +283,7 @@ func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
Signature: []byte{3}, Signature: []byte{3},
} }
err = engineV2.TimeoutHandler(timeoutMsg) err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
assert.Nil(t, err) assert.Nil(t, err)
syncInfoMsg := <-engineV2.BroadcastCh syncInfoMsg := <-engineV2.BroadcastCh
@ -321,7 +321,7 @@ func TestVoteMessageShallNotThrowErrorIfBlockNotYetExist(t *testing.T) {
voteSigningHash := utils.VoteSigHash(blockInfo) voteSigningHash := utils.VoteSigHash(blockInfo)
// Set round to 6 // Set round to 6
engineV2.SetNewRoundFaker(utils.Round(6), false) engineV2.SetNewRoundFaker(blockchain, utils.Round(6), false)
// Create two vote messages which will not reach vote pool threshold // Create two vote messages which will not reach vote pool threshold
voteMsg := &utils.Vote{ voteMsg := &utils.Vote{
ProposedBlockInfo: blockInfo, ProposedBlockInfo: blockInfo,

View file

@ -160,7 +160,7 @@ func TestTimeoutHandler(t *testing.T) {
return nil return nil
} }
tester.bfter.consensus.timeoutHandler = func(timeout *utils.Timeout) error { tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *utils.Timeout) error {
atomic.AddUint32(&handlerCounter, 1) atomic.AddUint32(&handlerCounter, 1)
return nil return nil
} }
@ -190,7 +190,7 @@ func TestTimeoutHandlerRoundNotEqual(t *testing.T) {
return nil return nil
} }
tester.bfter.consensus.timeoutHandler = func(timeout *utils.Timeout) error { tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *utils.Timeout) error {
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{ return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{
Type: "timeout", Type: "timeout",
IncomingRound: utils.Round(1), IncomingRound: utils.Round(1),

View file

@ -34,11 +34,11 @@ type Bfter struct {
} }
type ConsensusFns struct { type ConsensusFns struct {
verifyVote func(chain consensus.ChainReader, vote *utils.Vote) (bool, error) verifyVote func(consensus.ChainReader, *utils.Vote) (bool, error)
voteHandler func(consensus.ChainReader, *utils.Vote) error voteHandler func(consensus.ChainReader, *utils.Vote) error
verifyTimeout func(*utils.Timeout) error verifyTimeout func(*utils.Timeout) error
timeoutHandler func(*utils.Timeout) error timeoutHandler func(consensus.ChainReader, *utils.Timeout) error
verifySyncInfo func(*utils.SyncInfo) error verifySyncInfo func(*utils.SyncInfo) error
syncInfoHandler func(consensus.ChainReader, *utils.SyncInfo) error syncInfoHandler func(consensus.ChainReader, *utils.SyncInfo) error
@ -123,7 +123,7 @@ func (b *Bfter) Timeout(timeout *utils.Timeout) error {
} }
b.broadcastCh <- timeout b.broadcastCh <- timeout
err = b.consensus.timeoutHandler(timeout) err = b.consensus.timeoutHandler(b.blockChainReader, timeout)
if err != nil { if err != nil {
if _, ok := err.(*utils.ErrIncomingMessageRoundNotEqualCurrentRound); ok { if _, ok := err.(*utils.ErrIncomingMessageRoundNotEqualCurrentRound); ok {
log.Warn("timeout round not equal", "error", err) log.Warn("timeout round not equal", "error", err)