diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index 6a28012676..d9998c8f58 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -15,8 +15,8 @@ use { solana_account_decoder::parse_token::UiTokenAccount, solana_clap_utils::keypair::SignOnly, solana_client::rpc_response::{ - RpcAccountBalance, RpcInflationGovernor, RpcInflationRate, RpcKeyedAccount, RpcSupply, - RpcVoteAccountInfo, + RpcAccountBalance, RpcContactInfo, RpcInflationGovernor, RpcInflationRate, RpcKeyedAccount, + RpcSupply, RpcVoteAccountInfo, }, solana_sdk::{ clock::{Epoch, Slot, UnixTimestamp}, @@ -2283,6 +2283,97 @@ impl fmt::Display for CliTransactionConfirmation { } } +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CliGossipNode { + #[serde(skip_serializing_if = "Option::is_none")] + pub ip_address: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub identity_label: Option, + pub identity_pubkey: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub gossip_port: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tpu_port: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rpc_host: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + +impl CliGossipNode { + pub fn new(info: RpcContactInfo, labels: &HashMap) -> Self { + Self { + ip_address: info.gossip.map(|addr| addr.ip().to_string()), + identity_label: labels.get(&info.pubkey).cloned(), + identity_pubkey: info.pubkey, + gossip_port: info.gossip.map(|addr| addr.port()), + tpu_port: info.tpu.map(|addr| addr.port()), + rpc_host: info.rpc.map(|addr| addr.to_string()), + version: info.version, + } + } +} + +fn unwrap_to_string_or_none(option: Option) -> String +where + T: std::string::ToString, +{ + unwrap_to_string_or_default(option, "none") +} + +fn unwrap_to_string_or_default(option: Option, default: &str) -> String +where + T: std::string::ToString, +{ + option + .as_ref() + .map(|v| v.to_string()) + .unwrap_or_else(|| default.to_string()) +} + +impl fmt::Display for CliGossipNode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{:15} | {:44} | {:6} | {:5} | {:21} | {}", + unwrap_to_string_or_none(self.ip_address.as_ref()), + self.identity_label + .as_ref() + .unwrap_or(&self.identity_pubkey), + unwrap_to_string_or_none(self.gossip_port.as_ref()), + unwrap_to_string_or_none(self.tpu_port.as_ref()), + unwrap_to_string_or_none(self.rpc_host.as_ref()), + unwrap_to_string_or_default(self.version.as_ref(), "unknown"), + ) + } +} + +impl QuietDisplay for CliGossipNode {} +impl VerboseDisplay for CliGossipNode {} + +#[derive(Serialize, Deserialize)] +pub struct CliGossipNodes(pub Vec); + +impl fmt::Display for CliGossipNodes { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "IP Address | Node identifier \ + | Gossip | TPU | RPC Address | Version\n\ + ----------------+----------------------------------------------+\ + --------+-------+-----------------------+----------------", + )?; + for node in self.0.iter() { + writeln!(f, "{}", node)?; + } + writeln!(f, "Nodes: {}", self.0.len()) + } +} + +impl QuietDisplay for CliGossipNodes {} +impl VerboseDisplay for CliGossipNodes {} + #[cfg(test)] mod tests { use super::*; diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 1c61c8fb38..9ab426fa54 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -61,7 +61,6 @@ use solana_vote_program::vote_state::VoteState; use std::{ collections::{BTreeMap, HashMap, VecDeque}, fmt, - net::SocketAddr, str::FromStr, sync::{ atomic::{AtomicBool, Ordering}, @@ -1655,40 +1654,14 @@ pub fn process_live_slots(config: &CliConfig) -> ProcessResult { pub fn process_show_gossip(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult { let cluster_nodes = rpc_client.get_cluster_nodes()?; - fn format_port(addr: Option) -> String { - addr.map(|addr| addr.port().to_string()) - .unwrap_or_else(|| "none".to_string()) - } - - let s: Vec<_> = cluster_nodes + let nodes: Vec<_> = cluster_nodes .into_iter() - .map(|node| { - format!( - "{:15} | {:44} | {:6} | {:5} | {:21} | {}", - node.gossip - .map(|addr| addr.ip().to_string()) - .unwrap_or_else(|| "none".to_string()), - format_labeled_address(&node.pubkey, &config.address_labels), - format_port(node.gossip), - format_port(node.tpu), - node.rpc - .map(|addr| addr.to_string()) - .unwrap_or_else(|| "none".to_string()), - node.version.unwrap_or_else(|| "unknown".to_string()), - ) - }) + .map(|node| CliGossipNode::new(node, &config.address_labels)) .collect(); - Ok(format!( - "IP Address | Node identifier \ - | Gossip | TPU | RPC Address | Version\n\ - ----------------+----------------------------------------------+\ - --------+-------+-----------------------+----------------\n\ - {}\n\ - Nodes: {}", - s.join("\n"), - s.len(), - )) + Ok(config + .output_format + .formatted_string(&CliGossipNodes(nodes))) } pub fn process_show_stakes(