diff --git a/cli/src/cli_output.rs b/cli/src/cli_output.rs index 242dacb8c5..fa7a4cb767 100644 --- a/cli/src/cli_output.rs +++ b/cli/src/cli_output.rs @@ -255,6 +255,13 @@ pub struct CliValidators { pub total_deliquent_stake: u64, pub current_validators: Vec, pub delinquent_validators: Vec, + pub stake_by_version: BTreeMap< + String, /*version*/ + ( + usize, /*num validators*/ + u64, /*total_active_stake*/ + ), + >, #[serde(skip_serializing)] pub use_lamports_unit: bool, } @@ -278,7 +285,7 @@ impl fmt::Display for CliValidators { writeln!( f, - "{} {:<44} {:<44} {:>9}% {:>8} {:>10} {:>10} {}", + "{} {:<44} {:<44} {:>3}% {:>8} {:>10} {:>10} {:>17} {}", if delinquent { WARNING.to_string() } else { @@ -290,6 +297,7 @@ impl fmt::Display for CliValidators { non_zero_or_dash(validator.last_vote), non_zero_or_dash(validator.root_slot), validator.credits, + validator.version, if validator.activated_stake > 0 { format!( "{} ({:.2}%)", @@ -330,18 +338,32 @@ impl fmt::Display for CliValidators { ), )?; } + + writeln!(f)?; + writeln!(f, "{}", style("Active Stake By Version:").bold())?; + for (version, (num_validators, total_active_stake)) in self.stake_by_version.iter() { + writeln!( + f, + "{} x {} = {:0.2}%", + version, + num_validators, + 100. * *total_active_stake as f64 / self.total_active_stake as f64 + )?; + } + writeln!(f)?; writeln!( f, "{}", style(format!( - " {:<44} {:<44} {} {} {} {:>10} {}", + " {:<44} {:<38} {} {} {} {:>10} {:>17} {}", "Identity Pubkey", "Vote Account Pubkey", "Commission", "Last Vote", "Root Block", "Credits", + "Version", "Active Stake", )) .bold() @@ -378,10 +400,11 @@ pub struct CliValidator { pub root_slot: u64, pub credits: u64, pub activated_stake: u64, + pub version: String, } impl CliValidator { - pub fn new(vote_account: &RpcVoteAccountInfo, current_epoch: Epoch) -> Self { + pub fn new(vote_account: &RpcVoteAccountInfo, current_epoch: Epoch, version: String) -> Self { Self { identity_pubkey: vote_account.node_pubkey.to_string(), vote_account_pubkey: vote_account.vote_pubkey.to_string(), @@ -400,6 +423,7 @@ impl CliValidator { }) .unwrap_or(0), activated_stake: vote_account.activated_stake, + version, } } } diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 2659c42bba..e9669d22aa 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -33,7 +33,7 @@ use solana_sdk::{ transaction::Transaction, }; use std::{ - collections::{HashMap, VecDeque}, + collections::{BTreeMap, HashMap, VecDeque}, net::SocketAddr, sync::{ atomic::{AtomicBool, Ordering}, @@ -1094,10 +1094,10 @@ pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult { } let s: Vec<_> = cluster_nodes - .iter() + .into_iter() .map(|node| { format!( - "{:15} | {:44} | {:6} | {:5} | {:5}", + "{:15} | {:44} | {:6} | {:5} | {:5} | {}", node.gossip .map(|addr| addr.ip().to_string()) .unwrap_or_else(|| "none".to_string()), @@ -1105,15 +1105,16 @@ pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult { format_port(node.gossip), format_port(node.tpu), format_port(node.rpc), + node.version.unwrap_or_else(|| "unknown".to_string()), ) }) .collect(); Ok(format!( "IP Address | Node identifier \ - | Gossip | TPU | RPC\n\ + | Gossip | TPU | RPC | Version\n\ ----------------+----------------------------------------------+\ - --------+-------+-------\n\ + --------+-------+-------+----------------\n\ {}\n\ Nodes: {}", s.join("\n"), @@ -1196,6 +1197,18 @@ pub fn process_show_validators( ) -> ProcessResult { let epoch_info = rpc_client.get_epoch_info_with_commitment(config.commitment)?; let vote_accounts = rpc_client.get_vote_accounts_with_commitment(config.commitment)?; + + let mut node_version = HashMap::new(); + let unknown_version = "unknown".to_string(); + for contact_info in rpc_client.get_cluster_nodes()? { + node_version.insert( + contact_info.pubkey, + contact_info + .version + .unwrap_or_else(|| unknown_version.clone()), + ); + } + let total_active_stake = vote_accounts .current .iter() @@ -1214,21 +1227,52 @@ pub fn process_show_validators( current.sort_by(|a, b| b.activated_stake.cmp(&a.activated_stake)); let current_validators: Vec = current .iter() - .map(|vote_account| CliValidator::new(vote_account, epoch_info.epoch)) + .map(|vote_account| { + CliValidator::new( + vote_account, + epoch_info.epoch, + node_version + .get(&vote_account.node_pubkey) + .unwrap_or(&unknown_version) + .clone(), + ) + }) .collect(); let mut delinquent = vote_accounts.delinquent; delinquent.sort_by(|a, b| b.activated_stake.cmp(&a.activated_stake)); let delinquent_validators: Vec = delinquent .iter() - .map(|vote_account| CliValidator::new(vote_account, epoch_info.epoch)) + .map(|vote_account| { + CliValidator::new( + vote_account, + epoch_info.epoch, + node_version + .get(&vote_account.node_pubkey) + .unwrap_or(&unknown_version) + .clone(), + ) + }) .collect(); + let mut stake_by_version: BTreeMap<_, (usize, u64)> = BTreeMap::new(); + for validator in current_validators + .iter() + .chain(delinquent_validators.iter()) + { + let mut entry = stake_by_version + .entry(validator.version.clone()) + .or_default(); + entry.0 += 1; + entry.1 += validator.activated_stake; + } + let cli_validators = CliValidators { total_active_stake, total_current_stake, total_deliquent_stake, current_validators, delinquent_validators, + stake_by_version, use_lamports_unit, }; Ok(config.output_format.formatted_string(&cli_validators))