update forensics proof data structure to accomedate vote type (#104)

* update forensics proof data structure to accomedate vote type

* refactor log

* change blocknum type to uint64

* fix test

Co-authored-by: Liam Lai <liam.icheng.lai@gmail.com>
This commit is contained in:
Jerome 2022-06-30 07:58:18 +10:00 committed by GitHub
parent 35b964fc16
commit 3ebaea1945
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 78 additions and 55 deletions

View file

@ -1,6 +1,7 @@
package engine_v2
import (
"encoding/json"
"fmt"
"math/big"
"reflect"
@ -46,7 +47,7 @@ func (f *Forensics) SetCommittedQCs(headers []types.Header, incomingQC types.Quo
// 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_QC-1 {
log.Error("[SetCommittedQcs] Received input length not equal to 2", len(headers))
return fmt.Errorf("Received headers length not equal to 2 ")
return fmt.Errorf("received headers length not equal to 2 ")
}
var committedQCs []types.QuorumCert
@ -55,13 +56,13 @@ func (f *Forensics) SetCommittedQCs(headers []types.Header, incomingQC types.Quo
// 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)
log.Error("[SetCommittedQCs] Fail to decode extra when committing QC to forensics", "err", 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")
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())
@ -114,7 +115,7 @@ func (f *Forensics) ProcessForensics(chain consensus.ChainReader, engine *XDPoS_
// Not found, need a more complex approach to find the two QC
ancestorQC, lowerRoundQCs, _, err := f.findAncestorQcThroughRound(chain, highestCommittedQCs, incomingQuorunCerts)
if err != nil {
log.Error("[ProcessForensics] Error while trying to find ancestor QC through round number", "Error", err)
log.Error("[ProcessForensics] Error while trying to find ancestor QC through round number", "err", err)
}
f.SendForensicProof(chain, engine, ancestorQC, lowerRoundQCs[NUM_OF_FORENSICS_QC-1])
}
@ -136,7 +137,7 @@ func (f *Forensics) SendForensicProof(chain consensus.ChainReader, engine *XDPoS
// Find common ancestor block
ancestorHash, ancestorToLowerRoundPath, ancestorToHigherRoundPath, err := f.FindAncestorBlockHash(chain, lowerRoundQC.ProposedBlockInfo, higherRoundQC.ProposedBlockInfo)
if err != nil {
log.Error("[SendForensicProof] Error while trying to find ancestor block hash", err)
log.Error("[SendForensicProof] Error while trying to find ancestor block hash", "err", err)
return err
}
@ -156,9 +157,9 @@ func (f *Forensics) SendForensicProof(chain consensus.ChainReader, engine *XDPoS
accrossEpoches = true
}
forensicsProof := &types.ForensicProof{
DivergingHash: ancestorHash,
AcrossEpochs: accrossEpoches,
content, err := json.Marshal(&types.ForensicsContent{
DivergingBlockHash: ancestorHash.Hex(),
AcrossEpoch: accrossEpoches,
SmallerRoundInfo: &types.ForensicsInfo{
HashPath: ancestorToLowerRoundPath,
QuorumCert: lowerRoundQC,
@ -169,8 +170,18 @@ func (f *Forensics) SendForensicProof(chain consensus.ChainReader, engine *XDPoS
QuorumCert: higherRoundQC,
SignerAddresses: f.getQcSignerAddresses(higherRoundQC),
},
})
if err != nil {
log.Error("[SendForensicProof] fail to json stringify forensics content", "err", err)
return err
}
log.Info("Forensics proof report generated, sending to the stats server", forensicsProof)
forensicsProof := &types.ForensicProof{
ForensicsType: "QC",
Content: string(content),
}
log.Info("Forensics proof report generated, sending to the stats server", "forensicsProof", forensicsProof)
go f.forensicsFeed.Send(types.ForensicsEvent{ForensicsProof: forensicsProof})
return nil
}
@ -187,7 +198,7 @@ func (f *Forensics) findAncestorQCs(chain consensus.ChainReader, currentQc types
parentHeader := chain.GetHeaderByHash(parentHash)
if parentHeader == nil {
log.Error("[findAncestorQCs] Forensics findAncestorQCs unable to find its parent block header", "BlockNum", parentHeader.Number.Int64(), "ParentHash", parentHash.Hex())
return nil, fmt.Errorf("Unable to find parent block header in forensics")
return nil, fmt.Errorf("unable to find parent block header in forensics")
}
var decodedExtraField types.ExtraFields_v2
err := utils.DecodeBytesExtraFields(parentHeader.Extra, &decodedExtraField)
@ -221,7 +232,7 @@ func (f *Forensics) checkQCsOnTheSameChain(chain consensus.ChainReader, highestC
var decodedExtraField types.ExtraFields_v2
err := utils.DecodeBytesExtraFields(parentHeader.Extra, &decodedExtraField)
if err != nil {
log.Error("[checkQCsOnTheSameChain] Fail to decode extra when checking the two QCs set on the same chain", "Error", err)
log.Error("[checkQCsOnTheSameChain] Fail to decode extra when checking the two QCs set on the same chain", "err", err)
return false, err
}
proposedBlockInfo = decodedExtraField.QuorumCert.ProposedBlockInfo
@ -322,7 +333,7 @@ func (f *Forensics) FindAncestorBlockHash(chain consensus.ChainReader, firstBloc
for i := 0; i < int(blockNumberDifference); i++ {
ph := chain.GetHeaderByHash(higherBlockNumberHash)
if ph == nil {
return common.Hash{}, ancestorToLowerBlockNumHashPath, ancestorToHigherBlockNumHashPath, fmt.Errorf("Unable to find parent block of hash %v", higherBlockNumberHash)
return common.Hash{}, ancestorToLowerBlockNumHashPath, ancestorToHigherBlockNumHashPath, fmt.Errorf("unable to find parent block of hash %v", higherBlockNumberHash)
}
higherBlockNumberHash = ph.ParentHash
ancestorToHigherBlockNumHashPath = append(ancestorToHigherBlockNumHashPath, ph.ParentHash.Hex())

View file

@ -2,6 +2,7 @@ package engine_v2_tests
import (
"crypto/ecdsa"
"encoding/json"
"math/big"
"testing"
"time"
@ -92,7 +93,7 @@ func TestSetCommittedQCsInOrder(t *testing.T) {
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())
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)
@ -169,15 +170,18 @@ func TestForensicsMonitoringNotOnSameChainButHaveSameRoundQC(t *testing.T) {
select {
case forensics := <-forensicsEventCh:
assert.NotNil(t, forensics.ForensicsProof)
assert.False(t, forensics.ForensicsProof.AcrossEpochs)
assert.Equal(t, types.Round(13), forensics.ForensicsProof.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(913), forensics.ForensicsProof.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 9, len(forensics.ForensicsProof.SmallerRoundInfo.HashPath))
assert.Equal(t, 4, len(forensics.ForensicsProof.SmallerRoundInfo.SignerAddresses))
assert.Equal(t, types.Round(13), forensics.ForensicsProof.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(912), forensics.ForensicsProof.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 8, len(forensics.ForensicsProof.LargerRoundInfo.HashPath))
assert.Equal(t, 4, len(forensics.ForensicsProof.LargerRoundInfo.SignerAddresses))
assert.Equal(t, "QC", forensics.ForensicsProof.ForensicsType)
content := &types.ForensicsContent{}
json.Unmarshal([]byte(forensics.ForensicsProof.Content), &content)
assert.False(t, content.AcrossEpoch)
assert.Equal(t, types.Round(13), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(913), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 9, len(content.SmallerRoundInfo.HashPath))
assert.Equal(t, 4, len(content.SmallerRoundInfo.SignerAddresses))
assert.Equal(t, types.Round(13), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(912), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 8, len(content.LargerRoundInfo.HashPath))
assert.Equal(t, 4, len(content.LargerRoundInfo.SignerAddresses))
return
case <-time.After(5 * time.Second):
t.FailNow()
@ -225,15 +229,19 @@ func TestForensicsMonitoringNotOnSameChainDoNotHaveSameRoundQC(t *testing.T) {
select {
case forensics := <-forensicsEventCh:
assert.NotNil(t, forensics.ForensicsProof)
assert.False(t, forensics.ForensicsProof.AcrossEpochs)
assert.Equal(t, types.Round(14), forensics.ForensicsProof.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(914), forensics.ForensicsProof.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 10, len(forensics.ForensicsProof.SmallerRoundInfo.HashPath))
assert.Equal(t, 4, len(forensics.ForensicsProof.SmallerRoundInfo.SignerAddresses))
assert.Equal(t, types.Round(16), forensics.ForensicsProof.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(906), forensics.ForensicsProof.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 2, len(forensics.ForensicsProof.LargerRoundInfo.HashPath))
assert.Equal(t, 2, len(forensics.ForensicsProof.LargerRoundInfo.SignerAddresses))
assert.Equal(t, "QC", forensics.ForensicsProof.ForensicsType)
content := &types.ForensicsContent{}
json.Unmarshal([]byte(forensics.ForensicsProof.Content), &content)
assert.False(t, content.AcrossEpoch)
assert.Equal(t, types.Round(14), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(914), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 10, len(content.SmallerRoundInfo.HashPath))
assert.Equal(t, 4, len(content.SmallerRoundInfo.SignerAddresses))
assert.Equal(t, types.Round(16), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(906), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 2, len(content.LargerRoundInfo.HashPath))
assert.Equal(t, 2, len(content.LargerRoundInfo.SignerAddresses))
return
case <-time.After(5 * time.Second):
t.FailNow()
@ -282,15 +290,19 @@ func TestForensicsAcrossEpoch(t *testing.T) {
select {
case forensics := <-forensicsEventCh:
assert.NotNil(t, forensics.ForensicsProof)
assert.True(t, forensics.ForensicsProof.AcrossEpochs)
assert.Equal(t, types.Round(900), forensics.ForensicsProof.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(1800), forensics.ForensicsProof.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 10, len(forensics.ForensicsProof.SmallerRoundInfo.HashPath))
assert.Equal(t, 4, len(forensics.ForensicsProof.SmallerRoundInfo.SignerAddresses))
assert.Equal(t, types.Round(902), forensics.ForensicsProof.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(1792), forensics.ForensicsProof.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 2, len(forensics.ForensicsProof.LargerRoundInfo.HashPath))
assert.Equal(t, 2, len(forensics.ForensicsProof.LargerRoundInfo.SignerAddresses))
assert.Equal(t, "QC", forensics.ForensicsProof.ForensicsType)
content := &types.ForensicsContent{}
json.Unmarshal([]byte(forensics.ForensicsProof.Content), &content)
assert.True(t, content.AcrossEpoch)
assert.Equal(t, types.Round(900), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(1800), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 10, len(content.SmallerRoundInfo.HashPath))
assert.Equal(t, 4, len(content.SmallerRoundInfo.SignerAddresses))
assert.Equal(t, types.Round(902), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
assert.Equal(t, uint64(1792), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
assert.Equal(t, 2, len(content.LargerRoundInfo.HashPath))
assert.Equal(t, 2, len(content.LargerRoundInfo.SignerAddresses))
return
case <-time.After(5 * time.Second):
t.FailNow()

View file

@ -1,18 +1,22 @@
package types
import "github.com/XinFinOrg/XDPoSChain/common"
type ForensicsInfo struct {
HashPath []string // HashesTillSmallerRoundQc or HashesTillLargerRoundQc
QuorumCert QuorumCert
SignerAddresses []string
HashPath []string `json:"hashPath"`
QuorumCert QuorumCert `json:"quorumCert"`
SignerAddresses []string `json:"signerAddresses"`
}
type ForensicsContent struct {
DivergingBlockNumber uint64 `json:"divergingBlockNumber"`
DivergingBlockHash string `json:"divergingBlockHash"`
AcrossEpoch bool `json:"acrossEpoch"`
SmallerRoundInfo *ForensicsInfo `json:"smallerRoundInfo"`
LargerRoundInfo *ForensicsInfo `json:"largerRoundInfo"`
}
type ForensicProof struct {
SmallerRoundInfo *ForensicsInfo
LargerRoundInfo *ForensicsInfo
DivergingHash common.Hash
AcrossEpochs bool
ForensicsType string `json:"forensicsType"` // QC or VOTE
Content string `json:"content"` // Json string of the forensics data
}
type ForensicsEvent struct {

View file

@ -547,11 +547,7 @@ func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error {
// reportForensics forward the forensics repors it to the stats server.
func (s *Service) reportForensics(conn *websocket.Conn, forensicsProof *types.ForensicProof) error {
log.Info(
"Sending Forensics report to ethstats",
"SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Hash", forensicsProof.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Hash,
"LargerRoundInfo.QuorumCert.ProposedBlockInfo.Hash", forensicsProof.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Hash,
)
log.Info("Sending Forensics report to ethstats", "ForensicsType", forensicsProof.ForensicsType)
stats := map[string]interface{}{
"id": s.node,