allow forensics send msg to stats server (#95)

* allow forensics send msg to stats server

* add test for forensics reporting mechanism
This commit is contained in:
Jerome 2022-05-25 19:42:30 +10:00 committed by GitHub
parent ca6a645fca
commit 7effc71547
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 204 additions and 27 deletions

View file

@ -24,6 +24,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v1" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v1"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/consensus/clique" "github.com/XinFinOrg/XDPoSChain/consensus/clique"
"github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/state"
@ -65,6 +66,11 @@ type XDPoS struct {
EngineV2 *engine_v2.XDPoS_v2 EngineV2 *engine_v2.XDPoS_v2
} }
// Subscribe to consensus engines forensics events. Currently only exist for engine v2
func (x *XDPoS) SubscribeForensicsEvent(ch chan<- types.ForensicsEvent) event.Subscription {
return x.EngineV2.ForensicsProcessor.SubscribeForensicsEvent(ch)
}
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial // New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
// signers set to the ones provided by the user. // signers set to the ones provided by the user.
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS { func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS {

View file

@ -58,7 +58,7 @@ type XDPoS_v2 struct {
HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error)
HookPenalty func(chain consensus.ChainReader, number *big.Int, parentHash common.Hash, candidates []common.Address) ([]common.Address, error) HookPenalty func(chain consensus.ChainReader, number *big.Int, parentHash common.Hash, candidates []common.Address) ([]common.Address, error)
forensics *Forensics ForensicsProcessor *Forensics
} }
func New(config *params.XDPoSConfig, db ethdb.Database, waitPeriodCh chan int) *XDPoS_v2 { func New(config *params.XDPoSConfig, db ethdb.Database, waitPeriodCh chan int) *XDPoS_v2 {
@ -107,7 +107,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database, waitPeriodCh chan int) *
}, },
highestVotedRound: types.Round(0), highestVotedRound: types.Round(0),
highestCommitBlock: nil, highestCommitBlock: nil,
forensics: NewForensics(), ForensicsProcessor: NewForensics(),
} }
// Add callback to the timer // Add callback to the timer
timeoutTimer.OnTimeoutFn = engine.OnCountdownTimeout timeoutTimer.OnTimeoutFn = engine.OnCountdownTimeout
@ -925,7 +925,7 @@ func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposed
// Perform forensics related operation // Perform forensics related operation
var headerQcToBeCommitted []types.Header var headerQcToBeCommitted []types.Header
headerQcToBeCommitted = append(headerQcToBeCommitted, *parentBlock, *proposedBlockHeader) headerQcToBeCommitted = append(headerQcToBeCommitted, *parentBlock, *proposedBlockHeader)
go x.forensics.ForensicsMonitoring(blockChainReader, x, headerQcToBeCommitted, *incomingQc) go x.ForensicsProcessor.ForensicsMonitoring(blockChainReader, x, headerQcToBeCommitted, *incomingQc)
return true, nil return true, nil
} }
// Everything else, fail to commit // Everything else, fail to commit

View file

