mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 18:30:45 +00:00
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:
parent
ca6a645fca
commit
7effc71547
7 changed files with 204 additions and 27 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
20
core/types/forensics.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue