From 3ebaea19454979e56fecc0c21c0adcda130ef519 Mon Sep 17 00:00:00 2001 From: Jerome Date: Thu, 30 Jun 2022 07:58:18 +1000 Subject: [PATCH] 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 --- .../XDPoS/engines/engine_v2/forensics.go | 37 ++++++---- .../tests/engine_v2_tests/forensics_test.go | 68 +++++++++++-------- core/types/forensics.go | 22 +++--- ethstats/ethstats.go | 6 +- 4 files changed, 78 insertions(+), 55 deletions(-) diff --git a/consensus/XDPoS/engines/engine_v2/forensics.go b/consensus/XDPoS/engines/engine_v2/forensics.go index da841ca4aa..4f66da628c 100644 --- a/consensus/XDPoS/engines/engine_v2/forensics.go +++ b/consensus/XDPoS/engines/engine_v2/forensics.go @@ -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()) diff --git a/consensus/tests/engine_v2_tests/forensics_test.go b/consensus/tests/engine_v2_tests/forensics_test.go index b89874d111..e546f4540b 100644 --- a/consensus/tests/engine_v2_tests/forensics_test.go +++ b/consensus/tests/engine_v2_tests/forensics_test.go @@ -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() diff --git a/core/types/forensics.go b/core/types/forensics.go index 0d0ac54f2f..76e6b0a9b5 100644 --- a/core/types/forensics.go +++ b/core/types/forensics.go @@ -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 { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 4a380dec56..12a5e8173a 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -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,