diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index c00c23956..6a2801267 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -311,6 +311,7 @@ pub enum CliValidatorsSortOrder { Identity, LastVote, Root, + SkipRate, Stake, VoteAccount, } @@ -360,7 +361,7 @@ impl fmt::Display for CliValidators { writeln!( f, - "{} {:<44} {:<44} {:>3}% {:>14} {:>14} {:>13} {:>7} {}", + "{} {:<44} {:<44} {:>3}% {:>14} {:>14} {:>7} {:>8} {:>7} {}", if validator.delinquent { WARNING.to_string() } else { @@ -371,6 +372,11 @@ impl fmt::Display for CliValidators { validator.commission, non_zero_or_dash(validator.last_vote, highest_last_vote), non_zero_or_dash(validator.root_slot, highest_root), + if let Some(skip_rate) = validator.skip_rate { + format!("{:.2}%", skip_rate) + } else { + "- ".to_string() + }, validator.epoch_credits, validator.version, if validator.activated_stake > 0 { @@ -391,14 +397,15 @@ impl fmt::Display for CliValidators { 0 }; let header = style(format!( - "{:padding$} {:<44} {:<38} {} {} {} {:>11} {:^7} {}", + "{:padding$} {:<44} {:<38} {} {} {} {} {} {} {}", " ", "Identity", "Vote Account", "Commission", - "Last Vote ", - "Root Slot ", - "Epoch Credits", + "Last Vote ", + "Root Slot ", + "Skip Rate", + "Credits", "Version", "Active Stake", padding = padding + 1 @@ -429,6 +436,17 @@ impl fmt::Display for CliValidators { CliValidatorsSortOrder::VoteAccount => { sorted_validators.sort_by(|a, b| a.vote_account_pubkey.cmp(&b.vote_account_pubkey)); } + CliValidatorsSortOrder::SkipRate => { + sorted_validators.sort_by(|a, b| { + use std::cmp::Ordering; + match (a.skip_rate, b.skip_rate) { + (None, None) => Ordering::Equal, + (None, Some(_)) => Ordering::Greater, + (Some(_), None) => Ordering::Less, + (Some(a), Some(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Equal), + } + }); + } CliValidatorsSortOrder::Stake => { sorted_validators.sort_by_key(|a| a.activated_stake); } @@ -537,6 +555,7 @@ pub struct CliValidator { pub activated_stake: u64, pub version: String, pub delinquent: bool, + pub skip_rate: Option, } impl CliValidator { @@ -544,24 +563,41 @@ impl CliValidator { vote_account: &RpcVoteAccountInfo, current_epoch: Epoch, version: String, + skip_rate: Option, address_labels: &HashMap, ) -> Self { - Self::_new(vote_account, current_epoch, version, address_labels, false) + Self::_new( + vote_account, + current_epoch, + version, + skip_rate, + address_labels, + false, + ) } pub fn new_delinquent( vote_account: &RpcVoteAccountInfo, current_epoch: Epoch, version: String, + skip_rate: Option, address_labels: &HashMap, ) -> Self { - Self::_new(vote_account, current_epoch, version, address_labels, true) + Self::_new( + vote_account, + current_epoch, + version, + skip_rate, + address_labels, + true, + ) } fn _new( vote_account: &RpcVoteAccountInfo, current_epoch: Epoch, version: String, + skip_rate: Option, address_labels: &HashMap, delinquent: bool, ) -> Self { @@ -587,6 +623,7 @@ impl CliValidator { activated_stake: vote_account.activated_stake, version, delinquent, + skip_rate, } } } diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 0959825ac..1c61c8fb3 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -376,6 +376,7 @@ impl ClusterQuerySubCommands for App<'_, '_> { "identity", "last-vote", "root", + "skip-rate", "stake", "vote-account", ]) @@ -624,6 +625,7 @@ pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result CliValidatorsSortOrder::Identity, "last-vote" => CliValidatorsSortOrder::LastVote, "root" => CliValidatorsSortOrder::Root, + "skip-rate" => CliValidatorsSortOrder::SkipRate, "stake" => CliValidatorsSortOrder::Stake, "vote-account" => CliValidatorsSortOrder::VoteAccount, _ => unreachable!(), @@ -1811,9 +1813,32 @@ pub fn process_show_validators( validators_reverse_sort: bool, number_validators: bool, ) -> ProcessResult { + let progress_bar = new_spinner_progress_bar(); + progress_bar.set_message("Fetching vote accounts..."); let epoch_info = rpc_client.get_epoch_info()?; let vote_accounts = rpc_client.get_vote_accounts()?; + progress_bar.set_message("Fetching block production..."); + let skip_rate: HashMap<_, _> = rpc_client + .get_block_production() + .ok() + .map(|result| { + result + .value + .by_identity + .into_iter() + .map(|(identity, (leader_slots, blocks_produced))| { + ( + identity, + 100. * (leader_slots.saturating_sub(blocks_produced)) as f64 + / leader_slots as f64, + ) + }) + .collect() + }) + .unwrap_or_default(); + + progress_bar.set_message("Fetching version information..."); let mut node_version = HashMap::new(); let unknown_version = "unknown".to_string(); for contact_info in rpc_client.get_cluster_nodes()? { @@ -1825,6 +1850,8 @@ pub fn process_show_validators( ); } + progress_bar.finish_and_clear(); + let total_active_stake = vote_accounts .current .iter() @@ -1850,6 +1877,7 @@ pub fn process_show_validators( .get(&vote_account.node_pubkey) .unwrap_or(&unknown_version) .clone(), + skip_rate.get(&vote_account.node_pubkey).cloned(), &config.address_labels, ) }) @@ -1865,6 +1893,7 @@ pub fn process_show_validators( .get(&vote_account.node_pubkey) .unwrap_or(&unknown_version) .clone(), + skip_rate.get(&vote_account.node_pubkey).cloned(), &config.address_labels, ) })