Last active
July 21, 2022 12:34
-
-
Save nnkken/01056b761ee60a53afa9f7a46b4b8b7d to your computer and use it in GitHub Desktop.
Count online voting power during upgrade
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
const axios = require('axios'); | |
const BigNumber = require('bignumber.js'); | |
const lcdEndpoint = 'http://localhost:1317'; | |
const rpcEndpoint = 'http://localhost:26657'; | |
const nilVote = 'nil-Vote'; | |
const lcd = axios.create({ | |
baseURL: lcdEndpoint, | |
}); | |
const rpc = axios.create({ | |
baseURL: rpcEndpoint, | |
}); | |
async function getValidators() { | |
const res = await lcd.get('/cosmos/staking/v1beta1/validators?pagination.limit=300'); | |
const { validators } = res.data; | |
return validators; | |
} | |
async function getDumpConsensusState() { | |
const res = await rpc.get('/dump_consensus_state'); | |
const { result } = res.data; | |
return result; | |
} | |
function buildValidatorPubKeyMap(validators) { | |
const map = new Map(); | |
for (const v of validators) { | |
const { consensus_pubkey: { key: conPubKey } } = v; | |
map.set(conPubKey, v); | |
} | |
return map; | |
} | |
function getPendingHeight(dumpConsensusState) { | |
return Number.parseInt(dumpConsensusState.round_state.height, 10); | |
} | |
function getCurrentRound(dumpConsensusState) { | |
return dumpConsensusState.round_state.round; | |
} | |
function getLastCommitVotes(dumpConsensusState) { | |
return dumpConsensusState.round_state.last_commit.votes; | |
} | |
function getCurrentPrevotes(dumpConsensusState) { | |
return dumpConsensusState.round_state.votes[getCurrentRound(dumpConsensusState)].prevotes; | |
} | |
function getIsProducingBlock(dumpConsensusState) { | |
return getCurrentPrevotes(dumpConsensusState).every((v) => v === nilVote); | |
} | |
function extractVoteData(vote) { | |
const result = { | |
isOnline: false, | |
}; | |
if (vote === nilVote) { | |
return result; | |
} | |
result.isOnline = true; | |
const voteTimeRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z/ | |
const arr = voteTimeRegex.exec(vote); | |
if (!arr) { | |
return result; | |
} | |
result.voteTime = arr[0]; | |
return result; | |
} | |
function buildPubKeyOnlineStatusMap(tmValidators, votes) { | |
const map = new Map(); | |
for (let i = 0; i < tmValidators.length; i += 1) { | |
const { pub_key: { value: pubKey } } = tmValidators[i]; | |
map.set(pubKey, extractVoteData(votes[i])); | |
} | |
return map; | |
} | |
function getValidatorsWithOnlineInfo(validators, dumpConsensusState) { | |
const isProducingBlock = getIsProducingBlock(dumpConsensusState); | |
const pubKeyMap = buildValidatorPubKeyMap(validators); | |
const tmValidators = dumpConsensusState.round_state.validators.validators; | |
for (const v of tmValidators) { | |
pubKeyMap.get(v.pub_key.value).votingPower = new BigNumber(v.voting_power); | |
} | |
let onlinePubKeyMap; | |
if (isProducingBlock) { | |
onlinePubKeyMap = buildPubKeyOnlineStatusMap(tmValidators, getLastCommitVotes(dumpConsensusState)); | |
} else { | |
onlinePubKeyMap = buildPubKeyOnlineStatusMap(tmValidators, getCurrentPrevotes(dumpConsensusState)); | |
} | |
for (const [pubKey, onlineStatus] of onlinePubKeyMap.entries()) { | |
pubKeyMap.get(pubKey).onlineStatus = onlineStatus; | |
} | |
const validatorsSortedByVotingPowerDesc = [...tmValidators] | |
.sort((v1, v2) => new BigNumber(v2.voting_power).comparedTo(v1.voting_power)) | |
.map((v) => pubKeyMap.get(v.pub_key.value)) | |
return validatorsSortedByVotingPowerDesc; | |
} | |
async function main() { | |
const [rawValidators, dumpConsensusState] = await Promise.all([getValidators(), getDumpConsensusState()]); | |
const pendingHeight = getPendingHeight(dumpConsensusState); | |
const isProducingBlock = getIsProducingBlock(dumpConsensusState); | |
const validators = getValidatorsWithOnlineInfo(rawValidators, dumpConsensusState); | |
const onlineVotingPower = validators.filter((v) => v.onlineStatus.isOnline).reduce((sum, v) => sum.plus(v.votingPower), new BigNumber(0)); | |
const totalVotingPower = validators.reduce((sum, v) => sum.plus(v.votingPower), new BigNumber(0)); | |
console.log({ time: new Date(), pendingHeight, isProducingBlock }); | |
console.log(`${'Validator'.padStart(40)}\t${'Online Time'.padStart(30)}\t${'Voting Power'.padStart(20)}\t${'%'.padStart(8)}`) | |
console.log('----------------------------------------------------------------------------------------------------') | |
for (const v of validators) { | |
console.log(`${v.description.moniker.padStart(40, ' ')}\t${(v.onlineStatus.isOnline ? (v.onlineStatus.voteTime || 'Online') : 'Offline').padStart(30)}\t${v.votingPower.toFixed().padStart(20)}\t${v.votingPower.div(totalVotingPower).times(100).toFixed(2).padStart(7)}%`) | |
} | |
console.log('----------------------------------------------------------------------------------------------------') | |
console.log(`Online/Total: ${onlineVotingPower.toFixed()}/${totalVotingPower.toFixed()} (${onlineVotingPower.div(totalVotingPower).times(100).toFixed(2)}%)`) | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment