diff --git a/Cargo.lock b/Cargo.lock index 1eb45302bf..829d539ebc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5822,6 +5822,7 @@ dependencies = [ "num_cpus", "rand 0.7.3", "serde", + "serde_json", "signal-hook", "solana-clap-utils", "solana-cli-config", diff --git a/core/src/test_validator.rs b/core/src/test_validator.rs index 89a1221141..a2da266b37 100644 --- a/core/src/test_validator.rs +++ b/core/src/test_validator.rs @@ -1,7 +1,11 @@ use { crate::validator::{Validator, ValidatorConfig, ValidatorStartProgress}, solana_client::rpc_client::RpcClient, - solana_gossip::{cluster_info::Node, gossip_service::discover_cluster, socketaddr}, + solana_gossip::{ + cluster_info::{ClusterInfo, Node}, + gossip_service::discover_cluster, + socketaddr, + }, solana_ledger::{blockstore::create_new_ledger, create_new_tmp_ledger}, solana_net_utils::PortRange, solana_rpc::rpc::JsonRpcConfig, @@ -628,6 +632,10 @@ impl TestValidator { validator.join(); } } + + pub fn cluster_info(&self) -> Arc { + self.validator.as_ref().unwrap().cluster_info.clone() + } } impl Drop for TestValidator { diff --git a/core/src/validator.rs b/core/src/validator.rs index 60874f1a52..8127d54460 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -274,6 +274,7 @@ pub struct Validator { tvu: Tvu, ip_echo_server: Option, accountsdb_plugin_service: Option, + pub cluster_info: Arc, } // in the distant future, get rid of ::new()/exit() and use Result properly... @@ -863,6 +864,7 @@ impl Validator { ip_echo_server, validator_exit: config.validator_exit.clone(), accountsdb_plugin_service, + cluster_info, } } @@ -902,6 +904,8 @@ impl Validator { } pub fn join(self) { + drop(self.cluster_info); + self.poh_service.join().expect("poh_service"); drop(self.poh_recorder); diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 51873b31e2..a88c2484e0 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -28,6 +28,7 @@ log = "0.4.11" num_cpus = "1.13.0" rand = "0.7.0" serde = "1.0.112" +serde_json = "1.0.68" solana-clap-utils = { path = "../clap-utils", version = "=1.8.12" } solana-cli-config = { path = "../cli-config", version = "=1.8.12" } solana-client = { path = "../client", version = "=1.8.12" } diff --git a/validator/src/admin_rpc_service.rs b/validator/src/admin_rpc_service.rs index 26abe088d2..49fd9db5a1 100644 --- a/validator/src/admin_rpc_service.rs +++ b/validator/src/admin_rpc_service.rs @@ -5,12 +5,15 @@ use { jsonrpc_ipc_server::{RequestContext, ServerBuilder}, jsonrpc_server_utils::tokio, log::*, + serde::{Deserialize, Serialize}, solana_core::validator::ValidatorStartProgress, + solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo}, solana_sdk::{ exit::Exit, signature::{read_keypair_file, Keypair, Signer}, }, std::{ + fmt::{self, Display}, net::SocketAddr, path::{Path, PathBuf}, sync::{Arc, RwLock}, @@ -26,9 +29,80 @@ pub struct AdminRpcRequestMetadata { pub start_progress: Arc>, pub validator_exit: Arc>, pub authorized_voter_keypairs: Arc>>>, + pub cluster_info: Arc>>>, } impl Metadata for AdminRpcRequestMetadata {} +#[derive(Debug, Deserialize, Serialize)] +pub struct AdminRpcContactInfo { + pub id: String, + pub gossip: SocketAddr, + pub tvu: SocketAddr, + pub tvu_forwards: SocketAddr, + pub repair: SocketAddr, + pub tpu: SocketAddr, + pub tpu_forwards: SocketAddr, + pub tpu_vote: SocketAddr, + pub rpc: SocketAddr, + pub rpc_pubsub: SocketAddr, + pub serve_repair: SocketAddr, + pub last_updated_timestamp: u64, + pub shred_version: u16, +} + +impl From for AdminRpcContactInfo { + fn from(contact_info: ContactInfo) -> Self { + let ContactInfo { + id, + gossip, + tvu, + tvu_forwards, + repair, + tpu, + tpu_forwards, + tpu_vote, + rpc, + rpc_pubsub, + serve_repair, + wallclock, + shred_version, + } = contact_info; + Self { + id: id.to_string(), + last_updated_timestamp: wallclock, + gossip, + tvu, + tvu_forwards, + repair, + tpu, + tpu_forwards, + tpu_vote, + rpc, + rpc_pubsub, + serve_repair, + shred_version, + } + } +} + +impl Display for AdminRpcContactInfo { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "Identity: {}", self.id)?; + writeln!(f, "Gossip: {}", self.gossip)?; + writeln!(f, "TVU: {}", self.tvu)?; + writeln!(f, "TVU Forwards: {}", self.tvu_forwards)?; + writeln!(f, "Repair: {}", self.repair)?; + writeln!(f, "TPU: {}", self.tpu)?; + writeln!(f, "TPU Forwards: {}", self.tpu_forwards)?; + writeln!(f, "TPU Votes: {}", self.tpu_vote)?; + writeln!(f, "RPC: {}", self.rpc)?; + writeln!(f, "RPC Pubsub: {}", self.rpc_pubsub)?; + writeln!(f, "Serve Repair: {}", self.serve_repair)?; + writeln!(f, "Last Updated Timestamp: {}", self.last_updated_timestamp)?; + writeln!(f, "Shred Version: {}", self.shred_version) + } +} + #[rpc] pub trait AdminRpc { type Metadata; @@ -53,6 +127,9 @@ pub trait AdminRpc { #[rpc(meta, name = "removeAllAuthorizedVoters")] fn remove_all_authorized_voters(&self, meta: Self::Metadata) -> Result<()>; + + #[rpc(meta, name = "contactInfo")] + fn contact_info(&self, meta: Self::Metadata) -> Result; } pub struct AdminRpcImpl; @@ -128,6 +205,16 @@ impl AdminRpc for AdminRpcImpl { meta.authorized_voter_keypairs.write().unwrap().clear(); Ok(()) } + + fn contact_info(&self, meta: Self::Metadata) -> Result { + if let Some(cluster_info) = meta.cluster_info.read().unwrap().as_ref() { + Ok(cluster_info.my_contact_info().into()) + } else { + Err(jsonrpc_core::error::Error::invalid_params( + "Retry once validator start up is complete", + )) + } + } } // Start the Admin RPC interface diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index 9ee305dea5..49a6e39447 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -33,7 +33,7 @@ use { net::{IpAddr, Ipv4Addr, SocketAddr}, path::{Path, PathBuf}, process::exit, - sync::mpsc::channel, + sync::{mpsc::channel, Arc, RwLock}, time::{Duration, SystemTime, UNIX_EPOCH}, }, }; @@ -508,6 +508,7 @@ fn main() { genesis.max_ledger_shreds = value_of(&matches, "limit_ledger_size"); genesis.max_genesis_archive_unpacked_size = Some(u64::MAX); + let admin_service_cluster_info = Arc::new(RwLock::new(None)); admin_rpc_service::run( &ledger_path, admin_rpc_service::AdminRpcRequestMetadata { @@ -519,6 +520,7 @@ fn main() { start_time: std::time::SystemTime::now(), validator_exit: genesis.validator_exit.clone(), authorized_voter_keypairs: genesis.authorized_voter_keypairs.clone(), + cluster_info: admin_service_cluster_info.clone(), }, ); let dashboard = if output == Output::Dashboard { @@ -591,6 +593,7 @@ fn main() { match genesis.start_with_mint_address(mint_address, socket_addr_space) { Ok(test_validator) => { + *admin_service_cluster_info.write().unwrap() = Some(test_validator.cluster_info()); if let Some(dashboard) = dashboard { dashboard.run(Duration::from_millis(250)); } diff --git a/validator/src/main.rs b/validator/src/main.rs index 7665135594..16ea449183 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -2054,6 +2054,18 @@ pub fn main() { currently running validator instance") ) ) + .subcommand( + SubCommand::with_name("contact-info") + .about("Display the validator's contact info") + .arg( + Arg::with_name("output") + .long("output") + .takes_value(true) + .value_name("MODE") + .possible_values(&["json", "json-compact"]) + .help("Output display mode") + ) + ) .subcommand( SubCommand::with_name("init") .about("Initialize the ledger directory then exit") @@ -2167,6 +2179,26 @@ pub fn main() { _ => unreachable!(), } } + ("contact-info", Some(subcommand_matches)) => { + let output_mode = subcommand_matches.value_of("output"); + let admin_client = admin_rpc_service::connect(&ledger_path); + let contact_info = admin_rpc_service::runtime() + .block_on(async move { admin_client.await?.contact_info().await }) + .unwrap_or_else(|err| { + eprintln!("Contact info query failed: {}", err); + exit(1); + }); + if let Some(mode) = output_mode { + match mode { + "json" => println!("{}", serde_json::to_string_pretty(&contact_info).unwrap()), + "json-compact" => print!("{}", serde_json::to_string(&contact_info).unwrap()), + _ => unreachable!(), + } + } else { + print!("{}", contact_info); + } + return; + } ("init", _) => Operation::Initialize, ("exit", Some(subcommand_matches)) => { let min_idle_time = value_t_or_exit!(subcommand_matches, "min_idle_time", usize); @@ -2685,6 +2717,7 @@ pub fn main() { let _ledger_write_guard = lock_ledger(&ledger_path, &mut ledger_lock); let start_progress = Arc::new(RwLock::new(ValidatorStartProgress::default())); + let admin_service_cluster_info = Arc::new(RwLock::new(None)); admin_rpc_service::run( &ledger_path, admin_rpc_service::AdminRpcRequestMetadata { @@ -2693,6 +2726,7 @@ pub fn main() { validator_exit: validator_config.validator_exit.clone(), start_progress: start_progress.clone(), authorized_voter_keypairs: authorized_voter_keypairs.clone(), + cluster_info: admin_service_cluster_info.clone(), }, ); @@ -2832,6 +2866,7 @@ pub fn main() { start_progress, socket_addr_space, ); + *admin_service_cluster_info.write().unwrap() = Some(validator.cluster_info.clone()); if let Some(filename) = init_complete_file { File::create(filename).unwrap_or_else(|_| {