@ -10,6 +10,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/log"
) )
@ -17,22 +18,11 @@ const (
NUM_OF_FORENSICS_QC = 3 NUM_OF_FORENSICS_QC = 3
) )
type ForensicsInfo struct {
HashPath []string // HashesTillSmallerRoundQc or HashesTillLargerRoundQc
QuorumCert types.QuorumCert
SignerAddresses []string
}
type ForensicProof struct {
SmallerRoundInfo *ForensicsInfo
LargerRoundInfo *ForensicsInfo
DivergingHash common.Hash
AcrossEpochs bool
}
// Forensics instance. Placeholder for future properties to be added // Forensics instance. Placeholder for future properties to be added
type Forensics struct { type Forensics struct {
HighestCommittedQCs []types.QuorumCert HighestCommittedQCs []types.QuorumCert
forensicsFeed event.Feed
scope event.SubscriptionScope
} }
// Initiate a forensics process // Initiate a forensics process
@ -40,6 +30,12 @@ func NewForensics() *Forensics {
return &Forensics{} return &Forensics{}
} }
// SubscribeForensicsEvent registers a subscription of ForensicsEvent and
// starts sending event to the given channel.
func (f *Forensics) SubscribeForensicsEvent(ch chan<- types.ForensicsEvent) event.Subscription {
return f.scope.Track(f.forensicsFeed.Subscribe(ch))
}
func (f *Forensics) ForensicsMonitoring(chain consensus.ChainReader, engine *XDPoS_v2, headerQcToBeCommitted []types.Header, incomingQC types.QuorumCert) error { func (f *Forensics) ForensicsMonitoring(chain consensus.ChainReader, engine *XDPoS_v2, headerQcToBeCommitted []types.Header, incomingQC types.QuorumCert) error {
f.ProcessForensics(chain, engine, incomingQC) f.ProcessForensics(chain, engine, incomingQC)
return f.SetCommittedQCs(headerQcToBeCommitted, incomingQC) return f.SetCommittedQCs(headerQcToBeCommitted, incomingQC)
@ -132,7 +128,7 @@ func (f *Forensics) SendForensicProof(chain consensus.ChainReader, engine *XDPoS
lowerRoundQC := firstQc lowerRoundQC := firstQc
higherRoundQC := secondQc higherRoundQC := secondQc
if (secondQc.ProposedBlockInfo.Round - firstQc.ProposedBlockInfo.Round) < 0 { if secondQc.ProposedBlockInfo.Round < firstQc.ProposedBlockInfo.Round {
lowerRoundQC = secondQc lowerRoundQC = secondQc
higherRoundQC = firstQc higherRoundQC = firstQc
} }
@ -146,28 +142,36 @@ func (f *Forensics) SendForensicProof(chain consensus.ChainReader, engine *XDPoS
// Check if two QCs are across epoch, this is used as a indicator for the "prone to attack" scenario // Check if two QCs are across epoch, this is used as a indicator for the "prone to attack" scenario
lowerRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, lowerRoundQC.ProposedBlockInfo.Hash) lowerRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, lowerRoundQC.ProposedBlockInfo.Hash)
if err != nil {
log.Error("[SendForensicProof] Errir while trying to find lowerRoundQcEpochSwitchInfo", "lowerRoundQC.ProposedBlockInfo.Hash", lowerRoundQC.ProposedBlockInfo.Hash, "err", err)
return err
}
higherRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, higherRoundQC.ProposedBlockInfo.Hash) higherRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, higherRoundQC.ProposedBlockInfo.Hash)
if err != nil {
log.Error("[SendForensicProof] Errir while trying to find higherRoundQcEpochSwitchInfo", "higherRoundQC.ProposedBlockInfo.Hash", higherRoundQC.ProposedBlockInfo.Hash, "err", err)
return err
}
accrossEpoches := false accrossEpoches := false
if lowerRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash != higherRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash { if lowerRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash != higherRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash {
accrossEpoches = true accrossEpoches = true
} }
forensicsProof := &ForensicProof{ forensicsProof := &types.ForensicProof{
DivergingHash: ancestorHash, DivergingHash: ancestorHash,
AcrossEpochs: accrossEpoches, AcrossEpochs: accrossEpoches,
SmallerRoundInfo: &ForensicsInfo{ SmallerRoundInfo: &types.ForensicsInfo{
HashPath: ancestorToLowerRoundPath, HashPath: ancestorToLowerRoundPath,
QuorumCert: lowerRoundQC, QuorumCert: lowerRoundQC,
SignerAddresses: f.getQcSignerAddresses(lowerRoundQC), SignerAddresses: f.getQcSignerAddresses(lowerRoundQC),
}, },
LargerRoundInfo: &ForensicsInfo{ LargerRoundInfo: &types.ForensicsInfo{
HashPath: ancestorToHigherRoundPath, HashPath: ancestorToHigherRoundPath,
QuorumCert: higherRoundQC, QuorumCert: higherRoundQC,
SignerAddresses: f.getQcSignerAddresses(higherRoundQC), SignerAddresses: f.getQcSignerAddresses(higherRoundQC),
}, },
} }
// TODO: send to dedicated channel which will redirect to stats server
log.Info("Forensics proof report generated, sending to the stats server", forensicsProof) log.Info("Forensics proof report generated, sending to the stats server", forensicsProof)
go f.forensicsFeed.Send(types.ForensicsEvent{ForensicsProof: forensicsProof})
return nil return nil
} }

View file

