Skip to content

ethstats: added votes count to block stats #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 3, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions ethstats/ethstats.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/gorilla/websocket"
)
Expand Down Expand Up @@ -70,6 +71,8 @@ type backend interface {
GetTd(ctx context.Context, hash common.Hash) *big.Int
Stats() (pending int, queued int)
SyncProgress() ethereum.SyncProgress
// ADDED by Jakub Pajek (ethstats votes count)
ChainConfig() *params.ChainConfig
}

// fullNodeBackend encompasses the functionality necessary for a full node
Expand Down Expand Up @@ -585,6 +588,8 @@ type blockStats struct {
TxHash common.Hash `json:"transactionsRoot"`
Root common.Hash `json:"stateRoot"`
Uncles uncleStats `json:"uncles"`
// ADDED by Jakub Pajek (ethstats votes count)
Votes voteStats `json:"votes"`
}

// txStats is the information to report about individual transactions.
Expand All @@ -603,6 +608,55 @@ func (s uncleStats) MarshalJSON() ([]byte, error) {
return []byte("[]"), nil
}

// ADDED by Jakub Pajek BEG (ethstats votes count)

type vote struct {
Address common.Address `json:"address"`
Proposal string `json:"proposal"`
}

// voteStats is a custom wrapper around an vote array to force serializing
// empty arrays instead of returning null for them.
type voteStats []vote

func (s voteStats) MarshalJSON() ([]byte, error) {
if votes := ([]vote)(s); len(votes) > 0 {
return json.Marshal(votes)
}
return []byte("[]"), nil
}

// isTTDReached checks if the TotalTerminalDifficulty has been surpassed on the `parentHash` block.
// It depends on the parentHash already being stored in the database.
// If the parentHash is not stored in the database a UnknownAncestor error is returned.
func (s *Service) isTTDReached(parentHash common.Hash) (bool, error) {
ttd := s.backend.ChainConfig().TerminalTotalDifficulty
if ttd == nil {
return false, nil
}
td := s.backend.GetTd(context.Background(), parentHash)
if td == nil {
return false, consensus.ErrUnknownAncestor
}
return td.Cmp(ttd) >= 0, nil
}

// poaEngine returns the PoA consensus engine, or nil if PoW or PoS is being used.
func (s *Service) poaEngine(header *types.Header) consensus.PoA {
if pos, ok := s.engine.(consensus.PoS); ok {
if poa, ok := pos.EthOneEngine().(consensus.PoA); ok {
if reached, err := s.isTTDReached(header.ParentHash); err == nil && !reached {
return poa
}
}
} else if poa, ok := s.engine.(consensus.PoA); ok {
return poa
}
return nil
}

// ADDED by Jakub Pajek END (ethstats votes count)

// reportBlock retrieves the current chain head and reports it to the stats server.
func (s *Service) reportBlock(conn *connWrapper, block *types.Block) error {
// Gather the block details from the header or block chain
Expand Down Expand Up @@ -630,6 +684,8 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
td *big.Int
txs []txStats
uncles []*types.Header
// ADDED by Jakub Pajek (ethstats votes count)
votes []vote
)

// check if backend is a full node
Expand Down Expand Up @@ -667,6 +723,45 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
txs = []txStats{}
}

// ADDED by Jakub Pajek BEG (ethstats votes count)
if poa, cliqueCfg := s.poaEngine(header), s.backend.ChainConfig().Clique; poa != nil && cliqueCfg != nil {
// MEMO by Jakub Pajek (clique config: variable period)
// How to handle variable epoch changing with the number of sealers?
cliqueEpoch := cliqueCfg[0].Epoch
if cliqueEpoch == 0 {
cliqueEpoch = params.CliqueEpoch
}
if checkpoint, extraBytes := header.Number.Uint64()%cliqueEpoch == 0, len(header.Extra)-params.CliqueExtraVanity-params.CliqueExtraSeal; !checkpoint && extraBytes > 0 {
voteCount := extraBytes / (common.AddressLength + 1)
votes = make([]vote, voteCount)
for voteIdx := 0; voteIdx < voteCount; voteIdx++ {
// Get the address of the vote
index := params.CliqueExtraVanity + voteIdx*(common.AddressLength+1)
var address common.Address
copy(address[:], header.Extra[index:])
// Get the proposal of the vote
index += common.AddressLength
var proposal string
switch header.Extra[index] {
case params.CliqueExtraVoterVote:
proposal = "voter"
case params.CliqueExtraSignerVote:
proposal = "signer"
case params.CliqueExtraDropVote:
proposal = "drop"
default:
proposal = "unknown"
}
// Add the vote
votes[voteIdx] = vote{
Address: address,
Proposal: proposal,
}
}
}
}
// ADDED by Jakub Pajek END (ethstats votes count)

// Assemble and return the block stats
author, _ := s.engine.Author(header)

Expand All @@ -684,6 +779,8 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
TxHash: header.TxHash,
Root: header.Root,
Uncles: uncles,
// ADDED by Jakub Pajek (ethstats votes count)
Votes: votes,
}
}

Expand Down