Skip to content

Instantly share code, notes, and snippets.

@nnkken
Last active July 21, 2022 12:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nnkken/01056b761ee60a53afa9f7a46b4b8b7d to your computer and use it in GitHub Desktop.
Save nnkken/01056b761ee60a53afa9f7a46b4b8b7d to your computer and use it in GitHub Desktop.
Count online voting power during upgrade
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