use { crate::{ get_validator_rpc_addr, get_validator_start_time, new_spinner_progress_bar, println_name_value, }, console::style, indicatif::ProgressBar, solana_client::{ client_error, rpc_client::RpcClient, rpc_request, rpc_response::RpcContactInfo, }, solana_sdk::{ clock::{Slot, DEFAULT_TICKS_PER_SLOT, MS_PER_TICK}, commitment_config::CommitmentConfig, native_token::Sol, pubkey::Pubkey, }, std::{ io, path::{Path, PathBuf}, thread, time::Duration, }, }; pub struct Dashboard { progress_bar: ProgressBar, ledger_path: PathBuf, } impl Dashboard { pub fn new(ledger_path: &Path, log_path: Option<&Path>) -> Result { println_name_value("Ledger location:", &format!("{}", ledger_path.display())); if let Some(log_path) = log_path { println_name_value("Log:", &format!("{}", log_path.display())); } let rpc_addr = get_validator_rpc_addr(&ledger_path)?; if rpc_addr.is_none() { return Err(io::Error::new(io::ErrorKind::Other, "RPC not available")); } let progress_bar = new_spinner_progress_bar(); progress_bar.set_message("Initializing..."); Ok(Self { progress_bar, ledger_path: ledger_path.to_path_buf(), }) } pub fn run(self) -> ! { let Self { progress_bar, ledger_path, } = self; progress_bar.set_message("Connecting..."); let rpc_addr = get_validator_rpc_addr(&ledger_path).unwrap().unwrap(); let rpc_client = RpcClient::new_socket(rpc_addr); // Wait until RPC starts responding... loop { match rpc_client.get_identity() { Ok(_) => break, Err(err) => { progress_bar.set_message(&format!("{}", err)); thread::sleep(Duration::from_millis(500)); } } } drop(progress_bar); let identity = &rpc_client.get_identity().expect("get_identity"); println_name_value("Identity:", &identity.to_string()); fn get_contact_info(rpc_client: &RpcClient, identity: &Pubkey) -> Option { rpc_client .get_cluster_nodes() .ok() .unwrap_or_default() .into_iter() .find(|node| node.pubkey == identity.to_string()) } if let Some(contact_info) = get_contact_info(&rpc_client, &identity) { println_name_value( "Version:", &contact_info.version.unwrap_or_else(|| "?".to_string()), ); if let Some(gossip) = contact_info.gossip { println_name_value("Gossip Address:", &gossip.to_string()); } if let Some(tpu) = contact_info.tpu { println_name_value("TPU Address:", &tpu.to_string()); } if let Some(rpc) = contact_info.rpc { println_name_value("JSON RPC URL:", &format!("http://{}", rpc.to_string())); } } let progress_bar = new_spinner_progress_bar(); fn get_validator_stats( rpc_client: &RpcClient, identity: &Pubkey, ) -> client_error::Result<(Slot, Slot, Slot, u64, Sol, String)> { let processed_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::processed())?; let confirmed_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::confirmed())?; let finalized_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::finalized())?; let transaction_count = rpc_client.get_transaction_count_with_commitment(CommitmentConfig::processed())?; let identity_balance = rpc_client .get_balance_with_commitment(identity, CommitmentConfig::confirmed())? .value; let health = match rpc_client.get_health() { Ok(()) => "ok".to_string(), Err(err) => { if let client_error::ClientErrorKind::RpcError( rpc_request::RpcError::RpcResponseError { code: _, message: _, data: rpc_request::RpcResponseErrorData::NodeUnhealthy { num_slots_behind: Some(num_slots_behind), }, }, ) = &err.kind { format!("{} slots behind", num_slots_behind) } else { "unhealthy".to_string() } } }; Ok(( processed_slot, confirmed_slot, finalized_slot, transaction_count, Sol(identity_balance), health, )) } let mut start_time = get_validator_start_time(&ledger_path).ok(); loop { let snapshot_slot = rpc_client.get_snapshot_slot().ok(); for _i in 0..10 { match get_validator_stats(&rpc_client, &identity) { Ok(( processed_slot, confirmed_slot, finalized_slot, transaction_count, identity_balance, health, )) => { let uptime = match start_time { Some(start_time) => { let uptime = chrono::Duration::from_std(start_time.elapsed().unwrap()) .unwrap(); format!( "{:02}:{:02}:{:02} ", uptime.num_hours(), uptime.num_minutes() % 60, uptime.num_seconds() % 60 ) } None => " ".to_string(), }; progress_bar.set_message(&format!( "{}{}| \ Processed Slot: {} | Confirmed Slot: {} | Finalized Slot: {} | \ Snapshot Slot: {} | \ Transactions: {} | {}", uptime, if health == "ok" { "".to_string() } else { format!("| {} ", style(health).bold().red()) }, processed_slot, confirmed_slot, finalized_slot, snapshot_slot .map(|s| s.to_string()) .unwrap_or_else(|| "-".to_string()), transaction_count, identity_balance )); } Err(err) => { start_time = get_validator_start_time(&ledger_path).ok(); progress_bar.set_message(&format!("{}", err)); } } thread::sleep(Duration::from_millis( MS_PER_TICK * DEFAULT_TICKS_PER_SLOT / 2, )); } } } }