mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
Add set committed QC function in forensics (#83)
This commit is contained in:
parent
8fde52c512
commit
eb35d4e32e
4 changed files with 183 additions and 36 deletions
|
|
@ -107,13 +107,13 @@ func New(config *params.XDPoSConfig, db ethdb.Database, waitPeriodCh chan int) *
|
|||
},
|
||||
highestVotedRound: utils.Round(0),
|
||||
highestCommitBlock: nil,
|
||||
forensics: NewForensics(),
|
||||
}
|
||||
// Add callback to the timer
|
||||
timeoutTimer.OnTimeoutFn = engine.OnCountdownTimeout
|
||||
|
||||
engine.periodicJob()
|
||||
|
||||
engine.AttachForensics()
|
||||
return engine
|
||||
}
|
||||
|
||||
|
|
@ -822,41 +822,41 @@ func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *
|
|||
}
|
||||
|
||||
// Update local QC variables including highestQC & lockQuorumCert, as well as commit the blocks that satisfy the algorithm requirements
|
||||
func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, quorumCert *utils.QuorumCert) error {
|
||||
func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, incomingQuorumCert *utils.QuorumCert) error {
|
||||
log.Trace("[ProcessQC][Before]", "HighQC", x.highestQuorumCert)
|
||||
// 1. Update HighestQC
|
||||
if quorumCert.ProposedBlockInfo.Round > x.highestQuorumCert.ProposedBlockInfo.Round {
|
||||
x.highestQuorumCert = quorumCert
|
||||
if incomingQuorumCert.ProposedBlockInfo.Round > x.highestQuorumCert.ProposedBlockInfo.Round {
|
||||
x.highestQuorumCert = incomingQuorumCert
|
||||
}
|
||||
// 2. Get QC from header and update lockQuorumCert(lockQuorumCert is the parent of highestQC)
|
||||
proposedBlockHeader := blockChainReader.GetHeaderByHash(quorumCert.ProposedBlockInfo.Hash)
|
||||
proposedBlockHeader := blockChainReader.GetHeaderByHash(incomingQuorumCert.ProposedBlockInfo.Hash)
|
||||
if proposedBlockHeader == nil {
|
||||
log.Error("[processQC] Block not found using the QC", "quorumCert.ProposedBlockInfo.Hash", quorumCert.ProposedBlockInfo.Hash, "quorumCert.ProposedBlockInfo.Number", quorumCert.ProposedBlockInfo.Number)
|
||||
return fmt.Errorf("Block not found, number: %v, hash: %v", quorumCert.ProposedBlockInfo.Number, quorumCert.ProposedBlockInfo.Hash)
|
||||
log.Error("[processQC] Block not found using the QC", "quorumCert.ProposedBlockInfo.Hash", incomingQuorumCert.ProposedBlockInfo.Hash, "incomingQuorumCert.ProposedBlockInfo.Number", incomingQuorumCert.ProposedBlockInfo.Number)
|
||||
return fmt.Errorf("Block not found, number: %v, hash: %v", incomingQuorumCert.ProposedBlockInfo.Number, incomingQuorumCert.ProposedBlockInfo.Hash)
|
||||
}
|
||||
if proposedBlockHeader.Number.Cmp(x.config.V2.SwitchBlock) > 0 {
|
||||
// Extra field contain parent information
|
||||
quorumCert, round, _, err := x.getExtraFields(proposedBlockHeader)
|
||||
proposedBlockQuorumCert, round, _, err := x.getExtraFields(proposedBlockHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if x.lockQuorumCert == nil || quorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round {
|
||||
x.lockQuorumCert = quorumCert
|
||||
if x.lockQuorumCert == nil || proposedBlockQuorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round {
|
||||
x.lockQuorumCert = proposedBlockQuorumCert
|
||||
}
|
||||
|
||||
proposedBlockRound := &round
|
||||
// 3. Update commit block info
|
||||
_, err = x.commitBlocks(blockChainReader, proposedBlockHeader, proposedBlockRound)
|
||||
_, err = x.commitBlocks(blockChainReader, proposedBlockHeader, proposedBlockRound, incomingQuorumCert)
|
||||
if err != nil {
|
||||
log.Error("[processQC] Fail to commitBlocks", "proposedBlockRound", proposedBlockRound)
|
||||
log.Error("[processQC] Error while to commitBlocks", "proposedBlockRound", proposedBlockRound)
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 4. Set new round
|
||||
if quorumCert.ProposedBlockInfo.Round >= x.currentRound {
|
||||
err := x.setNewRound(blockChainReader, quorumCert.ProposedBlockInfo.Round+1)
|
||||
if incomingQuorumCert.ProposedBlockInfo.Round >= x.currentRound {
|
||||
err := x.setNewRound(blockChainReader, incomingQuorumCert.ProposedBlockInfo.Round+1)
|
||||
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", incomingQuorumCert.ProposedBlockInfo.Round+1)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -893,7 +893,7 @@ func (x *XDPoS_v2) getSyncInfo() *utils.SyncInfo {
|
|||
}
|
||||
|
||||
//Find parent and grandparent, check round number, if so, commit grandparent(grandGrandParent of currentBlock)
|
||||
func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposedBlockHeader *types.Header, proposedBlockRound *utils.Round) (bool, error) {
|
||||
func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposedBlockHeader *types.Header, proposedBlockRound *utils.Round, incomingQc *utils.QuorumCert) (bool, error) {
|
||||
// XDPoS v1.0 switch to v2.0, skip commit
|
||||
if big.NewInt(0).Sub(proposedBlockHeader.Number, big.NewInt(2)).Cmp(x.config.V2.SwitchBlock) <= 0 {
|
||||
return false, nil
|
||||
|
|
@ -930,6 +930,10 @@ func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposed
|
|||
Round: round,
|
||||
}
|
||||
log.Debug("Successfully committed block", "Committed block Hash", x.highestCommitBlock.Hash, "Committed round", x.highestCommitBlock.Round)
|
||||
// Perform forensics related operation
|
||||
var headerQcToBeCommitted []types.Header
|
||||
headerQcToBeCommitted = append(headerQcToBeCommitted, *parentBlock, *proposedBlockHeader)
|
||||
go x.forensics.SetCommittedQCs(headerQcToBeCommitted, *incomingQc)
|
||||
return true, nil
|
||||
}
|
||||
// Everything else, fail to commit
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
const (
|
||||
NUM_OF_FORENSICS_PARENTS = 2
|
||||
)
|
||||
|
||||
type ForensicProof struct {
|
||||
QcWithSmallerRound utils.QuorumCert
|
||||
QcWithLargerRound utils.QuorumCert
|
||||
|
|
@ -17,36 +25,68 @@ type ForensicProof struct {
|
|||
QcWithLargerRoundAddresses []common.Address
|
||||
}
|
||||
|
||||
// Forensics instance. Placeholder for future properties to be added
|
||||
type Forensics struct {
|
||||
ReceiverCh <-chan utils.QuorumCert
|
||||
Abort chan<- struct{}
|
||||
HighestCommittedQCs []utils.QuorumCert
|
||||
}
|
||||
|
||||
// Initiate a forensics process
|
||||
func (x *XDPoS_v2) AttachForensics() {
|
||||
receiver := make(chan utils.QuorumCert)
|
||||
abort := make(chan struct{})
|
||||
func NewForensics() *Forensics {
|
||||
return &Forensics{}
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
// A real event arrived, process interesting content
|
||||
select {
|
||||
case quorumCert := <-receiver:
|
||||
x.ProcessForensics(quorumCert)
|
||||
case <-abort:
|
||||
return
|
||||
/*
|
||||
Entry point for processing forensics.
|
||||
Triggered once processQC is successfully.
|
||||
Forensics runs in a seperate go routine as its no system critical
|
||||
Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/97878029/Forensics+Diagram+flow
|
||||
*/
|
||||
func (f *Forensics) ProcessForensics(chain consensus.ChainReader, incomingQC utils.QuorumCert) {
|
||||
log.Info("Received a QC in forensics", "QC", incomingQC)
|
||||
}
|
||||
|
||||
// Set the forensics committed QCs list. The order is from grandparent to current header. i.e it shall follow the QC in its header as follow [hcqc1, hcqc2, hcqc3]
|
||||
func (f *Forensics) SetCommittedQCs(headers []types.Header, incomingQC utils.QuorumCert) error {
|
||||
// highestCommitQCs is an array, assign the parentBlockQc and its child as well as its grandchild QC into this array for forensics purposes.
|
||||
if len(headers) != NUM_OF_FORENSICS_PARENTS {
|
||||
log.Error("[SetCommittedQcs] Received input length not equal to 2", len(headers))
|
||||
return fmt.Errorf("Received headers length not equal to 2 ")
|
||||
}
|
||||
|
||||
var committedQCs []utils.QuorumCert
|
||||
for i, h := range headers {
|
||||
var decodedExtraField utils.ExtraFields_v2
|
||||
// Decode the qc1 and qc2
|
||||
err := utils.DecodeBytesExtraFields(h.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[SetCommittedQCs] Fail to decode extra when committing QC to forensics", "Error", err, "Index", i)
|
||||
return err
|
||||
}
|
||||
if i != 0 {
|
||||
if decodedExtraField.QuorumCert.ProposedBlockInfo.Hash != headers[i-1].Hash() {
|
||||
log.Error("[SetCommittedQCs] Headers shall be on the same chain and in the right order", "ParentHash", h.ParentHash.Hex(), "headers[i-1].Hash()", headers[i-1].Hash().Hex())
|
||||
return fmt.Errorf("Headers shall be on the same chain and in the right order")
|
||||
} else if i == len(headers)-1 { // The last header shall be pointed by the incoming QC
|
||||
if incomingQC.ProposedBlockInfo.Hash != h.Hash() {
|
||||
log.Error("[SetCommittedQCs] incomingQc is not pointing at the last header received", "hash", h.Hash().Hex(), "incomingQC.ProposedBlockInfo.Hash", incomingQC.ProposedBlockInfo.Hash.Hex())
|
||||
return fmt.Errorf("incomingQc is not pointing at the last header received")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
x.forensics = &Forensics{
|
||||
ReceiverCh: receiver,
|
||||
Abort: abort,
|
||||
|
||||
committedQCs = append(committedQCs, *decodedExtraField.QuorumCert)
|
||||
}
|
||||
f.HighestCommittedQCs = append(committedQCs, incomingQC)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) SendForensicProof() {
|
||||
// Last step of forensics which sends out detailed proof to report service.
|
||||
func (f *Forensics) SendForensicProof() {
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) ProcessForensics(quorumCert utils.QuorumCert) {
|
||||
log.Info("Received a QC in forensics", "QC", quorumCert)
|
||||
// Find the blockInfo of the block -2 distance away from the QC. Note: We using block number which means not necessary on the same chain as QC received
|
||||
func (f *Forensics) findParentsQc(chain consensus.ChainReader, currentQc utils.QuorumCert, distanceFromCurrrentQc int64) {
|
||||
}
|
||||
|
||||
func (f *Forensics) findCommonSigners(currentQc utils.QuorumCert, higherQc utils.QuorumCert) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,3 +73,7 @@ func (x *XDPoS_v2) HygieneTimeoutPoolFaker() {
|
|||
func (x *XDPoS_v2) GetTimeoutPoolKeyListFaker() []string {
|
||||
return x.timeoutPool.PoolObjKeysList()
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetForensicsFaker() *Forensics {
|
||||
return x.forensics
|
||||
}
|
||||
|
|
|
|||
99
consensus/tests/engine_v2_tests/forensics_test.go
Normal file
99
consensus/tests/engine_v2_tests/forensics_test.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcessQcShallSetForensicsCommittedQc(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, 0)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Assuming we are getting block 906 which have QC pointing at block 905
|
||||
blockInfo := &utils.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: utils.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &utils.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := utils.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, utils.Round(5), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg := &utils.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
signedHash = SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg = &utils.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Create another vote which is signed by someone not from the master node list
|
||||
randomSigner, randomSignFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
assert.Nil(t, err)
|
||||
randomlySignedHash, err := randomSignFn(accounts.Account{Address: randomSigner}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg = &utils.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: randomlySignedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook and increment the round to 6
|
||||
signedHash = SignHashByPK(acc3Key, voteSigningHash.Bytes())
|
||||
voteMsg = &utils.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
assert.Equal(t, 3, len(engineV2.GetForensicsFaker().HighestCommittedQCs))
|
||||
}
|
||||
|
||||
func TestSetCommittedQCsInOrder(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, 0)
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
var headers []types.Header
|
||||
var decodedExtraField utils.ExtraFields_v2
|
||||
// Decode the qc1 and qc2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(903), *blockchain.GetHeaderByNumber(902)), *decodedExtraField.QuorumCert)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "Headers shall be on the same chain and in the right order", err.Error())
|
||||
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(903), *blockchain.GetHeaderByNumber(904)), *decodedExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(forensics.HighestCommittedQCs))
|
||||
}
|
||||
Loading…
Reference in a new issue