#![allow(clippy::integer_arithmetic)] #[cfg(not(target_env = "msvc"))] use jemallocator::Jemalloc; use { clap::{crate_name, value_t, value_t_or_exit, values_t, values_t_or_exit, ArgMatches}, console::style, crossbeam_channel::unbounded, log::*, rand::{seq::SliceRandom, thread_rng}, solana_clap_utils::input_parsers::{keypair_of, keypairs_of, pubkey_of, value_of}, solana_core::{ banking_trace::DISABLED_BAKING_TRACE_DIR, ledger_cleanup_service::{DEFAULT_MAX_LEDGER_SHREDS, DEFAULT_MIN_MAX_LEDGER_SHREDS}, system_monitor_service::SystemMonitorService, tower_storage, tpu::DEFAULT_TPU_COALESCE_MS, validator::{ is_snapshot_config_valid, BlockProductionMethod, BlockVerificationMethod, Validator, ValidatorConfig, ValidatorStartProgress, }, }, solana_gossip::{cluster_info::Node, legacy_contact_info::LegacyContactInfo as ContactInfo}, solana_ledger::blockstore_options::{ BlockstoreCompressionType, BlockstoreRecoveryMode, LedgerColumnOptions, ShredStorageType, }, solana_perf::recycler::enable_recycler_warming, solana_poh::poh_service, solana_rpc::{ rpc::{JsonRpcConfig, RpcBigtableConfig}, rpc_pubsub_service::PubSubConfig, }, solana_rpc_client::rpc_client::RpcClient, solana_rpc_client_api::config::RpcLeaderScheduleConfig, solana_runtime::{ accounts_db::{ AccountShrinkThreshold, AccountsDb, AccountsDbConfig, CreateAncientStorage, FillerAccountsConfig, }, accounts_index::{ AccountIndex, AccountSecondaryIndexes, AccountSecondaryIndexesIncludeExclude, AccountsIndexConfig, IndexLimitMb, }, runtime_config::RuntimeConfig, snapshot_config::{SnapshotConfig, SnapshotUsage}, snapshot_utils::{ self, create_all_accounts_run_and_snapshot_dirs, ArchiveFormat, SnapshotVersion, }, }, solana_sdk::{ clock::{Slot, DEFAULT_S_PER_SLOT}, commitment_config::CommitmentConfig, hash::Hash, pubkey::Pubkey, signature::{read_keypair, Keypair, Signer}, }, solana_send_transaction_service::send_transaction_service, solana_streamer::socket::SocketAddrSpace, solana_tpu_client::tpu_client::DEFAULT_TPU_ENABLE_UDP, solana_validator::{ admin_rpc_service, admin_rpc_service::{load_staked_nodes_overrides, StakedNodesOverrides}, bootstrap, cli::{app, warn_for_deprecated_arguments, DefaultArgs}, dashboard::Dashboard, ledger_lockfile, lock_ledger, new_spinner_progress_bar, println_name_value, redirect_stderr_to_file, }, std::{ collections::{HashSet, VecDeque}, env, fs::{self, File}, net::{IpAddr, Ipv4Addr, SocketAddr}, num::NonZeroUsize, path::{Path, PathBuf}, process::exit, str::FromStr, sync::{Arc, RwLock}, time::{Duration, SystemTime}, }, }; #[cfg(not(target_env = "msvc"))] #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; #[derive(Debug, PartialEq, Eq)] enum Operation { Initialize, Run, } const MILLIS_PER_SECOND: u64 = 1000; fn monitor_validator(ledger_path: &Path) { let dashboard = Dashboard::new(ledger_path, None, None).unwrap_or_else(|err| { println!( "Error: Unable to connect to validator at {}: {:?}", ledger_path.display(), err, ); exit(1); }); dashboard.run(Duration::from_secs(2)); } fn wait_for_restart_window( ledger_path: &Path, identity: Option, min_idle_time_in_minutes: usize, max_delinquency_percentage: u8, skip_new_snapshot_check: bool, ) -> Result<(), Box> { let sleep_interval = Duration::from_secs(5); let min_idle_slots = (min_idle_time_in_minutes as f64 * 60. / DEFAULT_S_PER_SLOT) as Slot; let admin_client = admin_rpc_service::connect(ledger_path); let rpc_addr = admin_rpc_service::runtime() .block_on(async move { admin_client.await?.rpc_addr().await }) .map_err(|err| format!("Unable to get validator RPC address: {err}"))?; let rpc_client = match rpc_addr { None => return Err("RPC not available".into()), Some(rpc_addr) => RpcClient::new_socket(rpc_addr), }; let my_identity = rpc_client.get_identity()?; let identity = identity.unwrap_or(my_identity); let monitoring_another_validator = identity != my_identity; println_name_value("Identity:", &identity.to_string()); println_name_value( "Minimum Idle Time:", &format!("{min_idle_slots} slots (~{min_idle_time_in_minutes} minutes)"), ); println!("Maximum permitted delinquency: {max_delinquency_percentage}%"); let mut current_epoch = None; let mut leader_schedule = VecDeque::new(); let mut restart_snapshot = None; let mut upcoming_idle_windows = vec![]; // Vec<(starting slot, idle window length in slots)> let progress_bar = new_spinner_progress_bar(); let monitor_start_time = SystemTime::now(); let mut seen_incremential_snapshot = false; loop { let snapshot_slot_info = rpc_client.get_highest_snapshot_slot().ok(); let snapshot_slot_info_has_incremential = snapshot_slot_info .as_ref() .map(|snapshot_slot_info| snapshot_slot_info.incremental.is_some()) .unwrap_or_default(); seen_incremential_snapshot |= snapshot_slot_info_has_incremential; let epoch_info = rpc_client.get_epoch_info_with_commitment(CommitmentConfig::processed())?; let healthy = rpc_client.get_health().ok().is_some(); let delinquent_stake_percentage = { let vote_accounts = rpc_client.get_vote_accounts()?; let current_stake: u64 = vote_accounts .current .iter() .map(|va| va.activated_stake) .sum(); let delinquent_stake: u64 = vote_accounts .delinquent .iter() .map(|va| va.activated_stake) .sum(); let total_stake = current_stake + delinquent_stake; delinquent_stake as f64 / total_stake as f64 }; if match current_epoch { None => true, Some(current_epoch) => current_epoch != epoch_info.epoch, } { progress_bar.set_message(format!( "Fetching leader schedule for epoch {}...", epoch_info.epoch )); let first_slot_in_epoch = epoch_info.absolute_slot - epoch_info.slot_index; leader_schedule = rpc_client .get_leader_schedule_with_config( Some(first_slot_in_epoch), RpcLeaderScheduleConfig { identity: Some(identity.to_string()), ..RpcLeaderScheduleConfig::default() }, )? .ok_or_else(|| { format!("Unable to get leader schedule from slot {first_slot_in_epoch}") })? .get(&identity.to_string()) .cloned() .unwrap_or_default() .into_iter() .map(|slot_index| first_slot_in_epoch.saturating_add(slot_index as u64)) .filter(|slot| *slot > epoch_info.absolute_slot) .collect::>(); upcoming_idle_windows.clear(); { let mut leader_schedule = leader_schedule.clone(); let mut max_idle_window = 0; let mut idle_window_start_slot = epoch_info.absolute_slot; while let Some(next_leader_slot) = leader_schedule.pop_front() { let idle_window = next_leader_slot - idle_window_start_slot; max_idle_window = max_idle_window.max(idle_window); if idle_window > min_idle_slots { upcoming_idle_windows.push((idle_window_start_slot, idle_window)); } idle_window_start_slot = next_leader_slot; } if !leader_schedule.is_empty() && upcoming_idle_windows.is_empty() { return Err(format!( "Validator has no idle window of at least {} slots. Largest idle window for epoch {} is {} slots", min_idle_slots, epoch_info.epoch, max_idle_window ) .into()); } } current_epoch = Some(epoch_info.epoch); } let status = { if !healthy { style("Node is unhealthy").red().to_string() } else { // Wait until a hole in the leader schedule before restarting the node let in_leader_schedule_hole = if epoch_info.slot_index + min_idle_slots > epoch_info.slots_in_epoch { Err("Current epoch is almost complete".to_string()) } else { while leader_schedule .get(0) .map(|slot| *slot < epoch_info.absolute_slot) .unwrap_or(false) { leader_schedule.pop_front(); } while upcoming_idle_windows .first() .map(|(slot, _)| *slot < epoch_info.absolute_slot) .unwrap_or(false) { upcoming_idle_windows.pop(); } match leader_schedule.get(0) { None => { Ok(()) // Validator has no leader slots } Some(next_leader_slot) => { let idle_slots = next_leader_slot.saturating_sub(epoch_info.absolute_slot); if idle_slots >= min_idle_slots { Ok(()) } else { Err(match upcoming_idle_windows.first() { Some((starting_slot, length_in_slots)) => { format!( "Next idle window in {} slots, for {} slots", starting_slot.saturating_sub(epoch_info.absolute_slot), length_in_slots ) } None => format!( "Validator will be leader soon. Next leader slot is {next_leader_slot}" ), }) } } } }; match in_leader_schedule_hole { Ok(_) => { if skip_new_snapshot_check { break; // Restart! } let snapshot_slot = snapshot_slot_info.map(|snapshot_slot_info| { snapshot_slot_info .incremental .unwrap_or(snapshot_slot_info.full) }); if restart_snapshot.is_none() { restart_snapshot = snapshot_slot; } if restart_snapshot == snapshot_slot && !monitoring_another_validator { "Waiting for a new snapshot".to_string() } else if delinquent_stake_percentage >= (max_delinquency_percentage as f64 / 100.) { style("Delinquency too high").red().to_string() } else if seen_incremential_snapshot && !snapshot_slot_info_has_incremential { // Restarts using just a full snapshot will put the node significantly // further behind than if an incremental snapshot is also used, as full // snapshots are larger and take much longer to create. // // Therefore if the node just created a new full snapshot, wait a // little longer until it creates the first incremental snapshot for // the full snapshot. "Waiting for incremental snapshot".to_string() } else { break; // Restart! } } Err(why) => style(why).yellow().to_string(), } } }; progress_bar.set_message(format!( "{} | Processed Slot: {} {} | {:.2}% delinquent stake | {}", { let elapsed = chrono::Duration::from_std(monitor_start_time.elapsed().unwrap()).unwrap(); format!( "{:02}:{:02}:{:02}", elapsed.num_hours(), elapsed.num_minutes() % 60, elapsed.num_seconds() % 60 ) }, epoch_info.absolute_slot, if monitoring_another_validator { "".to_string() } else { format!( "| Full Snapshot Slot: {} | Incremental Snapshot Slot: {}", snapshot_slot_info .as_ref() .map(|snapshot_slot_info| snapshot_slot_info.full.to_string()) .unwrap_or_else(|| '-'.to_string()), snapshot_slot_info .as_ref() .and_then(|snapshot_slot_info| snapshot_slot_info .incremental .map(|incremental| incremental.to_string())) .unwrap_or_else(|| '-'.to_string()), ) }, delinquent_stake_percentage * 100., status )); std::thread::sleep(sleep_interval); } drop(progress_bar); println!("{}", style("Ready to restart").green()); Ok(()) } fn set_repair_whitelist( ledger_path: &Path, whitelist: Vec, ) -> Result<(), Box> { let admin_client = admin_rpc_service::connect(ledger_path); admin_rpc_service::runtime() .block_on(async move { admin_client.await?.set_repair_whitelist(whitelist).await }) .map_err(|err| { std::io::Error::new( std::io::ErrorKind::Other, format!("setRepairWhitelist request failed: {err}"), ) })?; Ok(()) } /// Returns the default fifo shred storage size (include both data and coding /// shreds) based on the validator config. fn default_fifo_shred_storage_size(vc: &ValidatorConfig) -> Option { // The max shred size is around 1228 bytes. // Here we reserve a little bit more than that to give extra storage for FIFO // to prevent it from purging data that have not yet being marked as obsoleted // by LedgerCleanupService. const RESERVED_BYTES_PER_SHRED: u64 = 1500; vc.max_ledger_shreds.map(|max_ledger_shreds| { // x2 as we have data shred and coding shred. max_ledger_shreds * RESERVED_BYTES_PER_SHRED * 2 }) } // This function is duplicated in ledger-tool/src/main.rs... fn hardforks_of(matches: &ArgMatches<'_>, name: &str) -> Option> { if matches.is_present(name) { Some(values_t_or_exit!(matches, name, Slot)) } else { None } } fn validators_set( identity_pubkey: &Pubkey, matches: &ArgMatches<'_>, matches_name: &str, arg_name: &str, ) -> Option> { if matches.is_present(matches_name) { let validators_set: HashSet<_> = values_t_or_exit!(matches, matches_name, Pubkey) .into_iter() .collect(); if validators_set.contains(identity_pubkey) { eprintln!("The validator's identity pubkey cannot be a {arg_name}: {identity_pubkey}"); exit(1); } Some(validators_set) } else { None } } fn get_cluster_shred_version(entrypoints: &[SocketAddr]) -> Option { let entrypoints = { let mut index: Vec<_> = (0..entrypoints.len()).collect(); index.shuffle(&mut rand::thread_rng()); index.into_iter().map(|i| &entrypoints[i]) }; for entrypoint in entrypoints { match solana_net_utils::get_cluster_shred_version(entrypoint) { Err(err) => eprintln!("get_cluster_shred_version failed: {entrypoint}, {err}"), Ok(0) => eprintln!("zero shred-version from entrypoint: {entrypoint}"), Ok(shred_version) => { info!( "obtained shred-version {} from {}", shred_version, entrypoint ); return Some(shred_version); } } } None } fn configure_banking_trace_dir_byte_limit( validator_config: &mut ValidatorConfig, matches: &ArgMatches, ) { validator_config.banking_trace_dir_byte_limit = if matches.occurrences_of("banking_trace_dir_byte_limit") == 0 { // disable with no explicit flag; then, this effectively becomes `opt-in` even if we're // specifying a default value in clap configuration. DISABLED_BAKING_TRACE_DIR } else { // BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT or user-supplied override value value_t_or_exit!(matches, "banking_trace_dir_byte_limit", u64) }; } pub fn main() { let default_args = DefaultArgs::new(); let solana_version = solana_version::version!(); let cli_app = app(solana_version, &default_args); let matches = cli_app.get_matches(); warn_for_deprecated_arguments(&matches); let socket_addr_space = SocketAddrSpace::new(matches.is_present("allow_private_addr")); let ledger_path = PathBuf::from(matches.value_of("ledger_path").unwrap()); let operation = match matches.subcommand() { ("", _) | ("run", _) => Operation::Run, ("authorized-voter", Some(authorized_voter_subcommand_matches)) => { match authorized_voter_subcommand_matches.subcommand() { ("add", Some(subcommand_matches)) => { if let Some(authorized_voter_keypair) = value_t!(subcommand_matches, "authorized_voter_keypair", String).ok() { let authorized_voter_keypair = fs::canonicalize(&authorized_voter_keypair) .unwrap_or_else(|err| { println!( "Unable to access path: {authorized_voter_keypair}: {err:?}" ); exit(1); }); println!( "Adding authorized voter path: {}", authorized_voter_keypair.display() ); let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async move { admin_client .await? .add_authorized_voter( authorized_voter_keypair.display().to_string(), ) .await }) .unwrap_or_else(|err| { println!("addAuthorizedVoter request failed: {err}"); exit(1); }); } else { let mut stdin = std::io::stdin(); let authorized_voter_keypair = read_keypair(&mut stdin).unwrap_or_else(|err| { println!("Unable to read JSON keypair from stdin: {err:?}"); exit(1); }); println!( "Adding authorized voter: {}", authorized_voter_keypair.pubkey() ); let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async move { admin_client .await? .add_authorized_voter_from_bytes(Vec::from( authorized_voter_keypair.to_bytes(), )) .await }) .unwrap_or_else(|err| { println!("addAuthorizedVoterFromBytes request failed: {err}"); exit(1); }); } return; } ("remove-all", _) => { let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async move { admin_client.await?.remove_all_authorized_voters().await }) .unwrap_or_else(|err| { println!("removeAllAuthorizedVoters request failed: {err}"); exit(1); }); println!("All authorized voters removed"); return; } _ => unreachable!(), } } ("plugin", Some(plugin_subcommand_matches)) => { match plugin_subcommand_matches.subcommand() { ("list", _) => { let admin_client = admin_rpc_service::connect(&ledger_path); let plugins = admin_rpc_service::runtime() .block_on(async move { admin_client.await?.list_plugins().await }) .unwrap_or_else(|err| { println!("Failed to list plugins: {err}"); exit(1); }); if !plugins.is_empty() { println!("Currently the following plugins are loaded:"); for (plugin, i) in plugins.into_iter().zip(1..) { println!(" {i}) {plugin}"); } } else { println!("There are currently no plugins loaded"); } return; } ("unload", Some(subcommand_matches)) => { if let Some(name) = value_t!(subcommand_matches, "name", String).ok() { let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async { admin_client.await?.unload_plugin(name.clone()).await }) .unwrap_or_else(|err| { println!("Failed to unload plugin {name}: {err:?}"); exit(1); }); println!("Successfully unloaded plugin: {name}"); } return; } ("load", Some(subcommand_matches)) => { if let Some(config) = value_t!(subcommand_matches, "config", String).ok() { let admin_client = admin_rpc_service::connect(&ledger_path); let name = admin_rpc_service::runtime() .block_on(async { admin_client.await?.load_plugin(config.clone()).await }) .unwrap_or_else(|err| { println!("Failed to load plugin {config}: {err:?}"); exit(1); }); println!("Successfully loaded plugin: {name}"); } return; } ("reload", Some(subcommand_matches)) => { if let Some(name) = value_t!(subcommand_matches, "name", String).ok() { if let Some(config) = value_t!(subcommand_matches, "config", String).ok() { let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async { admin_client .await? .reload_plugin(name.clone(), config.clone()) .await }) .unwrap_or_else(|err| { println!("Failed to reload plugin {name}: {err:?}"); exit(1); }); println!("Successfully reloaded plugin: {name}"); } } return; } _ => 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); let force = subcommand_matches.is_present("force"); let monitor = subcommand_matches.is_present("monitor"); let skip_new_snapshot_check = subcommand_matches.is_present("skip_new_snapshot_check"); let max_delinquent_stake = value_t_or_exit!(subcommand_matches, "max_delinquent_stake", u8); if !force { wait_for_restart_window( &ledger_path, None, min_idle_time, max_delinquent_stake, skip_new_snapshot_check, ) .unwrap_or_else(|err| { println!("{err}"); exit(1); }); } let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async move { admin_client.await?.exit().await }) .unwrap_or_else(|err| { println!("exit request failed: {err}"); exit(1); }); println!("Exit request sent"); if monitor { monitor_validator(&ledger_path); } return; } ("monitor", _) => { monitor_validator(&ledger_path); return; } ("staked-nodes-overrides", Some(subcommand_matches)) => { if !subcommand_matches.is_present("path") { println!( "staked-nodes-overrides requires argument of location of the configuration" ); exit(1); } let path = subcommand_matches.value_of("path").unwrap(); let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async move { admin_client .await? .set_staked_nodes_overrides(path.to_string()) .await }) .unwrap_or_else(|err| { println!("setStakedNodesOverrides request failed: {err}"); exit(1); }); return; } ("set-identity", Some(subcommand_matches)) => { let require_tower = subcommand_matches.is_present("require_tower"); if let Some(identity_keypair) = value_t!(subcommand_matches, "identity", String).ok() { let identity_keypair = fs::canonicalize(&identity_keypair).unwrap_or_else(|err| { println!("Unable to access path: {identity_keypair}: {err:?}"); exit(1); }); println!( "New validator identity path: {}", identity_keypair.display() ); let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async move { admin_client .await? .set_identity(identity_keypair.display().to_string(), require_tower) .await }) .unwrap_or_else(|err| { println!("setIdentity request failed: {err}"); exit(1); }); } else { let mut stdin = std::io::stdin(); let identity_keypair = read_keypair(&mut stdin).unwrap_or_else(|err| { println!("Unable to read JSON keypair from stdin: {err:?}"); exit(1); }); println!("New validator identity: {}", identity_keypair.pubkey()); let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async move { admin_client .await? .set_identity_from_bytes( Vec::from(identity_keypair.to_bytes()), require_tower, ) .await }) .unwrap_or_else(|err| { println!("setIdentityFromBytes request failed: {err}"); exit(1); }); }; return; } ("set-log-filter", Some(subcommand_matches)) => { let filter = value_t_or_exit!(subcommand_matches, "filter", String); let admin_client = admin_rpc_service::connect(&ledger_path); admin_rpc_service::runtime() .block_on(async move { admin_client.await?.set_log_filter(filter).await }) .unwrap_or_else(|err| { println!("set log filter failed: {err}"); exit(1); }); return; } ("wait-for-restart-window", Some(subcommand_matches)) => { let min_idle_time = value_t_or_exit!(subcommand_matches, "min_idle_time", usize); let identity = pubkey_of(subcommand_matches, "identity"); let max_delinquent_stake = value_t_or_exit!(subcommand_matches, "max_delinquent_stake", u8); let skip_new_snapshot_check = subcommand_matches.is_present("skip_new_snapshot_check"); wait_for_restart_window( &ledger_path, identity, min_idle_time, max_delinquent_stake, skip_new_snapshot_check, ) .unwrap_or_else(|err| { println!("{err}"); exit(1); }); return; } ("repair-whitelist", Some(repair_whitelist_subcommand_matches)) => { match repair_whitelist_subcommand_matches.subcommand() { ("get", Some(subcommand_matches)) => { let output_mode = subcommand_matches.value_of("output"); let admin_client = admin_rpc_service::connect(&ledger_path); let repair_whitelist = admin_rpc_service::runtime() .block_on(async move { admin_client.await?.repair_whitelist().await }) .unwrap_or_else(|err| { eprintln!("Repair whitelist query failed: {err}"); exit(1); }); if let Some(mode) = output_mode { match mode { "json" => println!( "{}", serde_json::to_string_pretty(&repair_whitelist).unwrap() ), "json-compact" => { print!("{}", serde_json::to_string(&repair_whitelist).unwrap()) } _ => unreachable!(), } } else { print!("{repair_whitelist}"); } return; } ("set", Some(subcommand_matches)) => { let whitelist = if subcommand_matches.is_present("whitelist") { let validators_set: HashSet<_> = values_t_or_exit!(subcommand_matches, "whitelist", Pubkey) .into_iter() .collect(); validators_set.into_iter().collect::>() } else { return; }; set_repair_whitelist(&ledger_path, whitelist).unwrap_or_else(|err| { eprintln!("{err}"); exit(1); }); return; } ("remove-all", _) => { set_repair_whitelist(&ledger_path, Vec::default()).unwrap_or_else(|err| { eprintln!("{err}"); exit(1); }); return; } _ => unreachable!(), } } _ => unreachable!(), }; let identity_keypair = keypair_of(&matches, "identity").unwrap_or_else(|| { clap::Error::with_description( "The --identity argument is required", clap::ErrorKind::ArgumentNotFound, ) .exit(); }); let logfile = { let logfile = matches .value_of("logfile") .map(|s| s.into()) .unwrap_or_else(|| format!("solana-validator-{}.log", identity_keypair.pubkey())); if logfile == "-" { None } else { println!("log file: {logfile}"); Some(logfile) } }; let use_progress_bar = logfile.is_none(); let _logger_thread = redirect_stderr_to_file(logfile); info!("{} {}", crate_name!(), solana_version); info!("Starting validator with: {:#?}", std::env::args_os()); let cuda = matches.is_present("cuda"); if cuda { solana_perf::perf_libs::init_cuda(); enable_recycler_warming(); } solana_core::validator::report_target_features(); let authorized_voter_keypairs = keypairs_of(&matches, "authorized_voter_keypairs") .map(|keypairs| keypairs.into_iter().map(Arc::new).collect()) .unwrap_or_else(|| { vec![Arc::new( keypair_of(&matches, "identity").expect("identity"), )] }); let authorized_voter_keypairs = Arc::new(RwLock::new(authorized_voter_keypairs)); let staked_nodes_overrides_path = matches .value_of("staked_nodes_overrides") .map(str::to_string); let staked_nodes_overrides = Arc::new(RwLock::new( match staked_nodes_overrides_path { None => StakedNodesOverrides::default(), Some(p) => load_staked_nodes_overrides(&p).unwrap_or_else(|err| { error!("Failed to load stake-nodes-overrides from {}: {}", &p, err); clap::Error::with_description( "Failed to load configuration of stake-nodes-overrides argument", clap::ErrorKind::InvalidValue, ) .exit() }), } .staked_map_id, )); let init_complete_file = matches.value_of("init_complete_file"); let rpc_bootstrap_config = bootstrap::RpcBootstrapConfig { no_genesis_fetch: matches.is_present("no_genesis_fetch"), no_snapshot_fetch: matches.is_present("no_snapshot_fetch"), check_vote_account: matches .value_of("check_vote_account") .map(|url| url.to_string()), only_known_rpc: matches.is_present("only_known_rpc"), max_genesis_archive_unpacked_size: value_t_or_exit!( matches, "max_genesis_archive_unpacked_size", u64 ), incremental_snapshot_fetch: !matches.is_present("no_incremental_snapshots"), }; let private_rpc = matches.is_present("private_rpc"); let do_port_check = !matches.is_present("no_port_check"); let tpu_coalesce_ms = value_t!(matches, "tpu_coalesce_ms", u64).unwrap_or(DEFAULT_TPU_COALESCE_MS); let wal_recovery_mode = matches .value_of("wal_recovery_mode") .map(BlockstoreRecoveryMode::from); // Canonicalize ledger path to avoid issues with symlink creation let _ = fs::create_dir_all(&ledger_path); let ledger_path = fs::canonicalize(&ledger_path).unwrap_or_else(|err| { eprintln!("Unable to access ledger path: {err:?}"); exit(1); }); let debug_keys: Option>> = if matches.is_present("debug_key") { Some(Arc::new( values_t_or_exit!(matches, "debug_key", Pubkey) .into_iter() .collect(), )) } else { None }; let known_validators = validators_set( &identity_keypair.pubkey(), &matches, "known_validators", "--known-validator", ); let repair_validators = validators_set( &identity_keypair.pubkey(), &matches, "repair_validators", "--repair-validator", ); let repair_whitelist = validators_set( &identity_keypair.pubkey(), &matches, "repair_whitelist", "--repair-whitelist", ); let repair_whitelist = Arc::new(RwLock::new(repair_whitelist.unwrap_or_default())); let gossip_validators = validators_set( &identity_keypair.pubkey(), &matches, "gossip_validators", "--gossip-validator", ); let bind_address = solana_net_utils::parse_host(matches.value_of("bind_address").unwrap()) .expect("invalid bind_address"); let rpc_bind_address = if matches.is_present("rpc_bind_address") { solana_net_utils::parse_host(matches.value_of("rpc_bind_address").unwrap()) .expect("invalid rpc_bind_address") } else if private_rpc { solana_net_utils::parse_host("127.0.0.1").unwrap() } else { bind_address }; let contact_debug_interval = value_t_or_exit!(matches, "contact_debug_interval", u64); let account_indexes = process_account_indexes(&matches); let restricted_repair_only_mode = matches.is_present("restricted_repair_only_mode"); let accounts_shrink_optimize_total_space = value_t_or_exit!(matches, "accounts_shrink_optimize_total_space", bool); let tpu_use_quic = !matches.is_present("tpu_disable_quic"); let tpu_enable_udp = if matches.is_present("tpu_enable_udp") { true } else { DEFAULT_TPU_ENABLE_UDP }; let tpu_connection_pool_size = value_t_or_exit!(matches, "tpu_connection_pool_size", usize); let shrink_ratio = value_t_or_exit!(matches, "accounts_shrink_ratio", f64); if !(0.0..=1.0).contains(&shrink_ratio) { eprintln!( "The specified account-shrink-ratio is invalid, it must be between 0. and 1.0 inclusive: {shrink_ratio}" ); exit(1); } let accounts_shrink_ratio = if accounts_shrink_optimize_total_space { AccountShrinkThreshold::TotalSpace { shrink_ratio } } else { AccountShrinkThreshold::IndividualStore { shrink_ratio } }; let entrypoint_addrs = values_t!(matches, "entrypoint", String) .unwrap_or_default() .into_iter() .map(|entrypoint| { solana_net_utils::parse_host_port(&entrypoint).unwrap_or_else(|e| { eprintln!("failed to parse entrypoint address: {e}"); exit(1); }) }) .collect::>() .into_iter() .collect::>(); for addr in &entrypoint_addrs { if !socket_addr_space.check(addr) { eprintln!("invalid entrypoint address: {addr}"); exit(1); } } // TODO: Once entrypoints are updated to return shred-version, this should // abort if it fails to obtain a shred-version, so that nodes always join // gossip with a valid shred-version. The code to adopt entrypoint shred // version can then be deleted from gossip and get_rpc_node above. let expected_shred_version = value_t!(matches, "expected_shred_version", u16) .ok() .or_else(|| get_cluster_shred_version(&entrypoint_addrs)); let tower_storage: Arc = match value_t_or_exit!(matches, "tower_storage", String).as_str() { "file" => { let tower_path = value_t!(matches, "tower", PathBuf) .ok() .unwrap_or_else(|| ledger_path.clone()); Arc::new(tower_storage::FileTowerStorage::new(tower_path)) } "etcd" => { let endpoints = values_t_or_exit!(matches, "etcd_endpoint", String); let domain_name = value_t_or_exit!(matches, "etcd_domain_name", String); let ca_certificate_file = value_t_or_exit!(matches, "etcd_cacert_file", String); let identity_certificate_file = value_t_or_exit!(matches, "etcd_cert_file", String); let identity_private_key_file = value_t_or_exit!(matches, "etcd_key_file", String); let read = |file| { fs::read(&file).unwrap_or_else(|err| { eprintln!("Unable to read {file}: {err}"); exit(1) }) }; let tls_config = tower_storage::EtcdTlsConfig { domain_name, ca_certificate: read(ca_certificate_file), identity_certificate: read(identity_certificate_file), identity_private_key: read(identity_private_key_file), }; Arc::new( tower_storage::EtcdTowerStorage::new(endpoints, Some(tls_config)) .unwrap_or_else(|err| { eprintln!("Failed to connect to etcd: {err}"); exit(1); }), ) } _ => unreachable!(), }; let mut accounts_index_config = AccountsIndexConfig { started_from_validator: true, // this is the only place this is set ..AccountsIndexConfig::default() }; if let Some(bins) = value_t!(matches, "accounts_index_bins", usize).ok() { accounts_index_config.bins = Some(bins); } accounts_index_config.index_limit_mb = if let Some(limit) = value_t!(matches, "accounts_index_memory_limit_mb", usize).ok() { IndexLimitMb::Limit(limit) } else if matches.is_present("disable_accounts_disk_index") { IndexLimitMb::InMemOnly } else { IndexLimitMb::Unspecified }; { let mut accounts_index_paths: Vec = if matches.is_present("accounts_index_path") { values_t_or_exit!(matches, "accounts_index_path", String) .into_iter() .map(PathBuf::from) .collect() } else { vec![] }; if accounts_index_paths.is_empty() { accounts_index_paths = vec![ledger_path.join("accounts_index")]; } accounts_index_config.drives = Some(accounts_index_paths); } const MB: usize = 1_024 * 1_024; accounts_index_config.scan_results_limit_bytes = value_t!(matches, "accounts_index_scan_results_limit_mb", usize) .ok() .map(|mb| mb * MB); let filler_accounts_config = FillerAccountsConfig { count: value_t_or_exit!(matches, "accounts_filler_count", usize), size: value_t_or_exit!(matches, "accounts_filler_size", usize), }; let accounts_db_config = AccountsDbConfig { index: Some(accounts_index_config), accounts_hash_cache_path: Some(ledger_path.join(AccountsDb::ACCOUNTS_HASH_CACHE_DIR)), filler_accounts_config, write_cache_limit_bytes: value_t!(matches, "accounts_db_cache_limit_mb", u64) .ok() .map(|mb| mb * MB as u64), ancient_append_vec_offset: value_t!(matches, "accounts_db_ancient_append_vecs", i64).ok(), exhaustively_verify_refcounts: matches.is_present("accounts_db_verify_refcounts"), create_ancient_storage: matches .is_present("accounts_db_create_ancient_storage_packed") .then_some(CreateAncientStorage::Pack) .unwrap_or_default(), ..AccountsDbConfig::default() }; let accounts_db_config = Some(accounts_db_config); let on_start_geyser_plugin_config_files = if matches.is_present("geyser_plugin_config") { Some( values_t_or_exit!(matches, "geyser_plugin_config", String) .into_iter() .map(PathBuf::from) .collect(), ) } else { None }; let starting_with_geyser_plugins: bool = on_start_geyser_plugin_config_files.is_some(); let rpc_bigtable_config = if matches.is_present("enable_rpc_bigtable_ledger_storage") || matches.is_present("enable_bigtable_ledger_upload") { Some(RpcBigtableConfig { enable_bigtable_ledger_upload: matches.is_present("enable_bigtable_ledger_upload"), bigtable_instance_name: value_t_or_exit!(matches, "rpc_bigtable_instance_name", String), bigtable_app_profile_id: value_t_or_exit!( matches, "rpc_bigtable_app_profile_id", String ), timeout: value_t!(matches, "rpc_bigtable_timeout", u64) .ok() .map(Duration::from_secs), }) } else { None }; let rpc_send_retry_rate_ms = value_t_or_exit!(matches, "rpc_send_transaction_retry_ms", u64); let rpc_send_batch_size = value_t_or_exit!(matches, "rpc_send_transaction_batch_size", usize); let rpc_send_batch_send_rate_ms = value_t_or_exit!(matches, "rpc_send_transaction_batch_ms", u64); if rpc_send_batch_send_rate_ms > rpc_send_retry_rate_ms { eprintln!( "The specified rpc-send-batch-ms ({rpc_send_batch_send_rate_ms}) is invalid, it must be <= rpc-send-retry-ms ({rpc_send_retry_rate_ms})" ); exit(1); } let tps = rpc_send_batch_size as u64 * MILLIS_PER_SECOND / rpc_send_batch_send_rate_ms; if tps > send_transaction_service::MAX_TRANSACTION_SENDS_PER_SECOND { eprintln!( "Either the specified rpc-send-batch-size ({}) or rpc-send-batch-ms ({}) is invalid, \ 'rpc-send-batch-size * 1000 / rpc-send-batch-ms' must be smaller than ({}) .", rpc_send_batch_size, rpc_send_batch_send_rate_ms, send_transaction_service::MAX_TRANSACTION_SENDS_PER_SECOND ); exit(1); } let full_api = matches.is_present("full_rpc_api"); let mut validator_config = ValidatorConfig { require_tower: matches.is_present("require_tower"), tower_storage, halt_at_slot: value_t!(matches, "dev_halt_at_slot", Slot).ok(), expected_genesis_hash: matches .value_of("expected_genesis_hash") .map(|s| Hash::from_str(s).unwrap()), expected_bank_hash: matches .value_of("expected_bank_hash") .map(|s| Hash::from_str(s).unwrap()), expected_shred_version, new_hard_forks: hardforks_of(&matches, "hard_forks"), rpc_config: JsonRpcConfig { enable_rpc_transaction_history: matches.is_present("enable_rpc_transaction_history"), enable_extended_tx_metadata_storage: matches.is_present("enable_cpi_and_log_storage") || matches.is_present("enable_extended_tx_metadata_storage"), rpc_bigtable_config, faucet_addr: matches.value_of("rpc_faucet_addr").map(|address| { solana_net_utils::parse_host_port(address).expect("failed to parse faucet address") }), full_api, obsolete_v1_7_api: matches.is_present("obsolete_v1_7_rpc_api"), max_multiple_accounts: Some(value_t_or_exit!( matches, "rpc_max_multiple_accounts", usize )), health_check_slot_distance: value_t_or_exit!( matches, "health_check_slot_distance", u64 ), rpc_threads: value_t_or_exit!(matches, "rpc_threads", usize), rpc_niceness_adj: value_t_or_exit!(matches, "rpc_niceness_adj", i8), account_indexes: account_indexes.clone(), rpc_scan_and_fix_roots: matches.is_present("rpc_scan_and_fix_roots"), max_request_body_size: Some(value_t_or_exit!( matches, "rpc_max_request_body_size", usize )), }, on_start_geyser_plugin_config_files, rpc_addrs: value_t!(matches, "rpc_port", u16).ok().map(|rpc_port| { ( SocketAddr::new(rpc_bind_address, rpc_port), SocketAddr::new(rpc_bind_address, rpc_port + 1), // If additional ports are added, +2 needs to be skipped to avoid a conflict with // the websocket port (which is +2) in web3.js This odd port shifting is tracked at // https://github.com/solana-labs/solana/issues/12250 ) }), pubsub_config: PubSubConfig { enable_block_subscription: matches.is_present("rpc_pubsub_enable_block_subscription"), enable_vote_subscription: matches.is_present("rpc_pubsub_enable_vote_subscription"), max_active_subscriptions: value_t_or_exit!( matches, "rpc_pubsub_max_active_subscriptions", usize ), queue_capacity_items: value_t_or_exit!( matches, "rpc_pubsub_queue_capacity_items", usize ), queue_capacity_bytes: value_t_or_exit!( matches, "rpc_pubsub_queue_capacity_bytes", usize ), worker_threads: value_t_or_exit!(matches, "rpc_pubsub_worker_threads", usize), notification_threads: if full_api { value_of(&matches, "rpc_pubsub_notification_threads") } else { Some(0) }, }, voting_disabled: matches.is_present("no_voting") || restricted_repair_only_mode, wait_for_supermajority: value_t!(matches, "wait_for_supermajority", Slot).ok(), known_validators, repair_validators, repair_whitelist, gossip_validators, wal_recovery_mode, run_verification: !(matches.is_present("skip_poh_verify") || matches.is_present("skip_startup_ledger_verification")), debug_keys, contact_debug_interval, send_transaction_service_config: send_transaction_service::Config { retry_rate_ms: rpc_send_retry_rate_ms, leader_forward_count: value_t_or_exit!( matches, "rpc_send_transaction_leader_forward_count", u64 ), default_max_retries: value_t!( matches, "rpc_send_transaction_default_max_retries", usize ) .ok(), service_max_retries: value_t_or_exit!( matches, "rpc_send_transaction_service_max_retries", usize ), batch_send_rate_ms: rpc_send_batch_send_rate_ms, batch_size: rpc_send_batch_size, }, no_poh_speed_test: matches.is_present("no_poh_speed_test"), no_os_memory_stats_reporting: matches.is_present("no_os_memory_stats_reporting"), no_os_network_stats_reporting: matches.is_present("no_os_network_stats_reporting"), no_os_cpu_stats_reporting: matches.is_present("no_os_cpu_stats_reporting"), no_os_disk_stats_reporting: matches.is_present("no_os_disk_stats_reporting"), poh_pinned_cpu_core: value_of(&matches, "poh_pinned_cpu_core") .unwrap_or(poh_service::DEFAULT_PINNED_CPU_CORE), poh_hashes_per_batch: value_of(&matches, "poh_hashes_per_batch") .unwrap_or(poh_service::DEFAULT_HASHES_PER_BATCH), process_ledger_before_services: matches.is_present("process_ledger_before_services"), account_indexes, accounts_db_test_hash_calculation: matches.is_present("accounts_db_test_hash_calculation"), accounts_db_config, accounts_db_skip_shrink: true, tpu_coalesce_ms, no_wait_for_vote_to_start_leader: matches.is_present("no_wait_for_vote_to_start_leader"), accounts_shrink_ratio, runtime_config: RuntimeConfig { bpf_jit: !matches.is_present("no_bpf_jit"), log_messages_bytes_limit: value_of(&matches, "log_messages_bytes_limit"), ..RuntimeConfig::default() }, staked_nodes_overrides: staked_nodes_overrides.clone(), replay_slots_concurrently: matches.is_present("replay_slots_concurrently"), ..ValidatorConfig::default() }; let vote_account = pubkey_of(&matches, "vote_account").unwrap_or_else(|| { if !validator_config.voting_disabled { warn!("--vote-account not specified, validator will not vote"); validator_config.voting_disabled = true; } Keypair::new().pubkey() }); let dynamic_port_range = solana_net_utils::parse_port_range(matches.value_of("dynamic_port_range").unwrap()) .expect("invalid dynamic_port_range"); let account_paths: Vec = if let Ok(account_paths) = values_t!(matches, "account_paths", String) { account_paths .join(",") .split(',') .map(PathBuf::from) .collect() } else { vec![ledger_path.join("accounts")] }; let account_shrink_paths: Option> = values_t!(matches, "account_shrink_path", String) .map(|shrink_paths| shrink_paths.into_iter().map(PathBuf::from).collect()) .ok(); // Create and canonicalize account paths to avoid issues with symlink creation account_paths.iter().for_each(|account_path| { fs::create_dir_all(account_path) .and_then(|_| fs::canonicalize(account_path)) .unwrap_or_else(|err| { eprintln!("Unable to access account path: {account_path:?}, err: {err:?}"); exit(1); }); }); let (account_run_paths, account_snapshot_paths) = create_all_accounts_run_and_snapshot_dirs(&account_paths).unwrap_or_else(|err| { eprintln!("Error: {err:?}"); exit(1); }); // From now on, use run/ paths in the same way as the previous account_paths. validator_config.account_paths = account_run_paths; validator_config.account_snapshot_paths = account_snapshot_paths; validator_config.account_shrink_paths = account_shrink_paths.map(|paths| { paths .into_iter() .map(|account_path| { match fs::create_dir_all(&account_path) .and_then(|_| fs::canonicalize(&account_path)) { Ok(account_path) => account_path, Err(err) => { eprintln!("Unable to access account path: {account_path:?}, err: {err:?}"); exit(1); } } }) .collect() }); let maximum_local_snapshot_age = value_t_or_exit!(matches, "maximum_local_snapshot_age", u64); let maximum_full_snapshot_archives_to_retain = value_t_or_exit!(matches, "maximum_full_snapshots_to_retain", NonZeroUsize); let maximum_incremental_snapshot_archives_to_retain = value_t_or_exit!( matches, "maximum_incremental_snapshots_to_retain", NonZeroUsize ); let snapshot_packager_niceness_adj = value_t_or_exit!(matches, "snapshot_packager_niceness_adj", i8); let minimal_snapshot_download_speed = value_t_or_exit!(matches, "minimal_snapshot_download_speed", f32); let maximum_snapshot_download_abort = value_t_or_exit!(matches, "maximum_snapshot_download_abort", u64); let full_snapshot_archives_dir = if matches.is_present("snapshots") { PathBuf::from(matches.value_of("snapshots").unwrap()) } else { ledger_path.clone() }; let incremental_snapshot_archives_dir = if matches.is_present("incremental_snapshot_archive_path") { let incremental_snapshot_archives_dir = PathBuf::from( matches .value_of("incremental_snapshot_archive_path") .unwrap(), ); fs::create_dir_all(&incremental_snapshot_archives_dir).unwrap_or_else(|err| { eprintln!( "Failed to create incremental snapshot archives directory {:?}: {}", incremental_snapshot_archives_dir.display(), err ); exit(1); }); incremental_snapshot_archives_dir } else { full_snapshot_archives_dir.clone() }; let bank_snapshots_dir = incremental_snapshot_archives_dir.join("snapshot"); fs::create_dir_all(&bank_snapshots_dir).unwrap_or_else(|err| { eprintln!( "Failed to create snapshots directory {:?}: {}", bank_snapshots_dir.display(), err ); exit(1); }); let archive_format = { let archive_format_str = value_t_or_exit!(matches, "snapshot_archive_format", String); ArchiveFormat::from_cli_arg(&archive_format_str) .unwrap_or_else(|| panic!("Archive format not recognized: {archive_format_str}")) }; let snapshot_version = matches .value_of("snapshot_version") .map_or(SnapshotVersion::default(), |s| { s.parse::().unwrap_or_else(|err| { eprintln!("Error: {err}"); exit(1) }) }); let incremental_snapshot_interval_slots = value_t_or_exit!(matches, "incremental_snapshot_interval_slots", u64); let (full_snapshot_archive_interval_slots, incremental_snapshot_archive_interval_slots) = if incremental_snapshot_interval_slots > 0 { if !matches.is_present("no_incremental_snapshots") { ( value_t_or_exit!(matches, "full_snapshot_interval_slots", u64), incremental_snapshot_interval_slots, ) } else { (incremental_snapshot_interval_slots, Slot::MAX) } } else { (Slot::MAX, Slot::MAX) }; validator_config.snapshot_config = SnapshotConfig { usage: if full_snapshot_archive_interval_slots == Slot::MAX { SnapshotUsage::LoadOnly } else { SnapshotUsage::LoadAndGenerate }, full_snapshot_archive_interval_slots, incremental_snapshot_archive_interval_slots, bank_snapshots_dir, full_snapshot_archives_dir: full_snapshot_archives_dir.clone(), incremental_snapshot_archives_dir: incremental_snapshot_archives_dir.clone(), archive_format, snapshot_version, maximum_full_snapshot_archives_to_retain, maximum_incremental_snapshot_archives_to_retain, accounts_hash_debug_verify: validator_config.accounts_db_test_hash_calculation, packager_thread_niceness_adj: snapshot_packager_niceness_adj, }; validator_config.accounts_hash_interval_slots = value_t_or_exit!(matches, "accounts-hash-interval-slots", u64); if !is_snapshot_config_valid( &validator_config.snapshot_config, validator_config.accounts_hash_interval_slots, ) { eprintln!("Invalid snapshot configuration provided: snapshot intervals are incompatible. \ \n\t- full snapshot interval MUST be a multiple of accounts hash interval (if enabled) \ \n\t- incremental snapshot interval MUST be a multiple of accounts hash interval (if enabled) \ \n\t- full snapshot interval MUST be larger than incremental snapshot interval (if enabled) \ \nSnapshot configuration values: \ \n\tfull snapshot interval: {} \ \n\tincremental snapshot interval: {} \ \n\taccounts hash interval: {}", if full_snapshot_archive_interval_slots == Slot::MAX { "disabled".to_string() } else { full_snapshot_archive_interval_slots.to_string() }, if incremental_snapshot_archive_interval_slots == Slot::MAX { "disabled".to_string() } else { incremental_snapshot_archive_interval_slots.to_string() }, validator_config.accounts_hash_interval_slots); exit(1); } if matches.is_present("limit_ledger_size") { let limit_ledger_size = match matches.value_of("limit_ledger_size") { Some(_) => value_t_or_exit!(matches, "limit_ledger_size", u64), None => DEFAULT_MAX_LEDGER_SHREDS, }; if limit_ledger_size < DEFAULT_MIN_MAX_LEDGER_SHREDS { eprintln!( "The provided --limit-ledger-size value was too small, the minimum value is {DEFAULT_MIN_MAX_LEDGER_SHREDS}" ); exit(1); } validator_config.max_ledger_shreds = Some(limit_ledger_size); } configure_banking_trace_dir_byte_limit(&mut validator_config, &matches); validator_config.block_verification_method = value_t!( matches, "block_verification_method", BlockVerificationMethod ) .unwrap_or_default(); validator_config.block_production_method = value_t!( matches, // comment to align formatting... "block_production_method", BlockProductionMethod ) .unwrap_or_default(); validator_config.ledger_column_options = LedgerColumnOptions { compression_type: match matches.value_of("rocksdb_ledger_compression") { None => BlockstoreCompressionType::default(), Some(ledger_compression_string) => match ledger_compression_string { "none" => BlockstoreCompressionType::None, "snappy" => BlockstoreCompressionType::Snappy, "lz4" => BlockstoreCompressionType::Lz4, "zlib" => BlockstoreCompressionType::Zlib, _ => panic!("Unsupported ledger_compression: {ledger_compression_string}"), }, }, shred_storage_type: match matches.value_of("rocksdb_shred_compaction") { None => ShredStorageType::default(), Some(shred_compaction_string) => match shred_compaction_string { "level" => ShredStorageType::RocksLevel, "fifo" => match matches.value_of("rocksdb_fifo_shred_storage_size") { None => ShredStorageType::rocks_fifo(default_fifo_shred_storage_size( &validator_config, )), Some(_) => ShredStorageType::rocks_fifo(Some(value_t_or_exit!( matches, "rocksdb_fifo_shred_storage_size", u64 ))), }, _ => panic!("Unrecognized rocksdb-shred-compaction: {shred_compaction_string}"), }, }, rocks_perf_sample_interval: value_t_or_exit!( matches, "rocksdb_perf_sample_interval", usize ), }; if matches.is_present("halt_on_known_validators_accounts_hash_mismatch") { validator_config.halt_on_known_validators_accounts_hash_mismatch = true; } let public_rpc_addr = matches.value_of("public_rpc_addr").map(|addr| { solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| { eprintln!("failed to parse public rpc address: {e}"); exit(1); }) }); if !matches.is_present("no_os_network_limits_test") { if SystemMonitorService::check_os_network_limits() { info!("OS network limits test passed."); } else { eprintln!("OS network limit test failed. See: https://docs.solana.com/running-validator/validator-start#system-tuning"); exit(1); } } let mut ledger_lock = ledger_lockfile(&ledger_path); let _ledger_write_guard = lock_ledger(&ledger_path, &mut ledger_lock); let start_progress = Arc::new(RwLock::new(ValidatorStartProgress::default())); let admin_service_post_init = Arc::new(RwLock::new(None)); let (rpc_to_plugin_manager_sender, rpc_to_plugin_manager_receiver) = if starting_with_geyser_plugins { let (sender, receiver) = unbounded(); (Some(sender), Some(receiver)) } else { (None, None) }; admin_rpc_service::run( &ledger_path, admin_rpc_service::AdminRpcRequestMetadata { rpc_addr: validator_config.rpc_addrs.map(|(rpc_addr, _)| rpc_addr), start_time: std::time::SystemTime::now(), validator_exit: validator_config.validator_exit.clone(), start_progress: start_progress.clone(), authorized_voter_keypairs: authorized_voter_keypairs.clone(), post_init: admin_service_post_init.clone(), tower_storage: validator_config.tower_storage.clone(), staked_nodes_overrides, rpc_to_plugin_manager_sender, }, ); let gossip_host: IpAddr = matches .value_of("gossip_host") .map(|gossip_host| { solana_net_utils::parse_host(gossip_host).unwrap_or_else(|err| { eprintln!("Failed to parse --gossip-host: {err}"); exit(1); }) }) .unwrap_or_else(|| { if !entrypoint_addrs.is_empty() { let mut order: Vec<_> = (0..entrypoint_addrs.len()).collect(); order.shuffle(&mut thread_rng()); let gossip_host = order.into_iter().find_map(|i| { let entrypoint_addr = &entrypoint_addrs[i]; info!( "Contacting {} to determine the validator's public IP address", entrypoint_addr ); solana_net_utils::get_public_ip_addr(entrypoint_addr).map_or_else( |err| { eprintln!( "Failed to contact cluster entrypoint {entrypoint_addr}: {err}" ); None }, Some, ) }); gossip_host.unwrap_or_else(|| { eprintln!("Unable to determine the validator's public IP address"); exit(1); }) } else { IpAddr::V4(Ipv4Addr::LOCALHOST) } }); let gossip_addr = SocketAddr::new( gossip_host, value_t!(matches, "gossip_port", u16).unwrap_or_else(|_| { solana_net_utils::find_available_port_in_range(bind_address, (0, 1)).unwrap_or_else( |err| { eprintln!("Unable to find an available gossip port: {err}"); exit(1); }, ) }), ); let overwrite_tpu_addr = matches.value_of("tpu_host_addr").map(|tpu_addr| { solana_net_utils::parse_host_port(tpu_addr).unwrap_or_else(|err| { eprintln!("Failed to parse --overwrite-tpu-addr: {err}"); exit(1); }) }); let cluster_entrypoints = entrypoint_addrs .iter() .map(ContactInfo::new_gossip_entry_point) .collect::>(); let mut node = Node::new_with_external_ip( &identity_keypair.pubkey(), &gossip_addr, dynamic_port_range, bind_address, overwrite_tpu_addr, ); if restricted_repair_only_mode { // When in --restricted_repair_only_mode is enabled only the gossip and repair ports // need to be reachable by the entrypoint to respond to gossip pull requests and repair // requests initiated by the node. All other ports are unused. node.info.remove_tpu(); node.info.remove_tpu_forwards(); node.info.remove_tvu(); node.info.remove_tvu_forwards(); node.info.remove_serve_repair(); // A node in this configuration shouldn't be an entrypoint to other nodes node.sockets.ip_echo = None; } if !private_rpc { macro_rules! set_socket { ($method:ident, $addr:expr, $name:literal) => { node.info.$method($addr).expect(&format!( "Operator must spin up node with valid {} address", $name )) }; } if let Some(public_rpc_addr) = public_rpc_addr { set_socket!(set_rpc, public_rpc_addr, "RPC"); set_socket!(set_rpc_pubsub, public_rpc_addr, "RPC-pubsub"); } else if let Some((rpc_addr, rpc_pubsub_addr)) = validator_config.rpc_addrs { let addr = node .info .gossip() .expect("Operator must spin up node with valid gossip address") .ip(); set_socket!(set_rpc, (addr, rpc_addr.port()), "RPC"); set_socket!(set_rpc_pubsub, (addr, rpc_pubsub_addr.port()), "RPC-pubsub"); } } solana_metrics::set_host_id(identity_keypair.pubkey().to_string()); solana_metrics::set_panic_hook("validator", Some(String::from(solana_version))); solana_entry::entry::init_poh(); snapshot_utils::remove_tmp_snapshot_archives(&full_snapshot_archives_dir); snapshot_utils::remove_tmp_snapshot_archives(&incremental_snapshot_archives_dir); let identity_keypair = Arc::new(identity_keypair); let should_check_duplicate_instance = true; if !cluster_entrypoints.is_empty() { bootstrap::rpc_bootstrap( &node, &identity_keypair, &ledger_path, &full_snapshot_archives_dir, &incremental_snapshot_archives_dir, &vote_account, authorized_voter_keypairs.clone(), &cluster_entrypoints, &mut validator_config, rpc_bootstrap_config, do_port_check, use_progress_bar, maximum_local_snapshot_age, should_check_duplicate_instance, &start_progress, minimal_snapshot_download_speed, maximum_snapshot_download_abort, socket_addr_space, ); *start_progress.write().unwrap() = ValidatorStartProgress::Initializing; } if operation == Operation::Initialize { info!("Validator ledger initialization complete"); return; } let validator = Validator::new( node, identity_keypair, &ledger_path, &vote_account, authorized_voter_keypairs, cluster_entrypoints, &validator_config, should_check_duplicate_instance, rpc_to_plugin_manager_receiver, start_progress, socket_addr_space, tpu_use_quic, tpu_connection_pool_size, tpu_enable_udp, admin_service_post_init, ) .unwrap_or_else(|e| { error!("Failed to start validator: {:?}", e); exit(1); }); if let Some(filename) = init_complete_file { File::create(filename).unwrap_or_else(|_| { error!("Unable to create: {}", filename); exit(1); }); } info!("Validator initialized"); validator.join(); info!("Validator exiting.."); } fn process_account_indexes(matches: &ArgMatches) -> AccountSecondaryIndexes { let account_indexes: HashSet = matches .values_of("account_indexes") .unwrap_or_default() .map(|value| match value { "program-id" => AccountIndex::ProgramId, "spl-token-mint" => AccountIndex::SplTokenMint, "spl-token-owner" => AccountIndex::SplTokenOwner, _ => unreachable!(), }) .collect(); let account_indexes_include_keys: HashSet = values_t!(matches, "account_index_include_key", Pubkey) .unwrap_or_default() .iter() .cloned() .collect(); let account_indexes_exclude_keys: HashSet = values_t!(matches, "account_index_exclude_key", Pubkey) .unwrap_or_default() .iter() .cloned() .collect(); let exclude_keys = !account_indexes_exclude_keys.is_empty(); let include_keys = !account_indexes_include_keys.is_empty(); let keys = if !account_indexes.is_empty() && (exclude_keys || include_keys) { let account_indexes_keys = AccountSecondaryIndexesIncludeExclude { exclude: exclude_keys, keys: if exclude_keys { account_indexes_exclude_keys } else { account_indexes_include_keys }, }; Some(account_indexes_keys) } else { None }; AccountSecondaryIndexes { keys, indexes: account_indexes, } }