parent
a1ab81a896
commit
dad5c62df5
|
@ -13,6 +13,7 @@ use solana_client::{rpc_client::RpcClient, rpc_request::RpcVoteAccountInfo};
|
|||
use solana_sdk::{
|
||||
clock::{self, Slot},
|
||||
commitment_config::CommitmentConfig,
|
||||
epoch_schedule::{Epoch, EpochSchedule},
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, KeypairUtil},
|
||||
|
@ -261,6 +262,20 @@ fn new_spinner_progress_bar() -> ProgressBar {
|
|||
progress_bar
|
||||
}
|
||||
|
||||
/// Aggregate epoch credit stats and return (total credits, total slots, total epochs)
|
||||
pub fn aggregate_epoch_credits(
|
||||
epoch_credits: &[(Epoch, u64, u64)],
|
||||
epoch_schedule: &EpochSchedule,
|
||||
) -> (u64, u64, u64) {
|
||||
epoch_credits
|
||||
.iter()
|
||||
.fold((0, 0, 0), |acc, (epoch, credits, prev_credits)| {
|
||||
let credits_earned = credits - prev_credits;
|
||||
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
|
||||
(acc.0 + credits_earned, acc.1 + slots_in_epoch, acc.2 + 1)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_catchup(rpc_client: &RpcClient, node_pubkey: &Pubkey) -> ProcessResult {
|
||||
let cluster_nodes = rpc_client.get_cluster_nodes()?;
|
||||
|
||||
|
@ -551,6 +566,7 @@ pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult {
|
|||
}
|
||||
|
||||
pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool) -> ProcessResult {
|
||||
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
||||
let vote_accounts = rpc_client.get_vote_accounts()?;
|
||||
let total_active_stake = vote_accounts
|
||||
.current
|
||||
|
@ -593,19 +609,21 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
|
|||
println!(
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:<44} {} {} {} {}",
|
||||
" {:<44} {:<44} {} {} {} {:>7} {}",
|
||||
"Identity Pubkey",
|
||||
"Vote Account Pubkey",
|
||||
"Commission",
|
||||
"Last Vote",
|
||||
"Root Block",
|
||||
"Uptime",
|
||||
"Active Stake",
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
|
||||
fn print_vote_account(
|
||||
vote_account: &RpcVoteAccountInfo,
|
||||
vote_account: RpcVoteAccountInfo,
|
||||
epoch_schedule: &EpochSchedule,
|
||||
total_active_stake: f64,
|
||||
use_lamports_unit: bool,
|
||||
delinquent: bool,
|
||||
|
@ -617,8 +635,20 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
|
|||
format!("{}", v)
|
||||
}
|
||||
}
|
||||
|
||||
fn uptime(epoch_credits: Vec<(Epoch, u64, u64)>, epoch_schedule: &EpochSchedule) -> String {
|
||||
let (total_credits, total_slots, _) =
|
||||
aggregate_epoch_credits(&epoch_credits, &epoch_schedule);
|
||||
if total_slots > 0 {
|
||||
let total_uptime = 100_f64 * total_credits as f64 / total_slots as f64;
|
||||
format!("{:.2}%", total_uptime)
|
||||
} else {
|
||||
"-".into()
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
"{} {:<44} {:<44} {:>9}% {:>8} {:>10} {:>12}",
|
||||
"{} {:<44} {:<44} {:>9}% {:>8} {:>10} {:>7} {}",
|
||||
if delinquent {
|
||||
WARNING.to_string()
|
||||
} else {
|
||||
|
@ -629,6 +659,7 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
|
|||
vote_account.commission,
|
||||
non_zero_or_dash(vote_account.last_vote),
|
||||
non_zero_or_dash(vote_account.root_slot),
|
||||
uptime(vote_account.epoch_credits, epoch_schedule),
|
||||
if vote_account.activated_stake > 0 {
|
||||
format!(
|
||||
"{} ({:.2}%)",
|
||||
|
@ -641,11 +672,23 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
|
|||
);
|
||||
}
|
||||
|
||||
for vote_account in vote_accounts.current.iter() {
|
||||
print_vote_account(vote_account, total_active_stake, use_lamports_unit, false);
|
||||
for vote_account in vote_accounts.current.into_iter() {
|
||||
print_vote_account(
|
||||
vote_account,
|
||||
&epoch_schedule,
|
||||
total_active_stake,
|
||||
use_lamports_unit,
|
||||
false,
|
||||
);
|
||||
}
|
||||
for vote_account in vote_accounts.delinquent.iter() {
|
||||
print_vote_account(vote_account, total_active_stake, use_lamports_unit, true);
|
||||
for vote_account in vote_accounts.delinquent.into_iter() {
|
||||
print_vote_account(
|
||||
vote_account,
|
||||
&epoch_schedule,
|
||||
total_active_stake,
|
||||
use_lamports_unit,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
Ok("".to_string())
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
use crate::cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
||||
use crate::{
|
||||
cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||
ProcessResult,
|
||||
},
|
||||
cluster_query::aggregate_epoch_credits,
|
||||
};
|
||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
||||
|
@ -400,33 +404,37 @@ pub fn process_uptime(
|
|||
if !vote_state.votes.is_empty() {
|
||||
println!("Uptime:");
|
||||
|
||||
let epoch_credits_vec: Vec<(u64, u64, u64)> = vote_state.epoch_credits().copied().collect();
|
||||
|
||||
let epoch_credits = if let Some(x) = span {
|
||||
epoch_credits_vec.iter().rev().take(x as usize)
|
||||
let epoch_credits: Vec<(u64, u64, u64)> = if let Some(x) = span {
|
||||
vote_state
|
||||
.epoch_credits()
|
||||
.iter()
|
||||
.rev()
|
||||
.take(x as usize)
|
||||
.cloned()
|
||||
.collect()
|
||||
} else {
|
||||
epoch_credits_vec.iter().rev().take(epoch_credits_vec.len())
|
||||
vote_state.epoch_credits().iter().rev().cloned().collect()
|
||||
};
|
||||
|
||||
if aggregate {
|
||||
let (credits_earned, slots_in_epoch, epochs): (u64, u64, u64) =
|
||||
epoch_credits.fold((0, 0, 0), |acc, (epoch, credits, prev_credits)| {
|
||||
let credits_earned = credits - prev_credits;
|
||||
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
|
||||
(acc.0 + credits_earned, acc.1 + slots_in_epoch, acc.2 + 1)
|
||||
});
|
||||
let total_uptime = credits_earned as f64 / slots_in_epoch as f64;
|
||||
println!("{:.2}% over {} epochs", total_uptime * 100_f64, epochs,);
|
||||
let (total_credits, total_slots, epochs) =
|
||||
aggregate_epoch_credits(&epoch_credits, &epoch_schedule);
|
||||
if total_slots > 0 {
|
||||
let total_uptime = 100_f64 * total_credits as f64 / total_slots as f64;
|
||||
println!("{:.2}% over {} epochs", total_uptime, epochs);
|
||||
} else {
|
||||
println!("Insufficient voting history available");
|
||||
}
|
||||
} else {
|
||||
for (epoch, credits, prev_credits) in epoch_credits {
|
||||
let credits_earned = credits - prev_credits;
|
||||
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
|
||||
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
|
||||
let uptime = credits_earned as f64 / slots_in_epoch as f64;
|
||||
println!("- epoch: {} {:.2}% uptime", epoch, uptime * 100_f64,);
|
||||
}
|
||||
}
|
||||
if let Some(x) = span {
|
||||
if x > epoch_credits_vec.len() as u64 {
|
||||
if x > vote_state.epoch_credits().len() as u64 {
|
||||
println!("(span longer than available epochs)");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,10 @@ pub struct RpcVoteAccountInfo {
|
|||
/// Whether this account is staked for the current epoch
|
||||
pub epoch_vote_account: bool,
|
||||
|
||||
/// History of how many credits earned by the end of each epoch
|
||||
/// each tuple is (Epoch, credits, prev_credits)
|
||||
pub epoch_credits: Vec<(Epoch, u64, u64)>,
|
||||
|
||||
/// Most recent slot voted on by this vote account (0 if no votes exist)
|
||||
pub last_vote: u64,
|
||||
|
||||
|
|
|
@ -251,6 +251,7 @@ impl JsonRpcRequestProcessor {
|
|||
activated_stake: *activated_stake,
|
||||
commission: vote_state.commission,
|
||||
root_slot: vote_state.root_slot.unwrap_or(0),
|
||||
epoch_credits: vote_state.epoch_credits().clone(),
|
||||
epoch_vote_account,
|
||||
last_vote,
|
||||
}
|
||||
|
@ -1014,7 +1015,10 @@ pub mod tests {
|
|||
system_transaction,
|
||||
transaction::TransactionError,
|
||||
};
|
||||
use solana_vote_program::{vote_instruction, vote_state::VoteInit};
|
||||
use solana_vote_program::{
|
||||
vote_instruction,
|
||||
vote_state::{Vote, VoteInit, MAX_LOCKOUT_HISTORY},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
|
@ -1022,20 +1026,23 @@ pub mod tests {
|
|||
};
|
||||
|
||||
const TEST_MINT_LAMPORTS: u64 = 1_000_000;
|
||||
const TEST_SLOTS_PER_EPOCH: u64 = 50;
|
||||
|
||||
struct RpcHandler {
|
||||
io: MetaIoHandler<Meta>,
|
||||
meta: Meta,
|
||||
bank: Arc<Bank>,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
blockhash: Hash,
|
||||
alice: Keypair,
|
||||
leader_pubkey: Pubkey,
|
||||
leader_vote_keypair: Keypair,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
confirmed_block_signatures: Vec<Signature>,
|
||||
}
|
||||
|
||||
fn start_rpc_handler_with_tx(pubkey: &Pubkey) -> RpcHandler {
|
||||
let (bank_forks, alice) = new_bank_forks();
|
||||
let (bank_forks, alice, leader_vote_keypair) = new_bank_forks();
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
|
||||
let commitment_slot0 = BlockCommitment::new([8; MAX_LOCKOUT_HISTORY]);
|
||||
|
@ -1077,7 +1084,7 @@ pub mod tests {
|
|||
|
||||
let request_processor = Arc::new(RwLock::new(JsonRpcRequestProcessor::new(
|
||||
JsonRpcConfig::default(),
|
||||
bank_forks,
|
||||
bank_forks.clone(),
|
||||
block_commitment_cache.clone(),
|
||||
blocktree,
|
||||
StorageState::default(),
|
||||
|
@ -1107,9 +1114,11 @@ pub mod tests {
|
|||
io,
|
||||
meta,
|
||||
bank,
|
||||
bank_forks,
|
||||
blockhash,
|
||||
alice,
|
||||
leader_pubkey,
|
||||
leader_vote_keypair,
|
||||
block_commitment_cache,
|
||||
confirmed_block_signatures,
|
||||
}
|
||||
|
@ -1120,7 +1129,7 @@ pub mod tests {
|
|||
let bob_pubkey = Pubkey::new_rand();
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let validator_exit = create_validator_exit(&exit);
|
||||
let (bank_forks, alice) = new_bank_forks();
|
||||
let (bank_forks, alice, _) = new_bank_forks();
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default()));
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
|
@ -1630,20 +1639,23 @@ pub mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
fn new_bank_forks() -> (Arc<RwLock<BankForks>>, Keypair) {
|
||||
fn new_bank_forks() -> (Arc<RwLock<BankForks>>, Keypair, Keypair) {
|
||||
let GenesisConfigInfo {
|
||||
mut genesis_config,
|
||||
mint_keypair,
|
||||
..
|
||||
voting_keypair,
|
||||
} = create_genesis_config(TEST_MINT_LAMPORTS);
|
||||
|
||||
genesis_config.rent.lamports_per_byte_year = 50;
|
||||
genesis_config.rent.exemption_threshold = 2.0;
|
||||
genesis_config.epoch_schedule =
|
||||
EpochSchedule::custom(TEST_SLOTS_PER_EPOCH, TEST_SLOTS_PER_EPOCH, false);
|
||||
|
||||
let bank = Bank::new(&genesis_config);
|
||||
(
|
||||
Arc::new(RwLock::new(BankForks::new(bank.slot(), bank))),
|
||||
mint_keypair,
|
||||
voting_keypair,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1905,8 +1917,10 @@ pub mod tests {
|
|||
let RpcHandler {
|
||||
io,
|
||||
meta,
|
||||
bank,
|
||||
mut bank,
|
||||
bank_forks,
|
||||
alice,
|
||||
leader_vote_keypair,
|
||||
..
|
||||
} = start_rpc_handler_with_tx(&Pubkey::new_rand());
|
||||
|
||||
|
@ -1936,7 +1950,42 @@ pub mod tests {
|
|||
.expect("process transaction");
|
||||
assert_eq!(bank.vote_accounts().len(), 2);
|
||||
|
||||
let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getVoteAccounts"}}"#);
|
||||
// Advance bank to the next epoch
|
||||
for _ in 0..TEST_SLOTS_PER_EPOCH {
|
||||
bank.freeze();
|
||||
|
||||
let instruction = vote_instruction::vote(
|
||||
&leader_vote_keypair.pubkey(),
|
||||
&leader_vote_keypair.pubkey(),
|
||||
Vote {
|
||||
slots: vec![bank.slot()],
|
||||
hash: bank.hash(),
|
||||
timestamp: None,
|
||||
},
|
||||
);
|
||||
|
||||
bank = bank_forks.write().unwrap().insert(Bank::new_from_parent(
|
||||
&bank,
|
||||
&Pubkey::default(),
|
||||
bank.slot() + 1,
|
||||
));
|
||||
|
||||
let transaction = Transaction::new_signed_with_payer(
|
||||
vec![instruction],
|
||||
Some(&alice.pubkey()),
|
||||
&[&alice, &leader_vote_keypair],
|
||||
bank.last_blockhash(),
|
||||
);
|
||||
|
||||
bank.process_transaction(&transaction)
|
||||
.expect("process transaction");
|
||||
}
|
||||
|
||||
let req = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getVoteAccounts","params":{}}}"#,
|
||||
json!([CommitmentConfig::recent()])
|
||||
);
|
||||
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
|
@ -1944,12 +1993,19 @@ pub mod tests {
|
|||
let vote_account_status: RpcVoteAccountStatus =
|
||||
serde_json::from_value(result["result"].clone()).unwrap();
|
||||
|
||||
// The bootstrap leader vote account will be delinquent as it has stake but has never
|
||||
// voted. The vote account with no stake should not be present.
|
||||
assert!(vote_account_status.current.is_empty());
|
||||
assert_eq!(vote_account_status.delinquent.len(), 1);
|
||||
for vote_account_info in vote_account_status.delinquent {
|
||||
assert_ne!(vote_account_info.activated_stake, 0);
|
||||
}
|
||||
// The vote account with no stake should not be present.
|
||||
assert!(vote_account_status.delinquent.is_empty());
|
||||
|
||||
// The leader vote account should be active and have voting history.
|
||||
assert_eq!(vote_account_status.current.len(), 1);
|
||||
let leader_info = &vote_account_status.current[0];
|
||||
assert_eq!(
|
||||
leader_info.vote_pubkey,
|
||||
leader_vote_keypair.pubkey().to_string()
|
||||
);
|
||||
assert_ne!(leader_info.activated_stake, 0);
|
||||
// Subtract one because the last vote always carries over to the next epoch
|
||||
let expected_credits = TEST_SLOTS_PER_EPOCH - MAX_LOCKOUT_HISTORY as u64 - 1;
|
||||
assert_eq!(leader_info.epoch_credits, vec![(0, expected_credits, 0)]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -334,8 +334,8 @@ impl VoteState {
|
|||
/// Each tuple of (Epoch, u64, u64) is read as (epoch, credits, prev_credits), where
|
||||
/// credits for each epoch is credits - prev_credits; while redundant this makes
|
||||
/// calculating rewards over partial epochs nice and simple
|
||||
pub fn epoch_credits(&self) -> impl Iterator<Item = &(Epoch, u64, u64)> {
|
||||
self.epoch_credits.iter()
|
||||
pub fn epoch_credits(&self) -> &Vec<(Epoch, u64, u64)> {
|
||||
&self.epoch_credits
|
||||
}
|
||||
|
||||
fn pop_expired_votes(&mut self, slot: Slot) {
|
||||
|
@ -1236,13 +1236,7 @@ mod tests {
|
|||
let mut vote_state = VoteState::default();
|
||||
|
||||
assert_eq!(vote_state.credits(), 0);
|
||||
assert_eq!(
|
||||
vote_state
|
||||
.epoch_credits()
|
||||
.cloned()
|
||||
.collect::<Vec<(Epoch, u64, u64)>>(),
|
||||
vec![]
|
||||
);
|
||||
assert_eq!(vote_state.epoch_credits().clone(), vec![]);
|
||||
|
||||
let mut expected = vec![];
|
||||
let mut credits = 0;
|
||||
|
@ -1260,46 +1254,19 @@ mod tests {
|
|||
}
|
||||
|
||||
assert_eq!(vote_state.credits(), credits);
|
||||
assert_eq!(
|
||||
vote_state
|
||||
.epoch_credits()
|
||||
.cloned()
|
||||
.collect::<Vec<(Epoch, u64, u64)>>(),
|
||||
expected
|
||||
);
|
||||
assert_eq!(vote_state.epoch_credits().clone(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vote_state_epoch0_no_credits() {
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
assert_eq!(
|
||||
vote_state
|
||||
.epoch_credits()
|
||||
.cloned()
|
||||
.collect::<Vec<(Epoch, u64, u64)>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(vote_state.epoch_credits().len(), 0);
|
||||
vote_state.increment_credits(1);
|
||||
assert_eq!(
|
||||
vote_state
|
||||
.epoch_credits()
|
||||
.cloned()
|
||||
.collect::<Vec<(Epoch, u64, u64)>>()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(vote_state.epoch_credits().len(), 0);
|
||||
|
||||
vote_state.increment_credits(2);
|
||||
assert_eq!(
|
||||
vote_state
|
||||
.epoch_credits()
|
||||
.cloned()
|
||||
.collect::<Vec<(Epoch, u64, u64)>>()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
assert_eq!(vote_state.epoch_credits().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in New Issue