Skip to content

Instantly share code, notes, and snippets.

@ryan-u410
Created August 2, 2023 01:55
Show Gist options
  • Save ryan-u410/e9e81512636781ad12693a645a6d5325 to your computer and use it in GitHub Desktop.
Save ryan-u410/e9e81512636781ad12693a645a6d5325 to your computer and use it in GitHub Desktop.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)
const (
baseRPCURL = "$RPC_URL_HERE"
conStateURL = "/consensus_state?"
valURL1 = "/validators?page=1&per_page=200"
valURL2 = "/validators?page=2&per_page=200"
)
func main() {
vals := GetValidators()
conState := new(ConsensusStateResponse)
err := GetRequest(buildURL(conStateURL), &conState)
if err != nil {
panic(err)
}
var lastRound time.Time
roundsToCheck := len(conState.Result.RoundState.HeightVoteSet)
for r := range conState.Result.RoundState.HeightVoteSet[roundsToCheck-10 : roundsToCheck-1] {
round := conState.Result.RoundState.HeightVoteSet[roundsToCheck-10 : roundsToCheck-1][r]
fmt.Println("------------------")
fmt.Println("round: ", round.Round)
avgTimestamp := parseVotes(round.Prevotes, vals)
rDuration := avgTimestamp.Sub(lastRound)
fmt.Println("round est duration: ", rDuration.String())
fmt.Println("prevotes_bit_array: ", round.PrevotesBitArray)
fmt.Println("------------------")
lastRound = avgTimestamp
}
}
func GetValidators() []Validator {
result := make([]Validator, 0)
vals := new(ValsResponse)
valErr := GetRequest(buildURL(valURL1), &vals)
if valErr != nil {
panic(valErr)
}
result = append(result, vals.Result.Validators...)
vals2 := new(ValsResponse)
valErr2 := GetRequest(buildURL(valURL2), &vals2)
if valErr2 != nil {
panic(valErr2)
}
result = append(result, vals2.Result.Validators...)
return result
}
func GetRequest(url string, resp interface{}) error {
method := "GET"
client := &http.Client{}
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
return err
}
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return err
}
jsonErr := json.Unmarshal(body, &resp)
if jsonErr != nil {
return jsonErr
}
return nil
}
func GetValVotingPower(val string, vals []Validator) float64 {
for v := range vals {
this := vals[v]
if this.Address[0:12] == val[0:12] {
vp, err := this.VotingPower.Float64()
if err != nil {
panic(err)
}
return vp
}
}
return 0
}
func GetValVotingPowerTotal(vals []Validator) float64 {
var total float64 = 0
for v := range vals {
this := vals[v]
vp, err := this.VotingPower.Float64()
if err != nil {
panic(err)
}
total += vp
}
return total
}
func buildURL(url string) string {
return fmt.Sprintf("%v%v", baseRPCURL, url)
}
func parseVotes(v []string, vals []Validator) time.Time {
var offline float64 = 0
var zeroVote float64 = 0
var nonZeroVote float64 = 0
var meanTimeUnix int64 = 0
var divisor int64 = 0
for _, vote := range v {
if vote == "nil-Vote" {
continue
}
addr, vote, tstamp := parseVote(vote)
meanTimeUnix += tstamp.Unix()
divisor += 1
vp := GetValVotingPower(addr, vals)
if vote == "000000000000" {
zeroVote += vp
} else {
nonZeroVote += vp
}
}
if divisor == 0 {
return time.Unix(time.Now().Unix(), 0)
}
meanTimeStamp := meanTimeUnix / divisor
total := GetValVotingPowerTotal(vals)
offline = total - (zeroVote + nonZeroVote)
fmt.Println("timestamp: ", time.Unix(meanTimeStamp, 0).String())
fmt.Printf("total: %f\n", total)
fmt.Printf("offline: %f\n", offline)
fmt.Printf("zeroVote: %f\n", zeroVote)
fmt.Printf("nonZeroVote: %f\n", nonZeroVote)
fmt.Printf("total(offline + zeroVote + nonZeroVote): %.2f%%\n", 100*total/total)
fmt.Printf("offline (nil-Vote) %.2f%%\n", 100*offline/total)
fmt.Printf("zeroVote (000000000000): %.2f%%\n", 100*zeroVote/total)
fmt.Printf("nonZeroVote (XXXXXXXXXXX): %.2f%%\n", 100*nonZeroVote/total)
return time.Unix(meanTimeStamp, 0)
}
func parseVote(str string) (address string, vote string, tstamp time.Time) {
splits := strings.Split(str, " ")
addrSplits := strings.Split(splits[0], ":")
ts := splits[len(splits)-1]
t, err := time.Parse(time.RFC3339, ts[0:len(ts)-1])
if err != nil {
panic(err)
}
return addrSplits[1], splits[2], t
}
type ConsensusStateResponse struct {
Jsonrpc string `json:"jsonrpc"`
Id int `json:"id"`
Result struct {
RoundState struct {
HeightRoundStep string `json:"height/round/step"`
StartTime time.Time `json:"start_time"`
ProposalBlockHash string `json:"proposal_block_hash"`
LockedBlockHash string `json:"locked_block_hash"`
ValidBlockHash string `json:"valid_block_hash"`
HeightVoteSet []HVS `json:"height_vote_set"`
Proposer struct {
Address string `json:"address"`
Index int `json:"index"`
} `json:"proposer"`
} `json:"round_state"`
} `json:"result"`
}
type HVS struct {
Round int `json:"round"`
Prevotes []string `json:"prevotes"`
PrevotesBitArray string `json:"prevotes_bit_array"`
Precommits []string `json:"precommits"`
PrecommitsBitArray string `json:"precommits_bit_array"`
}
type ValsResponse struct {
Jsonrpc string `json:"jsonrpc"`
ID int `json:"id"`
Result struct {
BlockHeight string `json:"block_height"`
Validators []Validator `json:"validators"`
Count string `json:"count"`
Total string `json:"total"`
} `json:"result"`
}
type Validator struct {
Address string `json:"address"`
PubKey struct {
Type string `json:"type"`
Value string `json:"value"`
} `json:"pub_key"`
VotingPower json.Number `json:"voting_power"`
ProposerPriority string `json:"proposer_priority"`
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment