Created
August 2, 2023 01:55
-
-
Save ryan-u410/e9e81512636781ad12693a645a6d5325 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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