From a26e1f62bbcc379c2f2786b5289f754f0a5b3e0e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 19 Sep 2020 22:45:09 +0000 Subject: [PATCH] validator/ cleanup (bp #12340) (#12352) * validator/ cleanup (cherry picked from commit 1a03afccb1d969783e8be590bda8b26a1b1d1ecf) # Conflicts: # core/src/validator.rs * Move TestValidator into its own module (cherry picked from commit 208dd1de3afc9c1f9d39ace9a65ee2834f288dbf) # Conflicts: # core/src/validator.rs # tokens/tests/commands.rs * Rebase Co-authored-by: Michael Vines --- cli/tests/deploy.rs | 2 +- cli/tests/nonce.rs | 2 +- cli/tests/pay.rs | 2 +- cli/tests/request_airdrop.rs | 2 +- cli/tests/stake.rs | 2 +- cli/tests/transfer.rs | 2 +- cli/tests/vote.rs | 2 +- core/src/lib.rs | 1 + core/src/test_validator.rs | 106 ++++++ core/src/validator.rs | 136 ++------ core/tests/client.rs | 2 +- core/tests/rpc.rs | 2 +- local-cluster/src/local_cluster.rs | 3 - tokens/tests/commands.rs | 2 +- validator/Cargo.toml | 7 + validator/src/main.rs | 544 ++++++++++++++++------------- 16 files changed, 454 insertions(+), 363 deletions(-) create mode 100644 core/src/test_validator.rs diff --git a/cli/tests/deploy.rs b/cli/tests/deploy.rs index f605d5658e..3161e17f8d 100644 --- a/cli/tests/deploy.rs +++ b/cli/tests/deploy.rs @@ -1,7 +1,7 @@ use serde_json::Value; use solana_cli::cli::{process_command, CliCommand, CliConfig}; use solana_client::rpc_client::RpcClient; -use solana_core::validator::TestValidator; +use solana_core::test_validator::TestValidator; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ bpf_loader, diff --git a/cli/tests/nonce.rs b/cli/tests/nonce.rs index 12f786e42b..baec3ed00f 100644 --- a/cli/tests/nonce.rs +++ b/cli/tests/nonce.rs @@ -11,7 +11,7 @@ use solana_cli::{ }; use solana_client::rpc_client::RpcClient; use solana_core::contact_info::ContactInfo; -use solana_core::validator::{TestValidator, TestValidatorOptions}; +use solana_core::test_validator::{TestValidator, TestValidatorOptions}; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ commitment_config::CommitmentConfig, diff --git a/cli/tests/pay.rs b/cli/tests/pay.rs index 8ba44159d5..3900dc3b71 100644 --- a/cli/tests/pay.rs +++ b/cli/tests/pay.rs @@ -12,7 +12,7 @@ use solana_cli::{ test_utils::check_recent_balance, }; use solana_client::rpc_client::RpcClient; -use solana_core::validator::TestValidator; +use solana_core::test_validator::TestValidator; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ commitment_config::CommitmentConfig, diff --git a/cli/tests/request_airdrop.rs b/cli/tests/request_airdrop.rs index fd04d3441e..d6764b535e 100644 --- a/cli/tests/request_airdrop.rs +++ b/cli/tests/request_airdrop.rs @@ -1,6 +1,6 @@ use solana_cli::cli::{process_command, CliCommand, CliConfig}; use solana_client::rpc_client::RpcClient; -use solana_core::validator::TestValidator; +use solana_core::test_validator::TestValidator; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; use std::{fs::remove_dir_all, sync::mpsc::channel}; diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index 1975616762..6b6843aaeb 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -10,7 +10,7 @@ use solana_cli::{ test_utils::{check_ready, check_recent_balance}, }; use solana_client::rpc_client::RpcClient; -use solana_core::validator::{TestValidator, TestValidatorOptions}; +use solana_core::test_validator::{TestValidator, TestValidatorOptions}; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ account_utils::StateMut, diff --git a/cli/tests/transfer.rs b/cli/tests/transfer.rs index 86bb6fd54d..3c849b026a 100644 --- a/cli/tests/transfer.rs +++ b/cli/tests/transfer.rs @@ -10,7 +10,7 @@ use solana_cli::{ test_utils::{check_ready, check_recent_balance}, }; use solana_client::rpc_client::RpcClient; -use solana_core::validator::{TestValidator, TestValidatorOptions}; +use solana_core::test_validator::{TestValidator, TestValidatorOptions}; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ commitment_config::CommitmentConfig, diff --git a/cli/tests/vote.rs b/cli/tests/vote.rs index 3273adf75f..bb32dba825 100644 --- a/cli/tests/vote.rs +++ b/cli/tests/vote.rs @@ -5,7 +5,7 @@ use solana_cli::{ test_utils::check_recent_balance, }; use solana_client::rpc_client::RpcClient; -use solana_core::validator::TestValidator; +use solana_core::test_validator::TestValidator; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ account_utils::StateMut, diff --git a/core/src/lib.rs b/core/src/lib.rs index fbc4660567..8fedcb293d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -65,6 +65,7 @@ pub mod sigverify; pub mod sigverify_shreds; pub mod sigverify_stage; pub mod snapshot_packager_service; +pub mod test_validator; pub mod tpu; pub mod transaction_status_service; pub mod tree_diff; diff --git a/core/src/test_validator.rs b/core/src/test_validator.rs new file mode 100644 index 0000000000..8072c1c970 --- /dev/null +++ b/core/src/test_validator.rs @@ -0,0 +1,106 @@ +use crate::{ + cluster_info::Node, + contact_info::ContactInfo, + gossip_service::discover_cluster, + validator::{Validator, ValidatorConfig}, +}; +use solana_ledger::create_new_tmp_ledger; +use solana_sdk::{ + hash::Hash, + pubkey::Pubkey, + signature::{Keypair, Signer}, +}; +use std::{path::PathBuf, sync::Arc}; + +pub struct TestValidator { + pub server: Validator, + pub leader_data: ContactInfo, + pub alice: Keypair, + pub ledger_path: PathBuf, + pub genesis_hash: Hash, + pub vote_pubkey: Pubkey, +} + +pub struct TestValidatorOptions { + pub fees: u64, + pub bootstrap_validator_lamports: u64, + pub mint_lamports: u64, +} + +impl Default for TestValidatorOptions { + fn default() -> Self { + use solana_ledger::genesis_utils::BOOTSTRAP_VALIDATOR_LAMPORTS; + TestValidatorOptions { + fees: 0, + bootstrap_validator_lamports: BOOTSTRAP_VALIDATOR_LAMPORTS, + mint_lamports: 1_000_000, + } + } +} + +impl TestValidator { + pub fn run() -> Self { + Self::run_with_options(TestValidatorOptions::default()) + } + + pub fn run_with_options(options: TestValidatorOptions) -> Self { + use solana_ledger::genesis_utils::{ + create_genesis_config_with_leader_ex, GenesisConfigInfo, + }; + use solana_sdk::fee_calculator::FeeRateGovernor; + + let TestValidatorOptions { + fees, + bootstrap_validator_lamports, + mint_lamports, + } = options; + let node_keypair = Arc::new(Keypair::new()); + let node = Node::new_localhost_with_pubkey(&node_keypair.pubkey()); + let contact_info = node.info.clone(); + + let GenesisConfigInfo { + mut genesis_config, + mint_keypair, + voting_keypair, + } = create_genesis_config_with_leader_ex( + mint_lamports, + &contact_info.id, + &Keypair::new(), + &Pubkey::new_rand(), + 42, + bootstrap_validator_lamports, + ); + genesis_config + .native_instruction_processors + .push(solana_budget_program!()); + genesis_config.rent.lamports_per_byte_year = 1; + genesis_config.rent.exemption_threshold = 1.0; + genesis_config.fee_rate_governor = FeeRateGovernor::new(fees, 0); + + let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_config); + + let config = ValidatorConfig { + rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub, node.info.rpc_banks)), + ..ValidatorConfig::default() + }; + let vote_pubkey = voting_keypair.pubkey(); + let node = Validator::new( + node, + &node_keypair, + &ledger_path, + &voting_keypair.pubkey(), + vec![Arc::new(voting_keypair)], + None, + &config, + ); + discover_cluster(&contact_info.gossip, 1).expect("Node startup failed"); + TestValidator { + server: node, + leader_data: contact_info, + alice: mint_keypair, + ledger_path, + genesis_hash: blockhash, + vote_pubkey, + } + } +} diff --git a/core/src/validator.rs b/core/src/validator.rs index 87b7834628..c65ddee621 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -6,7 +6,7 @@ use crate::{ cluster_info_vote_listener::VoteTracker, completed_data_sets_service::CompletedDataSetsService, contact_info::ContactInfo, - gossip_service::{discover_cluster, GossipService}, + gossip_service::GossipService, poh_recorder::{PohRecorder, GRACE_TICKS_FACTOR, MAX_GRACE_SLOTS}, poh_service::PohService, rewards_recorder_service::{RewardsRecorderSender, RewardsRecorderService}, @@ -30,7 +30,6 @@ use solana_ledger::{ blockstore::{Blockstore, BlockstoreSignals, CompletedSlotsReceiver, PurgeType}, blockstore_db::BlockstoreRecoveryMode, blockstore_processor::{self, TransactionStatusSender}, - create_new_tmp_ledger, leader_schedule::FixedSchedule, leader_schedule_cache::LeaderScheduleCache, }; @@ -65,7 +64,7 @@ use std::{ time::Duration, }; -pub const MAX_COMPLETED_DATA_SETS_IN_CHANNEL: usize = 100_000; +const MAX_COMPLETED_DATA_SETS_IN_CHANNEL: usize = 100_000; #[derive(Clone, Debug)] pub struct ValidatorConfig { @@ -94,6 +93,8 @@ pub struct ValidatorConfig { pub accounts_hash_interval_slots: u64, pub max_genesis_archive_unpacked_size: u64, pub wal_recovery_mode: Option, + pub poh_verify: bool, // Perform PoH verification during blockstore processing at boo + pub cuda: bool, } impl Default for ValidatorConfig { @@ -124,6 +125,8 @@ impl Default for ValidatorConfig { accounts_hash_interval_slots: std::u64::MAX, max_genesis_archive_unpacked_size: MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, wal_recovery_mode: None, + poh_verify: true, + cuda: false, } } } @@ -171,18 +174,16 @@ pub struct Validator { } impl Validator { - #[allow(clippy::cognitive_complexity)] pub fn new( mut node: Node, - keypair: &Arc, + identity_keypair: &Arc, ledger_path: &Path, vote_account: &Pubkey, mut authorized_voter_keypairs: Vec>, - entrypoint_info_option: Option<&ContactInfo>, - poh_verify: bool, + cluster_entrypoint: Option<&ContactInfo>, config: &ValidatorConfig, ) -> Self { - let id = keypair.pubkey(); + let id = identity_keypair.pubkey(); assert_eq!(id, node.info.id); warn!("identity: {}", id); @@ -198,7 +199,7 @@ impl Validator { } report_target_features(); - info!("entrypoint: {:?}", entrypoint_info_option); + info!("entrypoint: {:?}", cluster_entrypoint); if solana_perf::perf_libs::api().is_some() { info!("Initializing sigverify, this could take a while..."); @@ -208,6 +209,14 @@ impl Validator { sigverify::init(); info!("Done."); + if !ledger_path.is_dir() { + error!( + "ledger directory does not exist or is not accessible: {:?}", + ledger_path + ); + process::exit(1); + } + if let Some(shred_version) = config.expected_shred_version { if let Some(wait_for_supermajority_slot) = config.wait_for_supermajority { backup_and_clear_blockstore( @@ -247,7 +256,7 @@ impl Validator { rewards_recorder_sender, rewards_recorder_service, }, - ) = new_banks_from_ledger(config, ledger_path, poh_verify, &exit); + ) = new_banks_from_ledger(config, ledger_path, config.poh_verify, &exit); let leader_schedule_cache = Arc::new(leader_schedule_cache); let bank = bank_forks.working_bank(); @@ -279,7 +288,10 @@ impl Validator { } } - let cluster_info = Arc::new(ClusterInfo::new(node.info.clone(), keypair.clone())); + let cluster_info = Arc::new(ClusterInfo::new( + node.info.clone(), + identity_keypair.clone(), + )); let mut block_commitment_cache = BlockCommitmentCache::default(); block_commitment_cache.initialize_slots(bank.slot()); let block_commitment_cache = Arc::new(RwLock::new(block_commitment_cache)); @@ -405,8 +417,8 @@ impl Validator { // Insert the entrypoint info, should only be None if this node // is the bootstrap validator - if let Some(entrypoint_info) = entrypoint_info_option { - cluster_info.set_entrypoint(entrypoint_info.clone()); + if let Some(cluster_entrypoint) = cluster_entrypoint { + cluster_info.set_entrypoint(cluster_entrypoint.clone()); } let (snapshot_packager_service, snapshot_package_sender) = @@ -833,100 +845,6 @@ fn wait_for_supermajority( false } -pub struct TestValidator { - pub server: Validator, - pub leader_data: ContactInfo, - pub alice: Keypair, - pub ledger_path: PathBuf, - pub genesis_hash: Hash, - pub vote_pubkey: Pubkey, -} - -pub struct TestValidatorOptions { - pub fees: u64, - pub bootstrap_validator_lamports: u64, - pub mint_lamports: u64, -} - -impl Default for TestValidatorOptions { - fn default() -> Self { - use solana_ledger::genesis_utils::BOOTSTRAP_VALIDATOR_LAMPORTS; - TestValidatorOptions { - fees: 0, - bootstrap_validator_lamports: BOOTSTRAP_VALIDATOR_LAMPORTS, - mint_lamports: 1_000_000, - } - } -} - -impl TestValidator { - pub fn run() -> Self { - Self::run_with_options(TestValidatorOptions::default()) - } - - pub fn run_with_options(options: TestValidatorOptions) -> Self { - use solana_ledger::genesis_utils::{ - create_genesis_config_with_leader_ex, GenesisConfigInfo, - }; - use solana_sdk::fee_calculator::FeeRateGovernor; - - let TestValidatorOptions { - fees, - bootstrap_validator_lamports, - mint_lamports, - } = options; - let node_keypair = Arc::new(Keypair::new()); - let node = Node::new_localhost_with_pubkey(&node_keypair.pubkey()); - let contact_info = node.info.clone(); - - let GenesisConfigInfo { - mut genesis_config, - mint_keypair, - voting_keypair, - } = create_genesis_config_with_leader_ex( - mint_lamports, - &contact_info.id, - &Keypair::new(), - &Pubkey::new_rand(), - 42, - bootstrap_validator_lamports, - ); - genesis_config - .native_instruction_processors - .push(solana_budget_program!()); - genesis_config.rent.lamports_per_byte_year = 1; - genesis_config.rent.exemption_threshold = 1.0; - genesis_config.fee_rate_governor = FeeRateGovernor::new(fees, 0); - - let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_config); - - let config = ValidatorConfig { - rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub, node.info.rpc_banks)), - ..ValidatorConfig::default() - }; - let vote_pubkey = voting_keypair.pubkey(); - let node = Validator::new( - node, - &node_keypair, - &ledger_path, - &voting_keypair.pubkey(), - vec![Arc::new(voting_keypair)], - None, - true, - &config, - ); - discover_cluster(&contact_info.gossip, 1).expect("Node startup failed"); - TestValidator { - server: node, - leader_data: contact_info, - alice: mint_keypair, - ledger_path, - genesis_hash: blockhash, - vote_pubkey, - } - } -} - fn report_target_features() { warn!( "CUDA is {}abled", @@ -1049,7 +967,7 @@ fn cleanup_accounts_path(account_path: &std::path::Path) { #[cfg(test)] mod tests { use super::*; - use solana_ledger::genesis_utils::create_genesis_config_with_leader; + use solana_ledger::{create_new_tmp_ledger, genesis_utils::create_genesis_config_with_leader}; use std::fs::remove_dir_all; #[test] @@ -1081,7 +999,6 @@ mod tests { &voting_keypair.pubkey(), vec![voting_keypair.clone()], Some(&leader_node.info), - true, &config, ); validator.close().unwrap(); @@ -1156,7 +1073,6 @@ mod tests { &vote_account_keypair.pubkey(), vec![Arc::new(vote_account_keypair)], Some(&leader_node.info), - true, &config, ) }) diff --git a/core/tests/client.rs b/core/tests/client.rs index bffe2e913d..bebe1a7773 100644 --- a/core/tests/client.rs +++ b/core/tests/client.rs @@ -1,7 +1,7 @@ use solana_client::{pubsub_client::PubsubClient, rpc_client::RpcClient, rpc_response::SlotInfo}; use solana_core::{ rpc_pubsub_service::PubSubService, rpc_subscriptions::RpcSubscriptions, - validator::TestValidator, + test_validator::TestValidator, }; use solana_runtime::{ bank::Bank, diff --git a/core/tests/rpc.rs b/core/tests/rpc.rs index 59cc32ff36..61406587bd 100644 --- a/core/tests/rpc.rs +++ b/core/tests/rpc.rs @@ -13,7 +13,7 @@ use solana_client::{ rpc_response::{Response, RpcSignatureResult}, }; use solana_core::contact_info::ContactInfo; -use solana_core::{rpc_pubsub::gen_client::Client as PubsubClient, validator::TestValidator}; +use solana_core::{rpc_pubsub::gen_client::Client as PubsubClient, test_validator::TestValidator}; use solana_sdk::{ commitment_config::CommitmentConfig, hash::Hash, pubkey::Pubkey, signature::Signer, system_transaction, transaction::Transaction, diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index ebb67ecac4..03e010bb71 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -217,7 +217,6 @@ impl LocalCluster { &leader_vote_keypair.pubkey(), vec![leader_vote_keypair.clone()], None, - true, &leader_config, ); @@ -365,7 +364,6 @@ impl LocalCluster { &voting_keypair.pubkey(), vec![voting_keypair.clone()], Some(&self.entry_point_info), - true, &config, ); @@ -648,7 +646,6 @@ impl Cluster for LocalCluster { &validator_info.voting_keypair.pubkey(), vec![validator_info.voting_keypair.clone()], entry_point_info, - true, &cluster_validator_info.config, ); diff --git a/tokens/tests/commands.rs b/tokens/tests/commands.rs index c58302f0a7..aa4da70cd7 100644 --- a/tokens/tests/commands.rs +++ b/tokens/tests/commands.rs @@ -1,5 +1,5 @@ use solana_client::rpc_client::RpcClient; -use solana_core::validator::{TestValidator, TestValidatorOptions}; +use solana_core::test_validator::{TestValidator, TestValidatorOptions}; use solana_sdk::native_token::sol_to_lamports; use solana_tokens::commands::test_process_distribute_tokens_with_client; use std::fs::remove_dir_all; diff --git a/validator/Cargo.toml b/validator/Cargo.toml index d35782a732..6993d40d56 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -35,5 +35,12 @@ solana-vote-signer = { path = "../vote-signer", version = "1.3.12" } libc = "0.2.72" signal-hook = "0.1.15" +#[[bin]] +#name = "solana-validator" +#path = "src/main.rs" +# +#[lib] +#name = "solana_validator" + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/validator/src/main.rs b/validator/src/main.rs index a34fbb35a5..6535f9477e 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -42,7 +42,7 @@ use std::{ env, fs::{self, File}, net::{SocketAddr, TcpListener, UdpSocket}, - path::PathBuf, + path::{Path, PathBuf}, process::exit, str::FromStr, sync::{ @@ -514,7 +514,270 @@ fn start_logger(logfile: Option) -> Option> { logger_thread } -#[allow(clippy::cognitive_complexity)] +fn verify_reachable_ports(node: &Node, cluster_entrypoint: &ContactInfo) { + let mut udp_sockets = vec![&node.sockets.gossip, &node.sockets.repair]; + + if ContactInfo::is_valid_address(&node.info.serve_repair) { + udp_sockets.push(&node.sockets.serve_repair); + } + if ContactInfo::is_valid_address(&node.info.tpu) { + udp_sockets.extend(node.sockets.tpu.iter()); + } + if ContactInfo::is_valid_address(&node.info.tpu_forwards) { + udp_sockets.extend(node.sockets.tpu_forwards.iter()); + } + if ContactInfo::is_valid_address(&node.info.tvu) { + udp_sockets.extend(node.sockets.tvu.iter()); + udp_sockets.extend(node.sockets.broadcast.iter()); + udp_sockets.extend(node.sockets.retransmit_sockets.iter()); + } + if ContactInfo::is_valid_address(&node.info.tvu_forwards) { + udp_sockets.extend(node.sockets.tvu_forwards.iter()); + } + + let mut tcp_listeners = vec![]; + for (purpose, addr) in &[ + ("RPC", node.info.rpc), + ("RPC pubsub", node.info.rpc_pubsub), + ("RPC banks", node.info.rpc_banks), + ] { + if ContactInfo::is_valid_address(&addr) { + tcp_listeners.push(( + addr.port(), + TcpListener::bind(addr).unwrap_or_else(|err| { + error!( + "Unable to bind to tcp/{} for {}: {}", + addr.port(), + purpose, + err + ); + exit(1); + }), + )); + } + } + + if let Some(ip_echo) = &node.sockets.ip_echo { + let ip_echo = ip_echo.try_clone().expect("unable to clone tcp_listener"); + tcp_listeners.push((ip_echo.local_addr().unwrap().port(), ip_echo)); + } + + if !solana_net_utils::verify_reachable_ports( + &cluster_entrypoint.gossip, + tcp_listeners, + &udp_sockets, + ) { + exit(1); + } +} + +struct RpcBootstrapConfig { + no_genesis_fetch: bool, + no_snapshot_fetch: bool, + no_untrusted_rpc: bool, + max_genesis_archive_unpacked_size: u64, + no_check_vote_account: bool, +} + +impl Default for RpcBootstrapConfig { + fn default() -> Self { + Self { + no_genesis_fetch: true, + no_snapshot_fetch: true, + no_untrusted_rpc: true, + max_genesis_archive_unpacked_size: MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, + no_check_vote_account: true, + } + } +} + +fn rpc_bootstrap( + node: &Node, + identity_keypair: &Arc, + ledger_path: &Path, + vote_account: &Pubkey, + authorized_voter_keypairs: &[Arc], + cluster_entrypoint: &ContactInfo, + validator_config: &mut ValidatorConfig, + bootstrap_config: RpcBootstrapConfig, +) { + verify_reachable_ports(&node, cluster_entrypoint); + + if bootstrap_config.no_genesis_fetch && bootstrap_config.no_snapshot_fetch { + return; + } + + let mut blacklisted_rpc_nodes = HashSet::new(); + let mut gossip = None; + loop { + if gossip.is_none() { + gossip = Some(start_gossip_node( + &identity_keypair, + &cluster_entrypoint.gossip, + &node.info.gossip, + node.sockets.gossip.try_clone().unwrap(), + validator_config.expected_shred_version, + validator_config.gossip_validators.clone(), + )); + } + + let (rpc_contact_info, snapshot_hash) = get_rpc_node( + &gossip.as_ref().unwrap().0, + &cluster_entrypoint.gossip, + &validator_config, + &mut blacklisted_rpc_nodes, + bootstrap_config.no_snapshot_fetch, + bootstrap_config.no_untrusted_rpc, + ); + info!( + "Using RPC service from node {}: {:?}", + rpc_contact_info.id, rpc_contact_info.rpc + ); + let rpc_client = RpcClient::new_socket(rpc_contact_info.rpc); + + let result = match rpc_client.get_version() { + Ok(rpc_version) => { + info!("RPC node version: {}", rpc_version.solana_core); + Ok(()) + } + Err(err) => Err(format!("Failed to get RPC node version: {}", err)), + } + .and_then(|_| { + let genesis_hash = download_then_check_genesis_hash( + &rpc_contact_info.rpc, + &ledger_path, + validator_config.expected_genesis_hash, + bootstrap_config.max_genesis_archive_unpacked_size, + bootstrap_config.no_genesis_fetch, + ); + + if let Ok(genesis_hash) = genesis_hash { + if validator_config.expected_genesis_hash.is_none() { + info!("Expected genesis hash set to {}", genesis_hash); + validator_config.expected_genesis_hash = Some(genesis_hash); + } + } + + if let Some(expected_genesis_hash) = validator_config.expected_genesis_hash { + // Sanity check that the RPC node is using the expected genesis hash before + // downloading a snapshot from it + let rpc_genesis_hash = rpc_client + .get_genesis_hash() + .map_err(|err| format!("Failed to get genesis hash: {}", err))?; + + if expected_genesis_hash != rpc_genesis_hash { + return Err(format!( + "Genesis hash mismatch: expected {} but RPC node genesis hash is {}", + expected_genesis_hash, rpc_genesis_hash + )); + } + } + + if let Some(snapshot_hash) = snapshot_hash { + rpc_client + .get_slot_with_commitment(CommitmentConfig::root()) + .map_err(|err| format!("Failed to get RPC node slot: {}", err)) + .and_then(|slot| { + info!("RPC node root slot: {}", slot); + let (_cluster_info, gossip_exit_flag, gossip_service) = + gossip.take().unwrap(); + gossip_exit_flag.store(true, Ordering::Relaxed); + let ret = + download_snapshot(&rpc_contact_info.rpc, &ledger_path, snapshot_hash); + gossip_service.join().unwrap(); + ret + }) + } else { + Ok(()) + } + }) + .map(|_| { + if !validator_config.voting_disabled && !bootstrap_config.no_check_vote_account { + check_vote_account( + &rpc_client, + &identity_keypair.pubkey(), + &vote_account, + &authorized_voter_keypairs + .iter() + .map(|k| k.pubkey()) + .collect::>(), + ) + .unwrap_or_else(|err| { + // Consider failures here to be more likely due to user error (eg, + // incorrect `solana-validator` command-line arguments) rather than the + // RPC node failing. + // + // Power users can always use the `--no-check-vote-account` option to + // bypass this check entirely + error!("{}", err); + exit(1); + }); + } + }); + + if result.is_ok() { + break; + } + warn!("{}", result.unwrap_err()); + + if let Some(ref trusted_validators) = validator_config.trusted_validators { + if trusted_validators.contains(&rpc_contact_info.id) { + continue; // Never blacklist a trusted node + } + } + + info!( + "Excluding {} as a future RPC candidate", + rpc_contact_info.id + ); + blacklisted_rpc_nodes.insert(rpc_contact_info.id); + } + if let Some((_cluster_info, gossip_exit_flag, gossip_service)) = gossip.take() { + gossip_exit_flag.store(true, Ordering::Relaxed); + gossip_service.join().unwrap(); + } +} + +fn create_validator( + node: Node, + identity_keypair: &Arc, + ledger_path: &Path, + vote_account: &Pubkey, + authorized_voter_keypairs: Vec>, + cluster_entrypoint: Option, + mut validator_config: ValidatorConfig, + rpc_bootstrap_config: RpcBootstrapConfig, +) -> Validator { + if validator_config.cuda { + solana_perf::perf_libs::init_cuda(); + enable_recycler_warming(); + } + solana_ledger::entry::init_poh(); + + if let Some(ref cluster_entrypoint) = cluster_entrypoint { + rpc_bootstrap( + &node, + &identity_keypair, + &ledger_path, + &vote_account, + &authorized_voter_keypairs, + cluster_entrypoint, + &mut validator_config, + rpc_bootstrap_config, + ); + } + + Validator::new( + node, + &identity_keypair, + &ledger_path, + &vote_account, + authorized_voter_keypairs, + cluster_entrypoint.as_ref(), + &validator_config, + ) +} + pub fn main() { let default_dynamic_port_range = &format!("{}-{}", VALIDATOR_PORT_RANGE.0, VALIDATOR_PORT_RANGE.1); @@ -964,11 +1227,19 @@ pub fn main() { let ledger_path = PathBuf::from(matches.value_of("ledger_path").unwrap()); let init_complete_file = matches.value_of("init_complete_file"); - let skip_poh_verify = matches.is_present("skip_poh_verify"); - let cuda = matches.is_present("cuda"); - let no_genesis_fetch = matches.is_present("no_genesis_fetch"); - let no_snapshot_fetch = matches.is_present("no_snapshot_fetch"); - let no_check_vote_account = matches.is_present("no_check_vote_account"); + + let rpc_bootstrap_config = RpcBootstrapConfig { + no_genesis_fetch: matches.is_present("no_genesis_fetch"), + no_snapshot_fetch: matches.is_present("no_snapshot_fetch"), + no_check_vote_account: matches.is_present("no_check_vote_account"), + no_untrusted_rpc: matches.is_present("no_untrusted_rpc"), + max_genesis_archive_unpacked_size: value_t_or_exit!( + matches, + "max_genesis_archive_unpacked_size", + u64 + ), + }; + let private_rpc = matches.is_present("private_rpc"); let no_rocksdb_compaction = matches.is_present("no_rocksdb_compaction"); let wal_recovery_mode = matches @@ -982,7 +1253,6 @@ pub fn main() { exit(1); }); - let no_untrusted_rpc = matches.is_present("no_untrusted_rpc"); let trusted_validators = validators_set( &identity_keypair.pubkey(), &matches, @@ -1015,6 +1285,7 @@ pub fn main() { let mut validator_config = ValidatorConfig { dev_halt_at_slot: value_t!(matches, "dev_halt_at_slot", Slot).ok(), + cuda: matches.is_present("cuda"), expected_genesis_hash: matches .value_of("expected_genesis_hash") .map(|s| Hash::from_str(&s).unwrap()), @@ -1055,6 +1326,7 @@ pub fn main() { frozen_accounts: values_t!(matches, "frozen_accounts", Pubkey).unwrap_or_default(), no_rocksdb_compaction, wal_recovery_mode, + poh_verify: !matches.is_present("skip_poh_verify"), ..ValidatorConfig::default() }; @@ -1174,6 +1446,13 @@ pub fn main() { warn!("--vote-signer-address ignored"); } + let entrypoint_addr = matches.value_of("entrypoint").map(|entrypoint| { + solana_net_utils::parse_host_port(entrypoint).unwrap_or_else(|e| { + eprintln!("failed to parse entrypoint address: {}", e); + exit(1); + }) + }); + let logfile = { let logfile = matches .value_of("logfile") @@ -1194,40 +1473,14 @@ pub fn main() { env::set_var("RUST_BACKTRACE", "1") } - info!("{} {}", crate_name!(), solana_version::version!()); - info!("Starting validator with: {:#?}", std::env::args_os()); - - solana_metrics::set_host_id(identity_keypair.pubkey().to_string()); - solana_metrics::set_panic_hook("validator"); - - if cuda { - solana_perf::perf_libs::init_cuda(); - enable_recycler_warming(); - } - - solana_ledger::entry::init_poh(); - - let entrypoint_addr = matches.value_of("entrypoint").map(|entrypoint| { - solana_net_utils::parse_host_port(entrypoint).unwrap_or_else(|e| { - eprintln!("failed to parse entrypoint address: {}", e); + let gossip_host = if let Some(entrypoint_addr) = entrypoint_addr { + solana_net_utils::get_public_ip_addr(&entrypoint_addr).unwrap_or_else(|err| { + eprintln!( + "Failed to contact cluster entrypoint {}: {}", + entrypoint_addr, err + ); exit(1); }) - }); - - let gossip_host = if let Some(entrypoint_addr) = entrypoint_addr { - let ip_addr = - solana_net_utils::get_public_ip_addr(&entrypoint_addr).unwrap_or_else(|err| { - eprintln!( - "Failed to contact cluster entrypoint {}: {}", - entrypoint_addr, err - ); - exit(1); - }); - info!( - "{} reports the IP address for this machine as {}", - entrypoint_addr, ip_addr - ); - ip_addr } else { solana_net_utils::parse_host(matches.value_of("gossip_host").unwrap_or("127.0.0.1")) .unwrap_or_else(|err| { @@ -1247,8 +1500,6 @@ pub fn main() { ) }), ); - let max_genesis_archive_unpacked_size = - value_t_or_exit!(matches, "max_genesis_archive_unpacked_size", u64); let cluster_entrypoint = entrypoint_addr .as_ref() @@ -1271,6 +1522,9 @@ pub fn main() { node.info.tvu = any; node.info.tvu_forwards = any; node.info.serve_repair = any; + + // A node in this configuration shouldn't be an entrypoint to other nodes + node.sockets.ip_echo = None; } if !private_rpc { @@ -1281,211 +1535,21 @@ pub fn main() { } } - if let Some(ref cluster_entrypoint) = cluster_entrypoint { - let mut udp_sockets = vec![&node.sockets.gossip, &node.sockets.repair]; + info!("{} {}", crate_name!(), solana_version::version!()); + info!("Starting validator with: {:#?}", std::env::args_os()); - if ContactInfo::is_valid_address(&node.info.serve_repair) { - udp_sockets.push(&node.sockets.serve_repair); - } - if ContactInfo::is_valid_address(&node.info.tpu) { - udp_sockets.extend(node.sockets.tpu.iter()); - } - if ContactInfo::is_valid_address(&node.info.tpu_forwards) { - udp_sockets.extend(node.sockets.tpu_forwards.iter()); - } - if ContactInfo::is_valid_address(&node.info.tvu) { - udp_sockets.extend(node.sockets.tvu.iter()); - udp_sockets.extend(node.sockets.broadcast.iter()); - udp_sockets.extend(node.sockets.retransmit_sockets.iter()); - } - if ContactInfo::is_valid_address(&node.info.tvu_forwards) { - udp_sockets.extend(node.sockets.tvu_forwards.iter()); - } + solana_metrics::set_host_id(identity_keypair.pubkey().to_string()); + solana_metrics::set_panic_hook("validator"); - let mut tcp_listeners = vec![]; - if let Some((rpc_addr, rpc_pubsub_addr, rpc_banks_addr)) = validator_config.rpc_addrs { - for (purpose, addr) in &[ - ("RPC", rpc_addr), - ("RPC pubsub", rpc_pubsub_addr), - ("RPC banks", rpc_banks_addr), - ] { - if !private_rpc && ContactInfo::is_valid_address(&addr) { - tcp_listeners.push(( - addr.port(), - TcpListener::bind(addr).unwrap_or_else(|err| { - error!( - "Unable to bind to tcp/{} for {}: {}", - addr.port(), - purpose, - err - ); - exit(1); - }), - )); - } - } - } - - if !restricted_repair_only_mode { - if let Some(ip_echo) = &node.sockets.ip_echo { - let ip_echo = ip_echo.try_clone().expect("unable to clone tcp_listener"); - tcp_listeners.push((ip_echo.local_addr().unwrap().port(), ip_echo)); - } - } - - if !solana_net_utils::verify_reachable_ports( - &cluster_entrypoint.gossip, - tcp_listeners, - &udp_sockets, - ) { - exit(1); - } - if !no_genesis_fetch || !no_snapshot_fetch { - let mut blacklisted_rpc_nodes = HashSet::new(); - let mut gossip = None; - loop { - if gossip.is_none() { - gossip = Some(start_gossip_node( - &identity_keypair, - &cluster_entrypoint.gossip, - &node.info.gossip, - node.sockets.gossip.try_clone().unwrap(), - validator_config.expected_shred_version, - validator_config.gossip_validators.clone(), - )); - } - - let (rpc_contact_info, snapshot_hash) = get_rpc_node( - &gossip.as_ref().unwrap().0, - &cluster_entrypoint.gossip, - &validator_config, - &mut blacklisted_rpc_nodes, - no_snapshot_fetch, - no_untrusted_rpc, - ); - info!( - "Using RPC service from node {}: {:?}", - rpc_contact_info.id, rpc_contact_info.rpc - ); - let rpc_client = RpcClient::new_socket(rpc_contact_info.rpc); - - let result = match rpc_client.get_version() { - Ok(rpc_version) => { - info!("RPC node version: {}", rpc_version.solana_core); - Ok(()) - } - Err(err) => Err(format!("Failed to get RPC node version: {}", err)), - } - .and_then(|_| { - let genesis_hash = download_then_check_genesis_hash( - &rpc_contact_info.rpc, - &ledger_path, - validator_config.expected_genesis_hash, - max_genesis_archive_unpacked_size, - no_genesis_fetch, - ); - - if let Ok(genesis_hash) = genesis_hash { - if validator_config.expected_genesis_hash.is_none() { - info!("Expected genesis hash set to {}", genesis_hash); - validator_config.expected_genesis_hash = Some(genesis_hash); - } - } - genesis_hash - }) - .and_then(|_| { - if let Some(expected_genesis_hash) = validator_config.expected_genesis_hash { - // Sanity check that the RPC node is using the expected genesis hash before - // downloading a snapshot from it - let rpc_genesis_hash = rpc_client - .get_genesis_hash() - .map_err(|err| format!("Failed to get genesis hash: {}", err))?; - - if expected_genesis_hash != rpc_genesis_hash { - return Err(format!("Genesis hash mismatch: expected {} but RPC node genesis hash is {}", - expected_genesis_hash, rpc_genesis_hash)); - } - } - Ok(()) - }) - .and_then(|_| { - if let Some(snapshot_hash) = snapshot_hash { - rpc_client.get_slot_with_commitment(CommitmentConfig::root()) - .map_err(|err| format!("Failed to get RPC node slot: {}", err)) - .and_then(|slot| { - info!("RPC node root slot: {}", slot); - let (_cluster_info, gossip_exit_flag, gossip_service) = gossip.take().unwrap(); - gossip_exit_flag.store(true, Ordering::Relaxed); - let ret = download_snapshot(&rpc_contact_info.rpc, &ledger_path, snapshot_hash); - gossip_service.join().unwrap(); - ret - }) - } else { - Ok(()) - } - }) - .map(|_| { - if !validator_config.voting_disabled && !no_check_vote_account { - check_vote_account( - &rpc_client, - &identity_keypair.pubkey(), - &vote_account, - &authorized_voter_keypairs.iter().map(|k| k.pubkey()).collect::>(), - - ).unwrap_or_else(|err| { - // Consider failures here to be more likely due to user error (eg, - // incorrect `solana-validator` command-line arguments) rather than the - // RPC node failing. - // - // Power users can always use the `--no-check-vote-account` option to - // bypass this check entirely - error!("{}", err); - exit(1); - }); - } - }); - - if result.is_ok() { - break; - } - warn!("{}", result.unwrap_err()); - - if let Some(ref trusted_validators) = validator_config.trusted_validators { - if trusted_validators.contains(&rpc_contact_info.id) { - continue; // Never blacklist a trusted node - } - } - - info!( - "Excluding {} as a future RPC candidate", - rpc_contact_info.id - ); - blacklisted_rpc_nodes.insert(rpc_contact_info.id); - } - if let Some((_cluster_info, gossip_exit_flag, gossip_service)) = gossip.take() { - gossip_exit_flag.store(true, Ordering::Relaxed); - gossip_service.join().unwrap(); - } - } - } - - if !ledger_path.is_dir() { - error!( - "ledger directory does not exist or is not accessible: {:?}", - ledger_path - ); - exit(1); - } - - let validator = Validator::new( + let validator = create_validator( node, &identity_keypair, &ledger_path, &vote_account, authorized_voter_keypairs, - cluster_entrypoint.as_ref(), - !skip_poh_verify, - &validator_config, + cluster_entrypoint, + validator_config, + rpc_bootstrap_config, ); if let Some(filename) = init_complete_file {