@ -84,5 +84,5 @@ func (x *XDPoS_v2) AuthorizeFaker(signer common.Address) {
} }
func (x *XDPoS_v2) GetForensicsFaker() *Forensics { func (x *XDPoS_v2) GetForensicsFaker() *Forensics {
return x.forensics return x.ForensicsProcessor
} }

View file

@ -156,9 +156,33 @@ func TestForensicsMonitoringNotOnSameChainButHaveSameRoundQC(t *testing.T) {
parentOfForkedHeader := blockchain.GetBlockByHash(currentForkBlock.ParentHash()).Header() parentOfForkedHeader := blockchain.GetBlockByHash(currentForkBlock.ParentHash()).Header()
grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header() grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header()
forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader) forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader)
// Set up forensics events trigger
forensicsEventCh := make(chan types.ForensicsEvent)
forensics.SubscribeForensicsEvent(forensicsEventCh)
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC) err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC)
assert.Nil(t, err) assert.Nil(t, err)
// TODO: Check SendForensicProof triggered
// Check SendForensicProof triggered
for {
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))
return
case <-time.After(5 * time.Second):
t.FailNow()
}
}
} }
func TestForensicsMonitoringNotOnSameChainDoNotHaveSameRoundQC(t *testing.T) { func TestForensicsMonitoringNotOnSameChainDoNotHaveSameRoundQC(t *testing.T) {
@ -190,7 +214,86 @@ func TestForensicsMonitoringNotOnSameChainDoNotHaveSameRoundQC(t *testing.T) {
grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header() grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header()
forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader) forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader)
// Set up forensics events trigger
forensicsEventCh := make(chan types.ForensicsEvent)
forensics.SubscribeForensicsEvent(forensicsEventCh)
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC) err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC)
assert.Nil(t, err) assert.Nil(t, err)
// TODO: Check SendForensicProof triggered // Check SendForensicProof triggered
for {
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))
return
case <-time.After(5 * time.Second):
t.FailNow()
}
}
}
// "prone to attack" test where the "across epoch" field is true
func TestForensicsAcrossEpoch(t *testing.T) {
var numOfForks = new(int)
*numOfForks = 10
var forkRoundDifference = new(int)
*forkRoundDifference = 10
var forkedChainSignersKey []*ecdsa.PrivateKey
forkedChainSignersKey = append(forkedChainSignersKey, acc1Key)
blockchain, _, _, _, _, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 1801, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks, forkedRoundDifference: forkRoundDifference, signersKey: forkedChainSignersKey})
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
// Now, let's try set committed blocks, where the highestedCommitted blocks are 1799, 1800 and 1801
var headers []types.Header
var decodedBlock1801ExtraField types.ExtraFields_v2
err := utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(1801).Extra, &decodedBlock1801ExtraField)
assert.Nil(t, err)
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(1799), *blockchain.GetHeaderByNumber(1800)), *decodedBlock1801ExtraField.QuorumCert)
assert.Nil(t, err)
var decodedExtraField types.ExtraFields_v2
// Decode the QC from forking chain
err = utils.DecodeBytesExtraFields(currentForkBlock.Header().Extra, &decodedExtraField)
assert.Nil(t, err)
incomingQC := decodedExtraField.QuorumCert
var forkedHeaders []types.Header
parentOfForkedHeader := blockchain.GetBlockByHash(currentForkBlock.ParentHash()).Header()
grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header()
forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader)
// Set up forensics events trigger
forensicsEventCh := make(chan types.ForensicsEvent)
forensics.SubscribeForensicsEvent(forensicsEventCh)
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC)
assert.Nil(t, err)
// Check SendForensicProof triggered
for {
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))
return
case <-time.After(5 * time.Second):
t.FailNow()
}
}
} }

20
core/types/forensics.go Normal file
View file

@ -0,0 +1,20 @@
package types
import "github.com/XinFinOrg/XDPoSChain/common"
type ForensicsInfo struct {
HashPath []string // HashesTillSmallerRoundQc or HashesTillLargerRoundQc
QuorumCert QuorumCert
SignerAddresses []string
}
type ForensicProof struct {
SmallerRoundInfo *ForensicsInfo
LargerRoundInfo *ForensicsInfo
DivergingHash common.Hash
AcrossEpochs bool
}
type ForensicsEvent struct {
ForensicsProof *ForensicProof
}

