mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
implemente proposeblock handler and SyncInfo handler
This commit is contained in:
parent
17eb4c6c65
commit
249d2b5b6d
6 changed files with 225 additions and 111 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
26
tests/consensus/countdown_test.go
Normal file
26
tests/consensus/countdown_test.go
Normal 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())
|
||||
}
|
||||
45
tests/consensus/proposed_block_test.go
Normal file
45
tests/consensus/proposed_block_test.go
Normal 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)
|
||||
}
|
||||
86
tests/consensus/timeout_test.go
Normal file
86
tests/consensus/timeout_test.go
Normal 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())
|
||||
}
|
||||
|
|
@ -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),
|
||||
Loading…
Reference in a new issue