diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index d03f324d4c..c06f3c53ff 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -433,6 +433,16 @@ func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNum } } +func (x *XDPoS) CalculateMissingRounds(chain consensus.ChainReader, hash common.Hash) (*utils.PublicApiMissedRoundsMetadata, error) { + header := chain.GetHeaderByHash(hash) + switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) { + case params.ConsensusEngineVersion2: + return x.EngineV2.CalculateMissingRounds(chain, header) + default: // Default "v1" + return nil, fmt.Errorf("Not supported in the v1 consensus") + } +} + // Same DB across all consensus engines func (x *XDPoS) GetDb() ethdb.Database { return x.db diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 768e8b9a5c..aec4fc4f6c 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -285,6 +285,13 @@ func (api *API) NetworkInformation() NetworkInformation { return info } +/* +An API exclusively for V2 consensus, designed to assist in troubleshooting miners by identifying who mined during their allocated term. +*/ +func (api *API) GetMissiedRoundsInEpochByBlockHash(hash common.Hash) (*utils.PublicApiMissedRoundsMetadata, error) { + return api.XDPoS.CalculateMissingRounds(api.chain, hash) +} + func calculateSigners(message map[string]SignerTypes, pool map[string]map[common.Hash]utils.PoolObj, masternodes []common.Address) { for name, objs := range pool { var currentSigners []common.Address diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 3e7eabd8df..cedeaa2f0b 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -360,7 +360,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er } if header.Coinbase != signer { - log.Error("[Prepare] The mined blocker header coinbase address mismatch with waller address", "headerCoinbase", header.Coinbase.Hex(), "WalletAddress", signer.Hex()) + log.Error("[Prepare] The mined blocker header coinbase address mismatch with wallet address", "headerCoinbase", header.Coinbase.Hex(), "WalletAddress", signer.Hex()) return consensus.ErrCoinbaseMismatch } diff --git a/consensus/XDPoS/engines/engine_v2/mining.go b/consensus/XDPoS/engines/engine_v2/mining.go index 69c861acc7..546de844a8 100644 --- a/consensus/XDPoS/engines/engine_v2/mining.go +++ b/consensus/XDPoS/engines/engine_v2/mining.go @@ -50,6 +50,7 @@ func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round types.Round, pare return false, nil } + // TODO: USE BELOW FOR THE NEW API leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes)) x.whosTurn = masterNodes[leaderIndex] if x.whosTurn != signer { diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index 1c3497ad60..395493f7e5 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -163,3 +163,52 @@ func (x *XDPoS_v2) GetSignersFromSnapshot(chain consensus.ChainReader, header *t snap, err := x.getSnapshot(chain, header.Number.Uint64(), false) return snap.NextEpochMasterNodes, err } + +func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiMissedRoundsMetadata, error) { + var missedRounds []utils.MissedRoundInfo + switchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash()) + if err != nil { + return nil, err + } + masternodes := switchInfo.Masternodes + + // Loop through from the epoch switch block to the current "header" block + nextHeader := header + for nextHeader.Number.Cmp(switchInfo.EpochSwitchBlockInfo.Number) > 0 { + parentHeader := chain.GetHeaderByHash(nextHeader.ParentHash) + parentRound, err := x.GetRoundNumber(parentHeader) + if err != nil { + return nil, err + } + currRound, err := x.GetRoundNumber(nextHeader) + if err != nil { + return nil, err + } + // This means there is a missing round incrementation when producing blocks + if parentRound+1 != currRound { + // We need to loop through between parentRound and the currRound to assess which miner did not mine + for i := parentRound; i < currRound; i++ { + leaderIndex := uint64(i) % x.config.Epoch % uint64(len(masternodes)) + whosTerm := masternodes[leaderIndex] + missedRounds = append( + missedRounds, + utils.MissedRoundInfo{ + Round: i, + Miner: whosTerm, + CurrentBlockHash: nextHeader.Hash(), + ParentBlockHash: parentHeader.Hash(), + }, + ) + } + } + // Assign the pointer to the next one + nextHeader = parentHeader + } + missedRoundsMetadata := &utils.PublicApiMissedRoundsMetadata{ + EpochRound: switchInfo.EpochSwitchBlockInfo.Round, + EpochBlockNumber: switchInfo.EpochSwitchBlockInfo.Number, + MissedRounds: missedRounds, + } + + return missedRoundsMetadata, nil +} diff --git a/consensus/XDPoS/utils/types.go b/consensus/XDPoS/utils/types.go index 4f55973cfd..3b60688c36 100644 --- a/consensus/XDPoS/utils/types.go +++ b/consensus/XDPoS/utils/types.go @@ -57,3 +57,15 @@ type PublicApiSnapshot struct { Votes []*clique.Vote `json:"votes"` // List of votes cast in chronological order Tally map[common.Address]clique.Tally `json:"tally"` // Current vote tally to avoid recalculating } + +type MissedRoundInfo struct { + Round types.Round + Miner common.Address + CurrentBlockHash common.Hash + ParentBlockHash common.Hash +} +type PublicApiMissedRoundsMetadata struct { + EpochRound types.Round + EpochBlockNumber *big.Int + MissedRounds []MissedRoundInfo +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index f80b2b6e0a..e479fea342 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -156,6 +156,11 @@ web3._extend({ name: 'getLatestPoolStatus', call: 'XDPoS_getLatestPoolStatus' }), + new web3._extend.Method({ + name: 'GetMissiedRoundsInEpochByBlockHash', + call: 'XDPoS_GetMissiedRoundsInEpochByBlockHash', + params: 1 + }), ], properties: [ new web3._extend.Property({