View file

@ -33,6 +33,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/mclock" "github.com/XinFinOrg/XDPoSChain/common/mclock"
"github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth" "github.com/XinFinOrg/XDPoSChain/eth"
@ -56,6 +57,12 @@ const (
chainHeadChanSize = 10 chainHeadChanSize = 10
) )
type consensusEngine interface {
// SubscribeForensicsEvent should return an event subscription of
// ForensicsEvent and send events to the given channel.
SubscribeForensicsEvent(chan<- types.ForensicsEvent) event.Subscription
}
type txPool interface { type txPool interface {
// SubscribeTxPreEvent should return an event subscription of // SubscribeTxPreEvent should return an event subscription of
// TxPreEvent and send events to the given channel. // TxPreEvent and send events to the given channel.
@ -140,9 +147,11 @@ func (s *Service) loop() {
// Subscribe to chain events to execute updates on // Subscribe to chain events to execute updates on
var blockchain blockChain var blockchain blockChain
var txpool txPool var txpool txPool
var engine consensusEngine
if s.eth != nil { if s.eth != nil {
blockchain = s.eth.BlockChain() blockchain = s.eth.BlockChain()
txpool = s.eth.TxPool() txpool = s.eth.TxPool()
engine = s.eth.Engine().(*XDPoS.XDPoS)
} else { } else {
blockchain = s.les.BlockChain() blockchain = s.les.BlockChain()
txpool = s.les.TxPool() txpool = s.les.TxPool()
@ -156,11 +165,19 @@ func (s *Service) loop() {
txSub := txpool.SubscribeTxPreEvent(txEventCh) txSub := txpool.SubscribeTxPreEvent(txEventCh)
defer txSub.Unsubscribe() defer txSub.Unsubscribe()
// Forensics events
forensicsEventCh := make(chan types.ForensicsEvent)
if engine != nil {
forensicsSub := engine.SubscribeForensicsEvent(forensicsEventCh)
defer forensicsSub.Unsubscribe()
}
// Start a goroutine that exhausts the subsciptions to avoid events piling up // Start a goroutine that exhausts the subsciptions to avoid events piling up
var ( var (
quitCh = make(chan struct{}) quitCh = make(chan struct{})
headCh = make(chan *types.Block, 1) headCh = make(chan *types.Block, 1)
txCh = make(chan struct{}, 1) txCh = make(chan struct{}, 1)
forensicsCh = make(chan *types.ForensicProof, 1)
) )
go func() { go func() {
var lastTx mclock.AbsTime var lastTx mclock.AbsTime
@ -168,6 +185,11 @@ func (s *Service) loop() {
HandleLoop: HandleLoop:
for { for {
select { select {
case forensics := <-forensicsEventCh:
select {
case forensicsCh <- forensics.ForensicsProof:
default:
}
// Notify of chain head events, but drop if too frequent // Notify of chain head events, but drop if too frequent
case head := <-chainHeadCh: case head := <-chainHeadCh:
select { select {
@ -268,6 +290,10 @@ func (s *Service) loop() {
if err = s.reportPending(conn); err != nil { if err = s.reportPending(conn); err != nil {
log.Warn("Transaction stats report failed", "err", err) log.Warn("Transaction stats report failed", "err", err)
} }
case forensicsReport := <-forensicsCh:
if err = s.reportForensics(conn, forensicsReport); err != nil {
log.Error("Forensics proof stats report failed", "err", err)
}
} }
} }
// Make sure the connection is closed // Make sure the connection is closed
@ -519,6 +545,24 @@ func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error {
return websocket.JSON.Send(conn, report) return websocket.JSON.Send(conn, report)
} }
// 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,
)
stats := map[string]interface{}{
"id": s.node,
"forensicsProof": forensicsProof,
}
report := map[string][]interface{}{
"emit": {"forensics", stats},
}
return websocket.JSON.Send(conn, report)
}
// assembleBlockStats retrieves any required metadata to report a single block // assembleBlockStats retrieves any required metadata to report a single block
// and assembles the block stats. If block is nil, the current head is processed. // and assembles the block stats. If block is nil, the current head is processed.
func (s *Service) assembleBlockStats(block *types.Block) *blockStats { func (s *Service) assembleBlockStats(block *types.Block) *blockStats {