implemente proposeblock handler and SyncInfo handler

This commit is contained in:
Jianrong 2021-12-01 22:26:38 +11:00
parent 17eb4c6c65
commit 249d2b5b6d
6 changed files with 225 additions and 111 deletions

View file

@ -2,6 +2,7 @@ package engine_v2
import (
"fmt"
"math/big"
"sync"
"time"
@ -56,6 +57,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
votePool: votePool,
highestTimeoutCert: &utils.TimeoutCert{},
highestQuorumCert: &utils.QuorumCert{},
highestVotedRound: utils.Round(0),
}
// Add callback to the timer
timer.OnTimeoutFn = engine.onCountdownTimeout
@ -124,12 +126,16 @@ func (x *XDPoS_v2) VerifySyncInfoMessage(syncInfo *utils.SyncInfo) error {
return nil
}
func (x *XDPoS_v2) SyncInfoHandler(syncInfo *utils.SyncInfo) error {
func (x *XDPoS_v2) SyncInfoHandler(chain consensus.ChainReader, syncInfo *utils.SyncInfo) error {
/*
1. processQC
2. processTC
*/
return nil
err := x.processQC(chain, syncInfo.HighestQuorumCert)
if err != nil {
return nil
}
return x.processTC(syncInfo.HighestTimeoutCert)
}
/*
@ -267,26 +273,38 @@ func (x *XDPoS_v2) onTimeoutPoolThresholdReached(pooledTimeouts map[common.Hash]
}
/*
Process Block workflow
Proposed Block workflow
*/
func (x *XDPoS_v2) ProcessBlockHandler() {
func (x *XDPoS_v2) ProposedBlockHandler(blockCahinReader consensus.ChainReader, blockInfo *utils.BlockInfo, quorumCert *utils.QuorumCert) error {
x.lock.Lock()
defer x.lock.Unlock()
/*
1. processQC()
2. verifyVotingRule()
3. sendVote()
*/
err := x.processQC(blockCahinReader, quorumCert)
if err != nil {
return err
}
verified, err := x.verifyVotingRule(blockCahinReader, blockInfo, quorumCert)
if err != nil {
return err
}
if verified {
return x.sendVote(blockInfo)
} else {
log.Info("Failed to pass the voting rule verification", "ProposeBlockHash", blockInfo.Hash)
}
return nil
}
/*
QC & TC Utils
*/
// Genrate blockInfo which contains Hash, round and blockNumber and send to queue
func (x *XDPoS_v2) generateBlockInfo() error {
return nil
}
// To be used by different message verification. Verify local DB block info against the received block information(i.e hash, blockNum, round)
func (x *XDPoS_v2) VerifyBlockInfo(blockInfo *utils.BlockInfo) error {
return nil
@ -317,7 +335,6 @@ func (x *XDPoS_v2) verifyTC(timeoutCert *utils.TimeoutCert) error {
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) {
//TODO: do I need a clone?
x.highestQuorumCert = quorumCert
}
// 2. Get QC from header and update lockQuorumCert(lockQuorumCert is the parent of highestQC)
@ -376,15 +393,29 @@ func (x *XDPoS_v2) setNewRound(round utils.Round) error {
}
// Hot stuff rule to decide whether this node is eligible to vote for the received block
func (x *XDPoS_v2) verifyVotingRule(header *types.Header) error {
func (x *XDPoS_v2) verifyVotingRule(blockCahinReader consensus.ChainReader, blockInfo *utils.BlockInfo, quorumCert *utils.QuorumCert) (bool, error) {
// Make sure this node has not voted for this round. We can have a variable highestVotedRound, and check currentRound > highestVotedRound.
if x.currentRound <= x.highestVotedRound {
return false, nil
}
/*
Make sure this node has not voted for this round. We can have a variable highestVotedRound, and check currentRound > highestVotedRound.
HotStuff Voting rule:
header's round == local current round, AND (one of the following two:)
header's block extends lockQuorumCert's ProposedBlockInfo (we need a isExtending(block_a, block_b) function), OR
header's QC's ProposedBlockInfo.Round > lockQuorumCert's ProposedBlockInfo.Round
*/
return nil
if blockInfo.Round != x.currentRound {
return false, nil
}
isExtended, err := x.isExtendingFromAncestor(blockCahinReader, blockInfo, &x.lockQuorumCert.ProposedBlockInfo)
if err != nil {
return false, err
}
if isExtended || (quorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round) {
return true, nil
}
return false, nil
}
// Once Hot stuff voting rule has verified, this node can then send vote
@ -392,6 +423,7 @@ func (x *XDPoS_v2) sendVote(blockInfo *utils.BlockInfo) error {
// First step: Generate the signature by using node's private key(The signature is the blockInfo signature)
// Second step: Construct the vote struct with the above signature & blockinfo struct
// Third step: Send the vote to broadcast channel
// Forth step: Update the highest Voted round
signedHash, err := x.signSignature(utils.VoteSigHash(blockInfo))
if err != nil {
return err
@ -401,6 +433,7 @@ func (x *XDPoS_v2) sendVote(blockInfo *utils.BlockInfo) error {
Signature: signedHash,
}
x.broadcastToBftChannel(voteMsg)
x.highestVotedRound = x.currentRound
return nil
}
@ -519,3 +552,22 @@ func (x *XDPoS_v2) commitBlocks(blockCahinReader consensus.ChainReader, proposed
return true, nil
}
func (x *XDPoS_v2) isExtendingFromAncestor(blockCahinReader consensus.ChainReader, currentBlock *utils.BlockInfo, ancestorBlock *utils.BlockInfo) (bool, error) {
blockNumDiff := int(big.NewInt(0).Sub(currentBlock.Number, ancestorBlock.Number).Int64())
nextBlockHash := currentBlock.Hash
for i := 0; i < blockNumDiff; i++ {
parentBlock := blockCahinReader.GetHeaderByHash(nextBlockHash)
if parentBlock == nil {
return false, fmt.Errorf("Could not find its parent block when checking whether currentBlock %v is extending from the ancestorBlock %v", currentBlock.Number, ancestorBlock.Number)
} else {
nextBlockHash = parentBlock.ParentHash
}
}
if nextBlockHash == ancestorBlock.Hash {
return true, nil
}
return false, nil
}

View file

@ -39,7 +39,7 @@ type ConsensusFns struct {
timeoutHandler func(*utils.Timeout) error
verifySyncInfo func(*utils.SyncInfo) error
syncInfoHandler func(*utils.SyncInfo) error
syncInfoHandler func(consensus.ChainReader, *utils.SyncInfo) error
}
type BroadcastFns struct {
@ -136,7 +136,7 @@ func (b *Bfter) SyncInfo(syncInfo *utils.SyncInfo) error {
b.knownSyncInfos.Add(syncInfo.Hash(), true)
b.broadcastCh <- syncInfo
err = b.consensus.syncInfoHandler(syncInfo)
err = b.consensus.syncInfoHandler(b.blockCahinReader, syncInfo)
if err != nil {
log.Error("handle BFT SyncInfo", "error", err)
return err

View file

@ -0,0 +1,26 @@
package consensus
import (
"testing"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
engineV2.SetNewRoundFaker(utils.Round(1), true)
timeoutMsg := <-engineV2.BroadcastCh
assert.NotNil(t, timeoutMsg)
valid, err := engineV2.VerifyTimeoutMessage(timeoutMsg.(*utils.Timeout))
// We can only test valid = false for now as the implementation for getCurrentRoundMasterNodes is not complete
assert.False(t, valid)
// This shows we are able to decode the timeout message, which is what this test is all about
assert.Regexp(t, "^Masternodes does not contain signer addres.*", err.Error())
}

View file

@ -0,0 +1,45 @@
package consensus
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
// ProposeBlock handler
func TestProposedBlockMessageHandlerSuccessfullyGenerateVote(t *testing.T) {
blockchain, _, currentBlock, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 11
engineV2.SetNewRoundFaker(utils.Round(11), false)
var extraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
if err != nil {
t.Fatal("Fail to decode extra data", err)
}
proposedBlockInfo := &utils.BlockInfo{
Hash: currentBlock.Hash(),
Round: utils.Round(12),
Number: big.NewInt(12),
}
err = engineV2.ProposedBlockHandler(blockchain, proposedBlockInfo, &extraField.QuorumCert)
if err != nil {
t.Fatal("Fail propose proposedBlock handler", err)
}
voteMsg := <-engineV2.BroadcastCh
assert.NotNil(t, voteMsg)
assert.Equal(t, proposedBlockInfo.Hash, voteMsg.(*utils.Vote).ProposedBlockInfo.Hash)
round, _, highestQC := engineV2.GetProperties()
assert.Equal(t, utils.Round(12), round)
assert.Equal(t, extraField.QuorumCert.Signatures, highestQC.Signatures)
}

View file

@ -0,0 +1,86 @@
package consensus
import (
"testing"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
// Timeout handler
func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 1
engineV2.SetNewRoundFaker(utils.Round(1), false)
// Create two timeout message which will not reach timeout pool threshold
timeoutMsg := &utils.Timeout{
Round: utils.Round(1),
Signature: []byte{1},
}
err := engineV2.TimeoutHandler(timeoutMsg)
assert.Nil(t, err)
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)
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{3},
}
err = engineV2.TimeoutHandler(timeoutMsg)
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
qc := syncInfoMsg.(utils.SyncInfo).HighestQuorumCert
assert.NotNil(t, qc)
tc := syncInfoMsg.(utils.SyncInfo).HighestTimeoutCert
assert.NotNil(t, tc)
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), currentRound)
}
func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 3
engineV2.SetNewRoundFaker(utils.Round(3), false)
timeoutMsg := &utils.Timeout{
Round: utils.Round(2),
Signature: []byte{1},
}
err := engineV2.TimeoutHandler(timeoutMsg)
assert.NotNil(t, err)
// Timeout msg round > currentRound
assert.Equal(t, "Timeout message round number: 2 does not match currentRound: 3", err.Error())
// Set round to 1
engineV2.SetNewRoundFaker(utils.Round(1), false)
err = engineV2.TimeoutHandler(timeoutMsg)
assert.NotNil(t, err)
// Timeout msg round < currentRound
assert.Equal(t, "Timeout message round number: 2 does not match currentRound: 1", err.Error())
}

View file

@ -11,106 +11,11 @@ import (
"github.com/stretchr/testify/assert"
)
func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
engineV2.SetNewRoundFaker(utils.Round(1), true)
timeoutMsg := <-engineV2.BroadcastCh
assert.NotNil(t, timeoutMsg)
valid, err := engineV2.VerifyTimeoutMessage(timeoutMsg.(*utils.Timeout))
// We can only test valid = false for now as the implementation for getCurrentRoundMasterNodes is not complete
assert.False(t, valid)
// This shows we are able to decode the timeout message, which is what this test is all about
assert.Regexp(t, "^Masternodes does not contain signer addres.*", err.Error())
}
// Timeout handler
func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 1
engineV2.SetNewRoundFaker(utils.Round(1), false)
// Create two timeout message which will not reach timeout pool threshold
timeoutMsg := &utils.Timeout{
Round: utils.Round(1),
Signature: []byte{1},
}
err := engineV2.TimeoutHandler(timeoutMsg)
assert.Nil(t, err)
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)
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{3},
}
err = engineV2.TimeoutHandler(timeoutMsg)
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
qc := syncInfoMsg.(utils.SyncInfo).HighestQuorumCert
assert.NotNil(t, qc)
tc := syncInfoMsg.(utils.SyncInfo).HighestTimeoutCert
assert.NotNil(t, tc)
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), currentRound)
}
func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) {
blockchain, _, _, _ := PrepareXDCTestBlockChain(t, 11, params.TestXDPoSMockChainConfigWithV2Engine)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
// Set round to 3
engineV2.SetNewRoundFaker(utils.Round(3), false)
timeoutMsg := &utils.Timeout{
Round: utils.Round(2),
Signature: []byte{1},
}
err := engineV2.TimeoutHandler(timeoutMsg)
assert.NotNil(t, err)
// Timeout msg round > currentRound
assert.Equal(t, "Timeout message round number: 2 does not match currentRound: 3", err.Error())
// Set round to 1
engineV2.SetNewRoundFaker(utils.Round(1), false)
err = engineV2.TimeoutHandler(timeoutMsg)
assert.NotNil(t, err)
// Timeout msg round < currentRound
assert.Equal(t, "Timeout message round number: 2 does not match currentRound: 1", err.Error())
}
// VoteHandler
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: currentBlock.Hash(),
Round: utils.Round(11),