diff --git a/Cargo.lock b/Cargo.lock index c81221eb1..7e2dc3bbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4014,6 +4014,7 @@ dependencies = [ "solana-metrics", "solana-net-utils", "solana-perf", + "solana-program-test", "solana-rayon-threadlimit", "solana-runtime", "solana-sdk", @@ -5071,12 +5072,14 @@ dependencies = [ "chrono", "clap", "console", + "indicatif", "libc", "log 0.4.8", "rand 0.7.3", "serde_json", "signal-hook", "solana-clap-utils", + "solana-cli-config", "solana-client", "solana-core", "solana-download-utils", @@ -5090,7 +5093,7 @@ dependencies = [ "solana-sdk", "solana-version", "solana-vote-program", - "solana-vote-signer", + "symlink", ] [[package]] diff --git a/cli/tests/deploy.rs b/cli/tests/deploy.rs index 360a237df..e6923bcbe 100644 --- a/cli/tests/deploy.rs +++ b/cli/tests/deploy.rs @@ -21,10 +21,11 @@ fn test_cli_deploy_program() { pathbuf.push("noop"); pathbuf.set_extension("so"); - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); diff --git a/cli/tests/nonce.rs b/cli/tests/nonce.rs index e79c2f68c..b974e921b 100644 --- a/cli/tests/nonce.rs +++ b/cli/tests/nonce.rs @@ -22,13 +22,21 @@ use std::sync::mpsc::channel; #[test] fn test_nonce() { - full_battery_tests(TestValidator::with_no_fees(), None, false); + let mint_keypair = Keypair::new(); + full_battery_tests( + TestValidator::with_no_fees(mint_keypair.pubkey()), + mint_keypair, + None, + false, + ); } #[test] fn test_nonce_with_seed() { + let mint_keypair = Keypair::new(); full_battery_tests( - TestValidator::with_no_fees(), + TestValidator::with_no_fees(mint_keypair.pubkey()), + mint_keypair, Some(String::from("seed")), false, ); @@ -36,16 +44,23 @@ fn test_nonce_with_seed() { #[test] fn test_nonce_with_authority() { - full_battery_tests(TestValidator::with_no_fees(), None, true); + let mint_keypair = Keypair::new(); + full_battery_tests( + TestValidator::with_no_fees(mint_keypair.pubkey()), + mint_keypair, + None, + true, + ); } fn full_battery_tests( test_validator: TestValidator, + mint_keypair: Keypair, seed: Option, use_nonce_authority: bool, ) { let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -202,10 +217,11 @@ fn full_battery_tests( #[test] fn test_create_account_with_seed() { solana_logger::setup(); - let test_validator = TestValidator::with_custom_fees(1); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap(); diff --git a/cli/tests/request_airdrop.rs b/cli/tests/request_airdrop.rs index fe460bbee..78211a967 100644 --- a/cli/tests/request_airdrop.rs +++ b/cli/tests/request_airdrop.rs @@ -2,15 +2,19 @@ use solana_cli::cli::{process_command, CliCommand, CliConfig}; use solana_client::rpc_client::RpcClient; use solana_core::test_validator::TestValidator; use solana_faucet::faucet::run_local_faucet; -use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; +use solana_sdk::{ + commitment_config::CommitmentConfig, + signature::{Keypair, Signer}, +}; use std::sync::mpsc::channel; #[test] fn test_cli_request_airdrop() { - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let mut bob_config = CliConfig::recent_for_tests(); diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index a3d11b1b0..de7529a18 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -26,9 +26,10 @@ use std::sync::mpsc::channel; #[test] fn test_stake_delegation_force() { - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -115,9 +116,10 @@ fn test_stake_delegation_force() { fn test_seed_stake_delegation_and_deactivation() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -195,9 +197,10 @@ fn test_seed_stake_delegation_and_deactivation() { fn test_stake_delegation_and_deactivation() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -271,9 +274,10 @@ fn test_stake_delegation_and_deactivation() { fn test_offline_stake_delegation_and_deactivation() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -404,9 +408,10 @@ fn test_offline_stake_delegation_and_deactivation() { fn test_nonced_stake_delegation_and_deactivation() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -519,9 +524,10 @@ fn test_nonced_stake_delegation_and_deactivation() { fn test_stake_authorize() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -792,9 +798,10 @@ fn test_stake_authorize_with_fee_payer() { solana_logger::setup(); const SIG_FEE: u64 = 42; - let test_validator = TestValidator::with_custom_fees(SIG_FEE); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), SIG_FEE); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -917,9 +924,10 @@ fn test_stake_authorize_with_fee_payer() { fn test_stake_split() { solana_logger::setup(); - let test_validator = TestValidator::with_custom_fees(1); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -1061,9 +1069,10 @@ fn test_stake_split() { fn test_stake_set_lockup() { solana_logger::setup(); - let test_validator = TestValidator::with_custom_fees(1); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -1323,9 +1332,10 @@ fn test_stake_set_lockup() { fn test_offline_nonced_create_stake_account_and_withdraw() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); diff --git a/cli/tests/transfer.rs b/cli/tests/transfer.rs index 6d5141763..d5b621975 100644 --- a/cli/tests/transfer.rs +++ b/cli/tests/transfer.rs @@ -22,10 +22,11 @@ use std::sync::mpsc::channel; #[test] fn test_transfer() { solana_logger::setup(); - let test_validator = TestValidator::with_custom_fees(1); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); @@ -243,10 +244,11 @@ fn test_transfer() { #[test] fn test_transfer_multisession_signing() { solana_logger::setup(); - let test_validator = TestValidator::with_custom_fees(1); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let to_pubkey = Pubkey::new(&[1u8; 32]); @@ -363,10 +365,11 @@ fn test_transfer_multisession_signing() { #[test] fn test_transfer_all() { solana_logger::setup(); - let test_validator = TestValidator::with_custom_fees(1); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); diff --git a/cli/tests/vote.rs b/cli/tests/vote.rs index 30bd9bf7f..e7418724e 100644 --- a/cli/tests/vote.rs +++ b/cli/tests/vote.rs @@ -19,9 +19,10 @@ use std::sync::mpsc::channel; #[test] fn test_vote_authorize_and_withdraw() { - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let (sender, receiver) = channel(); - run_local_faucet(test_validator.mint_keypair(), sender, None); + run_local_faucet(mint_keypair, sender, None); let faucet_addr = receiver.recv().unwrap(); let rpc_client = RpcClient::new(test_validator.rpc_url()); diff --git a/core/Cargo.toml b/core/Cargo.toml index b80b34a79..470f2fcd2 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -58,6 +58,7 @@ solana-metrics = { path = "../metrics", version = "1.5.0" } solana-measure = { path = "../measure", version = "1.5.0" } solana-net-utils = { path = "../net-utils", version = "1.5.0" } solana-perf = { path = "../perf", version = "1.5.0" } +solana-program-test = { path = "../program-test", version = "1.5.0" } solana-runtime = { path = "../runtime", version = "1.5.0" } solana-sdk = { path = "../sdk", version = "1.5.0" } solana-frozen-abi = { path = "../frozen-abi", version = "1.5.0" } diff --git a/core/src/test_validator.rs b/core/src/test_validator.rs index dc107038a..9bd99bc90 100644 --- a/core/src/test_validator.rs +++ b/core/src/test_validator.rs @@ -2,188 +2,279 @@ use { crate::{ cluster_info::Node, gossip_service::discover_cluster, + rpc::JsonRpcConfig, validator::{Validator, ValidatorConfig}, }, - solana_ledger::create_new_tmp_ledger, + solana_ledger::{blockstore::create_new_ledger, create_new_tmp_ledger}, + solana_runtime::{ + bank_forks::{CompressionType, SnapshotConfig, SnapshotVersion}, + genesis_utils::create_genesis_config_with_leader_ex, + hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, + }, solana_sdk::{ fee_calculator::FeeRateGovernor, - hash::Hash, native_token::sol_to_lamports, pubkey::Pubkey, rent::Rent, - signature::{Keypair, Signer}, + signature::{read_keypair_file, write_keypair_file, Keypair, Signer}, + }, + std::{ + fs::remove_dir_all, + net::SocketAddr, + path::{Path, PathBuf}, + sync::Arc, }, - std::{fs::remove_dir_all, net::SocketAddr, path::PathBuf, sync::Arc}, }; -pub struct TestValidatorConfig { +pub struct TestValidatorGenesisConfig { pub fee_rate_governor: FeeRateGovernor, - pub mint_lamports: u64, + pub mint_address: Pubkey, pub rent: Rent, - pub validator_identity_keypair: Keypair, - pub validator_identity_lamports: u64, - pub validator_stake_lamports: u64, } -impl Default for TestValidatorConfig { - fn default() -> Self { - Self { - fee_rate_governor: FeeRateGovernor::default(), - mint_lamports: sol_to_lamports(500_000_000.), - rent: Rent::default(), - validator_identity_keypair: Keypair::new(), - validator_identity_lamports: sol_to_lamports(500.), - validator_stake_lamports: sol_to_lamports(1.), - } - } +#[derive(Default)] +pub struct TestValidatorStartConfig { + pub rpc_config: JsonRpcConfig, + pub rpc_ports: Option<(u16, u16)>, // (JsonRpc, JsonRpcPubSub), None == random ports } pub struct TestValidator { - validator: Validator, ledger_path: PathBuf, - preserve_ledger: bool, - - genesis_hash: Hash, - mint_keypair: Keypair, - vote_account_address: Pubkey, - - tpu: SocketAddr, - rpc_url: String, rpc_pubsub_url: String, -} - -impl Default for TestValidator { - fn default() -> Self { - Self::new(TestValidatorConfig::default()) - } + rpc_url: String, + tpu: SocketAddr, + gossip: SocketAddr, + validator: Validator, + vote_account_address: Pubkey, } impl TestValidator { - pub fn with_no_fees() -> Self { - Self::new(TestValidatorConfig { - fee_rate_governor: FeeRateGovernor::new(0, 0), - rent: Rent { - lamports_per_byte_year: 1, - exemption_threshold: 1.0, - ..Rent::default() + /// The default test validator is intended to be generically suitable for unit testing. + /// + /// It uses a unique temporary ledger that is deleted on `close` and randomly assigned ports. + /// All test tokens will be minted into `mint_address` + /// + /// This function panics on initialization failure. + pub fn new(mint_address: Pubkey) -> Self { + let ledger_path = Self::initialize_ledger( + None, + TestValidatorGenesisConfig { + fee_rate_governor: FeeRateGovernor::default(), + mint_address, + rent: Rent::default(), }, - ..TestValidatorConfig::default() - }) + ) + .unwrap(); + Self::start(&ledger_path, TestValidatorStartConfig::default()).unwrap() } - pub fn with_custom_fees(target_lamports_per_signature: u64) -> Self { - Self::new(TestValidatorConfig { - fee_rate_governor: FeeRateGovernor::new(target_lamports_per_signature, 0), - rent: Rent { - lamports_per_byte_year: 1, - exemption_threshold: 1.0, - ..Rent::default() + /// Create a `TestValidator` with no transaction fees and minimal rent. + /// + /// This function panics on initialization failure. + pub fn with_no_fees(mint_address: Pubkey) -> Self { + let ledger_path = Self::initialize_ledger( + None, + TestValidatorGenesisConfig { + fee_rate_governor: FeeRateGovernor::new(0, 0), + mint_address, + rent: Rent { + lamports_per_byte_year: 1, + exemption_threshold: 1.0, + ..Rent::default() + }, }, - ..TestValidatorConfig::default() - }) + ) + .unwrap(); + Self::start(&ledger_path, TestValidatorStartConfig::default()).unwrap() } - pub fn new(config: TestValidatorConfig) -> Self { - use solana_ledger::genesis_utils::{ - create_genesis_config_with_leader_ex, GenesisConfigInfo, - }; + /// Create a `TestValidator` with custom transaction fees and minimal rent. + /// + /// This function panics on initialization failure. + pub fn with_custom_fees(mint_address: Pubkey, target_lamports_per_signature: u64) -> Self { + let ledger_path = Self::initialize_ledger( + None, + TestValidatorGenesisConfig { + fee_rate_governor: FeeRateGovernor::new(target_lamports_per_signature, 0), + mint_address, + rent: Rent { + lamports_per_byte_year: 1, + exemption_threshold: 1.0, + ..Rent::default() + }, + }, + ) + .unwrap(); + Self::start(&ledger_path, TestValidatorStartConfig::default()).unwrap() + } - let TestValidatorConfig { + /// Initialize the test validator's ledger directory + /// + /// If `ledger_path` is `None`, a temporary ledger will be created. Otherwise the ledger will + /// be initialized in the provided directory. + /// + /// Returns the path to the ledger directory. + pub fn initialize_ledger( + ledger_path: Option<&Path>, + config: TestValidatorGenesisConfig, + ) -> Result> { + let TestValidatorGenesisConfig { fee_rate_governor, - mint_lamports, + mint_address, rent, - validator_identity_keypair, - validator_identity_lamports, - validator_stake_lamports, } = config; - let validator_identity_keypair = Arc::new(validator_identity_keypair); - let node = Node::new_localhost_with_pubkey(&validator_identity_keypair.pubkey()); + let validator_identity_keypair = Keypair::new(); + let validator_vote_account = Keypair::new(); + let validator_stake_account = Keypair::new(); + let validator_identity_lamports = sol_to_lamports(500.); + let validator_stake_lamports = sol_to_lamports(1_000_000.); + let mint_lamports = sol_to_lamports(500_000_000.); - let GenesisConfigInfo { - mut genesis_config, - mint_keypair, - voting_keypair: vote_account_keypair, - } = create_genesis_config_with_leader_ex( + let initial_accounts = solana_program_test::programs::spl_programs(&rent); + let genesis_config = create_genesis_config_with_leader_ex( mint_lamports, - &node.info.id, - &Keypair::new(), - &Keypair::new().pubkey(), + &mint_address, + &validator_identity_keypair.pubkey(), + &validator_vote_account.pubkey(), + &validator_stake_account.pubkey(), validator_stake_lamports, validator_identity_lamports, + fee_rate_governor, + rent, solana_sdk::genesis_config::ClusterType::Development, + initial_accounts, ); - genesis_config.rent = rent; - genesis_config.fee_rate_governor = fee_rate_governor; - - let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_config); - - let config = ValidatorConfig { - rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub)), - ..ValidatorConfig::default() + let ledger_path = match ledger_path { + None => create_new_tmp_ledger!(&genesis_config).0, + Some(ledger_path) => { + let _ = create_new_ledger( + ledger_path, + &genesis_config, + MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, + solana_ledger::blockstore_db::AccessType::PrimaryOnly, + ) + .map_err(|err| { + format!( + "Failed to create ledger at {}: {}", + ledger_path.display(), + err + ) + })?; + ledger_path.to_path_buf() + } }; - let vote_account_address = vote_account_keypair.pubkey(); + write_keypair_file( + &validator_identity_keypair, + ledger_path.join("validator-keypair.json").to_str().unwrap(), + )?; + write_keypair_file( + &validator_vote_account, + ledger_path + .join("vote-account-keypair.json") + .to_str() + .unwrap(), + )?; + + Ok(ledger_path) + } + + /// Starts a TestValidator at the provided ledger directory + pub fn start( + ledger_path: &Path, + config: TestValidatorStartConfig, + ) -> Result> { + let validator_identity_keypair = + read_keypair_file(ledger_path.join("validator-keypair.json").to_str().unwrap())?; + let validator_vote_account = read_keypair_file( + ledger_path + .join("vote-account-keypair.json") + .to_str() + .unwrap(), + )?; + + let mut node = Node::new_localhost_with_pubkey(&validator_identity_keypair.pubkey()); + if let Some((rpc, rpc_pubsub)) = config.rpc_ports { + node.info.rpc = SocketAddr::new(node.info.gossip.ip(), rpc); + node.info.rpc_pubsub = SocketAddr::new(node.info.gossip.ip(), rpc_pubsub); + } + + let vote_account_address = validator_vote_account.pubkey(); let rpc_url = format!("http://{}:{}", node.info.rpc.ip(), node.info.rpc.port()); let rpc_pubsub_url = format!("ws://{}/", node.info.rpc_pubsub); let tpu = node.info.tpu; let gossip = node.info.gossip; + let validator_config = ValidatorConfig { + rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub)), + rpc_config: config.rpc_config, + accounts_hash_interval_slots: 100, + account_paths: vec![ledger_path.join("accounts")], + poh_verify: false, // Skip PoH verification of ledger on startup for speed + snapshot_config: Some(SnapshotConfig { + snapshot_interval_slots: 100, + snapshot_path: ledger_path.join("snapshot"), + snapshot_package_output_path: ledger_path.to_path_buf(), + compression: CompressionType::NoCompression, + snapshot_version: SnapshotVersion::default(), + }), + ..ValidatorConfig::default() + }; + let validator = Validator::new( node, - &validator_identity_keypair, + &Arc::new(validator_identity_keypair), &ledger_path, - &vote_account_keypair.pubkey(), - vec![Arc::new(vote_account_keypair)], + &validator_vote_account.pubkey(), + vec![Arc::new(validator_vote_account)], None, - &config, + &validator_config, ); // Needed to avoid panics in `solana-responder-gossip` in tests that create a number of // test validators concurrently... discover_cluster(&gossip, 1).expect("TestValidator startup failed"); - TestValidator { + Ok(TestValidator { + ledger_path: ledger_path.to_path_buf(), + rpc_pubsub_url, + rpc_url, + gossip, + tpu, validator, vote_account_address, - mint_keypair, - ledger_path, - genesis_hash: blockhash, - tpu, - rpc_url, - rpc_pubsub_url, - preserve_ledger: false, - } + }) } + /// Stop the test validator and delete its ledger directory pub fn close(self) { self.validator.close().unwrap(); - if !self.preserve_ledger { - remove_dir_all(&self.ledger_path).unwrap(); - } + remove_dir_all(&self.ledger_path).unwrap(); } + /// Return the test validator's TPU address pub fn tpu(&self) -> &SocketAddr { &self.tpu } - pub fn mint_keypair(&self) -> Keypair { - Keypair::from_bytes(&self.mint_keypair.to_bytes()).unwrap() + /// Return the test validator's Gossip address + pub fn gossip(&self) -> &SocketAddr { + &self.gossip } + /// Return the test validator's JSON RPC URL pub fn rpc_url(&self) -> String { self.rpc_url.clone() } + /// Return the test validator's JSON RPC PubSub URL pub fn rpc_pubsub_url(&self) -> String { self.rpc_pubsub_url.clone() } - pub fn genesis_hash(&self) -> Hash { - self.genesis_hash - } - + /// Return the vote account address of the validator pub fn vote_account_address(&self) -> Pubkey { self.vote_account_address } diff --git a/core/src/validator.rs b/core/src/validator.rs index 464f99bea..9877ca8b0 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -490,6 +490,13 @@ impl Validator { let (snapshot_packager_service, snapshot_config_and_package_sender) = if let Some(snapshot_config) = config.snapshot_config.clone() { + if is_snapshot_config_invalid( + snapshot_config.snapshot_interval_slots, + config.accounts_hash_interval_slots, + ) { + error!("Snapshot config is invalid"); + } + // Start a snapshot packaging service let (sender, receiver) = channel(); let snapshot_packager_service = @@ -1218,6 +1225,15 @@ fn cleanup_accounts_path(account_path: &std::path::Path) { } } +pub fn is_snapshot_config_invalid( + snapshot_interval_slots: u64, + accounts_hash_interval_slots: u64, +) -> bool { + snapshot_interval_slots != 0 + && (snapshot_interval_slots < accounts_hash_interval_slots + || snapshot_interval_slots % accounts_hash_interval_slots != 0) +} + #[cfg(test)] mod tests { use super::*; @@ -1388,4 +1404,13 @@ mod tests { rpc_override_health_check )); } + + #[test] + fn test_interval_check() { + assert!(!is_snapshot_config_invalid(0, 100)); + assert!(is_snapshot_config_invalid(1, 100)); + assert!(is_snapshot_config_invalid(230, 100)); + assert!(!is_snapshot_config_invalid(500, 100)); + assert!(!is_snapshot_config_invalid(5, 5)); + } } diff --git a/core/tests/client.rs b/core/tests/client.rs index 94d26bd23..1487f9b66 100644 --- a/core/tests/client.rs +++ b/core/tests/client.rs @@ -12,8 +12,11 @@ use solana_runtime::{ genesis_utils::{create_genesis_config, GenesisConfigInfo}, }; use solana_sdk::{ - commitment_config::CommitmentConfig, native_token::sol_to_lamports, rpc_port, - signature::Signer, system_transaction, + commitment_config::CommitmentConfig, + native_token::sol_to_lamports, + rpc_port, + signature::{Keypair, Signer}, + system_transaction, }; use std::{ net::{IpAddr, SocketAddr}, @@ -30,8 +33,8 @@ use systemstat::Ipv4Addr; fn test_rpc_client() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_no_fees(alice.pubkey()); let bob_pubkey = solana_sdk::pubkey::new_rand(); diff --git a/core/tests/rpc.rs b/core/tests/rpc.rs index 6104edca2..6a2e4ef34 100644 --- a/core/tests/rpc.rs +++ b/core/tests/rpc.rs @@ -14,7 +14,10 @@ use solana_client::{ }; use solana_core::{rpc_pubsub::gen_client::Client as PubsubClient, test_validator::TestValidator}; use solana_sdk::{ - commitment_config::CommitmentConfig, hash::Hash, signature::Signer, system_transaction, + commitment_config::CommitmentConfig, + hash::Hash, + signature::{Keypair, Signer}, + system_transaction, transaction::Transaction, }; use std::{ @@ -52,8 +55,8 @@ fn post_rpc(request: Value, rpc_url: &str) -> Value { fn test_rpc_send_tx() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_no_fees(alice.pubkey()); let rpc_url = test_validator.rpc_url(); let bob_pubkey = solana_sdk::pubkey::new_rand(); @@ -113,7 +116,8 @@ fn test_rpc_send_tx() { fn test_rpc_invalid_requests() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_no_fees(alice.pubkey()); let rpc_url = test_validator.rpc_url(); let bob_pubkey = solana_sdk::pubkey::new_rand(); @@ -145,12 +149,15 @@ fn test_rpc_invalid_requests() { fn test_rpc_subscriptions() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_no_fees(alice.pubkey()); let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); transactions_socket.connect(test_validator.tpu()).unwrap(); + let rpc_client = RpcClient::new(test_validator.rpc_url()); + let recent_blockhash = rpc_client.get_recent_blockhash().unwrap().0; + // Create transaction signatures to subscribe to let transactions: Vec = (0..1000) .map(|_| { @@ -158,7 +165,7 @@ fn test_rpc_subscriptions() { &alice, &solana_sdk::pubkey::new_rand(), 1, - test_validator.genesis_hash(), + recent_blockhash, ) }) .collect(); diff --git a/ledger/src/genesis_utils.rs b/ledger/src/genesis_utils.rs index 9547958d8..d31b453e9 100644 --- a/ledger/src/genesis_utils.rs +++ b/ledger/src/genesis_utils.rs @@ -1,6 +1,5 @@ pub use solana_runtime::genesis_utils::{ - bootstrap_validator_stake_lamports, create_genesis_config_with_leader, - create_genesis_config_with_leader_ex, GenesisConfigInfo, + bootstrap_validator_stake_lamports, create_genesis_config_with_leader, GenesisConfigInfo, }; // same as genesis_config::create_genesis_config, but with bootstrap_validator staking logic diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index f962ced0d..63a4e9fa7 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -39,6 +39,7 @@ use { // Export types so test clients can limit their solana crate dependencies pub use solana_banks_client::BanksClient; +pub mod programs; #[macro_use] extern crate solana_bpf_loader_program; @@ -358,24 +359,6 @@ fn read_file>(path: P) -> Vec { file_data } -mod spl_token { - solana_sdk::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); -} -mod spl_memo { - solana_sdk::declare_id!("Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"); -} -mod spl_associated_token_account { - solana_sdk::declare_id!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); -} -static SPL_PROGRAMS: &[(Pubkey, &[u8])] = &[ - (spl_token::ID, include_bytes!("programs/spl_token-2.0.6.so")), - (spl_memo::ID, include_bytes!("programs/spl_memo-1.0.0.so")), - ( - spl_associated_token_account::ID, - include_bytes!("programs/spl_associated-token-account-1.0.1.so"), - ), -]; - pub struct ProgramTest { accounts: Vec<(Pubkey, Account)>, builtins: Vec, @@ -614,17 +597,8 @@ impl ProgramTest { } // Add commonly-used SPL programs as a convenience to the user - for (program_id, elf) in SPL_PROGRAMS.iter() { - bank.store_account( - program_id, - &Account { - lamports: Rent::default().minimum_balance(elf.len()).min(1), - data: elf.to_vec(), - owner: solana_program::bpf_loader::id(), - executable: true, - rent_epoch: 0, - }, - ) + for (program_id, account) in programs::spl_programs(&Rent::default()).iter() { + bank.store_account(program_id, &account); } // User-supplied additional builtins diff --git a/program-test/src/programs.rs b/program-test/src/programs.rs new file mode 100644 index 000000000..3ec71fd65 --- /dev/null +++ b/program-test/src/programs.rs @@ -0,0 +1,38 @@ +use solana_sdk::{account::Account, pubkey::Pubkey, rent::Rent}; + +mod spl_token { + solana_sdk::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); +} +mod spl_memo { + solana_sdk::declare_id!("Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"); +} +mod spl_associated_token_account { + solana_sdk::declare_id!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); +} + +static SPL_PROGRAMS: &[(Pubkey, &[u8])] = &[ + (spl_token::ID, include_bytes!("programs/spl_token-2.0.6.so")), + (spl_memo::ID, include_bytes!("programs/spl_memo-1.0.0.so")), + ( + spl_associated_token_account::ID, + include_bytes!("programs/spl_associated-token-account-1.0.1.so"), + ), +]; + +pub fn spl_programs(rent: &Rent) -> Vec<(Pubkey, Account)> { + SPL_PROGRAMS + .iter() + .map(|(program_id, elf)| { + ( + *program_id, + Account { + lamports: rent.minimum_balance(elf.len()).min(1), + data: elf.to_vec(), + owner: solana_program::bpf_loader::id(), + executable: true, + rent_epoch: 0, + }, + ) + }) + .collect() +} diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index c35af7631..24c293532 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -78,16 +78,30 @@ pub fn create_genesis_config_with_vote_accounts_and_cluster_type( assert!(!voting_keypairs.is_empty()); assert_eq!(voting_keypairs.len(), stakes.len()); - let mut genesis_config_info = create_genesis_config_with_leader_ex( + let mint_keypair = Keypair::new(); + let voting_keypair = + Keypair::from_bytes(&voting_keypairs[0].borrow().vote_keypair.to_bytes()).unwrap(); + + let genesis_config = create_genesis_config_with_leader_ex( mint_lamports, + &mint_keypair.pubkey(), &voting_keypairs[0].borrow().node_keypair.pubkey(), - &voting_keypairs[0].borrow().vote_keypair, + &voting_keypairs[0].borrow().vote_keypair.pubkey(), &voting_keypairs[0].borrow().stake_keypair.pubkey(), stakes[0], VALIDATOR_LAMPORTS, + FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees + Rent::free(), // most tests don't expect rent cluster_type, + vec![], ); + let mut genesis_config_info = GenesisConfigInfo { + genesis_config, + voting_keypair, + mint_keypair, + }; + for (validator_voting_keypairs, stake) in voting_keypairs[1..].iter().zip(&stakes[1..]) { let node_pubkey = validator_voting_keypairs.borrow().node_keypair.pubkey(); let vote_pubkey = validator_voting_keypairs.borrow().vote_keypair.pubkey(); @@ -120,15 +134,28 @@ pub fn create_genesis_config_with_leader( validator_pubkey: &Pubkey, validator_stake_lamports: u64, ) -> GenesisConfigInfo { - create_genesis_config_with_leader_ex( + let mint_keypair = Keypair::new(); + let voting_keypair = Keypair::new(); + + let genesis_config = create_genesis_config_with_leader_ex( mint_lamports, + &mint_keypair.pubkey(), validator_pubkey, - &Keypair::new(), + &voting_keypair.pubkey(), &solana_sdk::pubkey::new_rand(), validator_stake_lamports, VALIDATOR_LAMPORTS, + FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees + Rent::free(), // most tests don't expect rent ClusterType::Development, - ) + vec![], + ); + + GenesisConfigInfo { + genesis_config, + voting_keypair, + mint_keypair, + } } pub fn activate_all_features(genesis_config: &mut GenesisConfig) { @@ -146,55 +173,48 @@ pub fn activate_all_features(genesis_config: &mut GenesisConfig) { } } +#[allow(clippy::too_many_arguments)] pub fn create_genesis_config_with_leader_ex( mint_lamports: u64, + mint_pubkey: &Pubkey, validator_pubkey: &Pubkey, - validator_vote_account_keypair: &Keypair, + validator_vote_account_pubkey: &Pubkey, validator_stake_account_pubkey: &Pubkey, validator_stake_lamports: u64, validator_lamports: u64, + fee_rate_governor: FeeRateGovernor, + rent: Rent, cluster_type: ClusterType, -) -> GenesisConfigInfo { - let mint_keypair = Keypair::new(); + mut initial_accounts: Vec<(Pubkey, Account)>, +) -> GenesisConfig { let validator_vote_account = vote_state::create_account( - &validator_vote_account_keypair.pubkey(), + &validator_vote_account_pubkey, &validator_pubkey, 0, validator_stake_lamports, ); - let fee_rate_governor = FeeRateGovernor::new(0, 0); // most tests can't handle transaction fees - let rent = Rent::free(); // most tests don't expect rent - let validator_stake_account = stake_state::create_account( validator_stake_account_pubkey, - &validator_vote_account_keypair.pubkey(), + &validator_vote_account_pubkey, &validator_vote_account, &rent, validator_stake_lamports, ); - let accounts = [ - ( - mint_keypair.pubkey(), - Account::new(mint_lamports, 0, &system_program::id()), - ), - ( - *validator_pubkey, - Account::new(validator_lamports, 0, &system_program::id()), - ), - ( - validator_vote_account_keypair.pubkey(), - validator_vote_account, - ), - (*validator_stake_account_pubkey, validator_stake_account), - ] - .iter() - .cloned() - .collect(); + initial_accounts.push(( + *mint_pubkey, + Account::new(mint_lamports, 0, &system_program::id()), + )); + initial_accounts.push(( + *validator_pubkey, + Account::new(validator_lamports, 0, &system_program::id()), + )); + initial_accounts.push((*validator_vote_account_pubkey, validator_vote_account)); + initial_accounts.push((*validator_stake_account_pubkey, validator_stake_account)); let mut genesis_config = GenesisConfig { - accounts, + accounts: initial_accounts.iter().cloned().collect(), fee_rate_governor, rent, cluster_type, @@ -206,9 +226,5 @@ pub fn create_genesis_config_with_leader_ex( activate_all_features(&mut genesis_config); } - GenesisConfigInfo { - genesis_config, - mint_keypair, - voting_keypair: Keypair::from_bytes(&validator_vote_account_keypair.to_bytes()).unwrap(), - } + genesis_config } diff --git a/scripts/cargo-install-all.sh b/scripts/cargo-install-all.sh index 4b1388647..c9a4c2826 100755 --- a/scripts/cargo-install-all.sh +++ b/scripts/cargo-install-all.sh @@ -95,6 +95,7 @@ else solana-stake-monitor solana-stake-o-matic solana-sys-tuner + solana-test-validator solana-tokens solana-validator solana-watchtower diff --git a/sdk/src/rpc_port.rs b/sdk/src/rpc_port.rs index cbdbcb5dc..531be001b 100644 --- a/sdk/src/rpc_port.rs +++ b/sdk/src/rpc_port.rs @@ -3,6 +3,3 @@ pub const DEFAULT_RPC_PORT: u16 = 8899; /// Default port number for JSON RPC pubsub pub const DEFAULT_RPC_PUBSUB_PORT: u16 = 8900; - -/// Default port number for Banks RPC API -pub const DEFAULT_RPC_BANKS_PORT: u16 = 8901; diff --git a/tokens/src/commands.rs b/tokens/src/commands.rs index 1bc0468c3..d36098e6c 100644 --- a/tokens/src/commands.rs +++ b/tokens/src/commands.rs @@ -1038,7 +1038,7 @@ mod tests { use solana_core::test_validator::TestValidator; use solana_sdk::{ clock::DEFAULT_MS_PER_SLOT, - signature::{read_keypair_file, write_keypair_file}, + signature::{read_keypair_file, write_keypair_file, Signer}, }; use solana_stake_program::stake_instruction::StakeInstruction; @@ -1057,8 +1057,8 @@ mod tests { #[test] fn test_process_token_allocations() { - let test_validator = TestValidator::with_no_fees(); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_no_fees(alice.pubkey()); let url = test_validator.rpc_url(); let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent()); @@ -1069,8 +1069,8 @@ mod tests { #[test] fn test_process_transfer_amount_allocations() { - let test_validator = TestValidator::with_no_fees(); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_no_fees(alice.pubkey()); let url = test_validator.rpc_url(); let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent()); @@ -1081,8 +1081,8 @@ mod tests { #[test] fn test_process_stake_allocations() { - let test_validator = TestValidator::with_no_fees(); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_no_fees(alice.pubkey()); let url = test_validator.rpc_url(); let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent()); @@ -1398,8 +1398,8 @@ mod tests { let fees = 10_000; let fees_in_sol = lamports_to_sol(fees); - let test_validator = TestValidator::with_custom_fees(fees); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees); let url = test_validator.rpc_url(); let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent()); @@ -1485,8 +1485,8 @@ mod tests { fn test_check_payer_balances_distribute_tokens_separate_payers() { let fees = 10_000; let fees_in_sol = lamports_to_sol(fees); - let test_validator = TestValidator::with_custom_fees(fees); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees); let url = test_validator.rpc_url(); let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent()); @@ -1598,8 +1598,8 @@ mod tests { fn test_check_payer_balances_distribute_stakes_single_payer() { let fees = 10_000; let fees_in_sol = lamports_to_sol(fees); - let test_validator = TestValidator::with_custom_fees(fees); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees); let url = test_validator.rpc_url(); let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent()); test_validator_block_0_fee_workaround(&client); @@ -1707,8 +1707,8 @@ mod tests { fn test_check_payer_balances_distribute_stakes_separate_payers() { let fees = 10_000; let fees_in_sol = lamports_to_sol(fees); - let test_validator = TestValidator::with_custom_fees(fees); - let alice = test_validator.mint_keypair(); + let alice = Keypair::new(); + let test_validator = TestValidator::with_custom_fees(alice.pubkey(), fees); let url = test_validator.rpc_url(); let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent()); @@ -2025,8 +2025,8 @@ mod tests { #[test] fn test_distribute_allocations_dump_db() { - let test_validator = TestValidator::with_no_fees(); - let sender_keypair = test_validator.mint_keypair(); + let sender_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(sender_keypair.pubkey()); let url = test_validator.rpc_url(); let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent()); diff --git a/tokens/tests/commands.rs b/tokens/tests/commands.rs index c33b74988..a8f2fb5de 100644 --- a/tokens/tests/commands.rs +++ b/tokens/tests/commands.rs @@ -1,15 +1,17 @@ use solana_client::rpc_client::RpcClient; use solana_core::test_validator::TestValidator; +use solana_sdk::signature::{Keypair, Signer}; use solana_tokens::commands::test_process_distribute_tokens_with_client; #[test] fn test_process_distribute_with_rpc_client() { solana_logger::setup(); - let test_validator = TestValidator::with_no_fees(); + let mint_keypair = Keypair::new(); + let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey()); let client = RpcClient::new(test_validator.rpc_url()); - test_process_distribute_tokens_with_client(&client, test_validator.mint_keypair(), None); + test_process_distribute_tokens_with_client(&client, mint_keypair, None); test_validator.close(); } diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 6f7b9b563..af7ff7843 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -7,15 +7,18 @@ version = "1.5.0" repository = "https://github.com/solana-labs/solana" license = "Apache-2.0" homepage = "https://solana.com/" +default-run = "solana-validator" [dependencies] clap = "2.33.1" chrono = { version = "0.4.11", features = ["serde"] } console = "0.11.3" +indicatif = "0.15.0" log = "0.4.8" rand = "0.7.0" serde_json = "1.0.56" solana-clap-utils = { path = "../clap-utils", version = "1.5.0" } +solana-cli-config = { path = "../cli-config", version = "1.5.0" } solana-client = { path = "../client", version = "1.5.0" } solana-core = { path = "../core", version = "1.5.0" } solana-download-utils = { path = "../download-utils", version = "1.5.0" } @@ -29,18 +32,11 @@ solana-runtime = { path = "../runtime", version = "1.5.0" } solana-sdk = { path = "../sdk", version = "1.5.0" } solana-version = { path = "../version", version = "1.5.0" } solana-vote-program = { path = "../programs/vote", version = "1.5.0" } -solana-vote-signer = { path = "../vote-signer", version = "1.5.0" } +symlink = "0.1.0" [target."cfg(unix)".dependencies] 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/solana-test-validator b/validator/solana-test-validator new file mode 100755 index 000000000..39cd60c0d --- /dev/null +++ b/validator/solana-test-validator @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +here="$(dirname "$0")" +set -x +exec cargo run --manifest-path="$here"/Cargo.toml --bin solana-test-validator -- "$@" diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs new file mode 100644 index 000000000..b0295f549 --- /dev/null +++ b/validator/src/bin/solana-test-validator.rs @@ -0,0 +1,262 @@ +use { + clap::{value_t_or_exit, App, Arg}, + console::style, + indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}, + solana_clap_utils::{input_parsers::pubkey_of, input_validators::is_pubkey}, + solana_client::{client_error, rpc_client::RpcClient}, + solana_core::rpc::JsonRpcConfig, + solana_sdk::{ + clock::{Slot, DEFAULT_TICKS_PER_SLOT, MS_PER_TICK}, + commitment_config::CommitmentConfig, + fee_calculator::FeeRateGovernor, + rent::Rent, + rpc_port, + signature::{read_keypair_file, Signer}, + }, + solana_validator::{start_logger, test_validator::*}, + std::{ + path::PathBuf, + process::exit, + thread::sleep, + time::{Duration, SystemTime, UNIX_EPOCH}, + }, +}; + +#[derive(PartialEq)] +enum Output { + None, + Log, + Dashboard, +} + +/// Creates a new process bar for processing that will take an unknown amount of time +fn new_spinner_progress_bar() -> ProgressBar { + let progress_bar = ProgressBar::new(42); + progress_bar.set_draw_target(ProgressDrawTarget::stdout()); + progress_bar + .set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}")); + progress_bar.enable_steady_tick(100); + progress_bar +} + +/// Pretty print a "name value" +fn println_name_value(name: &str, value: &str) { + println!("{} {}", style(name).bold(), value); +} + +fn main() { + let default_rpc_port = rpc_port::DEFAULT_RPC_PORT.to_string(); + + let matches = App::new("solana-test-validator").about("Test Validator") + .version(solana_version::version!()) + .arg({ + let arg = Arg::with_name("config_file") + .short("C") + .long("config") + .value_name("PATH") + .takes_value(true) + .help("Configuration file to use"); + if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE { + arg.default_value(&config_file) + } else { + arg + } + }) + .arg( + Arg::with_name("mint_address") + .long("mint") + .value_name("PUBKEY") + .validator(is_pubkey) + .takes_value(true) + .help("Address of the mint account that will receive all the initial tokens [default: client keypair]"), + ) + .arg( + Arg::with_name("ledger_path") + .short("l") + .long("ledger") + .value_name("DIR") + .takes_value(true) + .required(true) + .default_value("test-ledger") + .help("Use DIR as ledger location"), + ) + .arg( + Arg::with_name("quiet") + .short("q") + .long("quiet") + .takes_value(false) + .conflicts_with("log") + .help("Quiet mode: suppress normal output") + ) + .arg( + Arg::with_name("log") + .long("log") + .takes_value(false) + .conflicts_with("quiet") + .help("Log mode: stream the validator log") + ) + .arg( + Arg::with_name("rpc_port") + .long("rpc-port") + .value_name("PORT") + .takes_value(true) + .default_value(&default_rpc_port) + .validator(solana_validator::port_validator) + .help("Use this port for JSON RPC and the next port for the RPC websocket"), + ) + .get_matches(); + + let cli_config = if let Some(config_file) = matches.value_of("config_file") { + solana_cli_config::Config::load(config_file).unwrap_or_default() + } else { + solana_cli_config::Config::default() + }; + + let mint_address = pubkey_of(&matches, "mint_address").unwrap_or_else(|| { + read_keypair_file(&cli_config.keypair_path) + .unwrap_or_else(|err| { + eprintln!( + "Error: Unable to read keypair file {}: {}", + cli_config.keypair_path, err + ); + exit(1); + }) + .pubkey() + }); + + let ledger_path = value_t_or_exit!(matches, "ledger_path", PathBuf); + let output = if matches.is_present("quiet") { + Output::None + } else if matches.is_present("log") { + Output::Log + } else { + Output::Dashboard + }; + + let rpc_ports = { + let rpc_port = value_t_or_exit!(matches, "rpc_port", u16); + (rpc_port, rpc_port + 1) + }; + + if !ledger_path.exists() { + let _progress_bar = if output == Output::Dashboard { + println_name_value("Mint address:", &mint_address.to_string()); + let progress_bar = new_spinner_progress_bar(); + progress_bar.set_message("Creating ledger..."); + Some(progress_bar) + } else { + None + }; + + TestValidator::initialize_ledger( + Some(&ledger_path), + TestValidatorGenesisConfig { + mint_address, + fee_rate_governor: FeeRateGovernor::default(), + rent: Rent::default(), + }, + ) + .unwrap_or_else(|err| { + eprintln!( + "Error: failed to initialize ledger at {}: {}", + ledger_path.display(), + err + ); + exit(1); + }); + } + + let validator_log_symlink = ledger_path.join("validator.log"); + let logfile = if output != Output::Log { + let validator_log_with_timestamp = format!( + "validator-{}.log", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() + ); + + let _ = std::fs::remove_file(&validator_log_symlink); + symlink::symlink_file(&validator_log_with_timestamp, &validator_log_symlink).unwrap(); + + Some( + ledger_path + .join(validator_log_with_timestamp) + .into_os_string() + .into_string() + .unwrap(), + ) + } else { + None + }; + + let _logger_thread = start_logger(logfile); + + let test_validator = { + let _progress_bar = if output == Output::Dashboard { + println_name_value("Ledger location:", &format!("{}", ledger_path.display())); + println_name_value("Log:", &format!("{}", validator_log_symlink.display())); + let progress_bar = new_spinner_progress_bar(); + progress_bar.set_message("Initializing..."); + Some(progress_bar) + } else { + None + }; + + TestValidator::start( + &ledger_path, + TestValidatorStartConfig { + rpc_config: JsonRpcConfig { + enable_validator_exit: true, + enable_rpc_transaction_history: true, + ..JsonRpcConfig::default() + }, + rpc_ports: Some(rpc_ports), + }, + ) + } + .unwrap_or_else(|err| { + eprintln!("Error: failed to start validator: {}", err); + exit(1); + }); + + if output == Output::Dashboard { + println_name_value("JSON RPC URL:", &test_validator.rpc_url()); + println_name_value( + "JSON RPC PubSub Websocket:", + &test_validator.rpc_pubsub_url(), + ); + println_name_value("Gossip Address:", &test_validator.gossip().to_string()); + println_name_value("TPU Address:", &test_validator.tpu().to_string()); + + let progress_bar = new_spinner_progress_bar(); + let rpc_client = RpcClient::new(test_validator.rpc_url()); + + fn get_validator_stats(rpc_client: &RpcClient) -> client_error::Result<(Slot, Slot, u64)> { + let max_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::max())?; + let recent_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::recent())?; + let transaction_count = + rpc_client.get_transaction_count_with_commitment(CommitmentConfig::recent())?; + Ok((recent_slot, max_slot, transaction_count)) + } + + loop { + match get_validator_stats(&rpc_client) { + Ok((recent_slot, max_slot, transaction_count)) => { + progress_bar.set_message(&format!( + "Recent slot: {} | Max confirmed slot: {} | Transaction count: {}", + recent_slot, max_slot, transaction_count, + )); + } + Err(err) => { + progress_bar.set_message(&format!("{}", err)); + } + } + sleep(Duration::from_millis( + MS_PER_TICK * DEFAULT_TICKS_PER_SLOT / 2, + )); + } + } + + std::thread::park(); +} diff --git a/validator/src/lib.rs b/validator/src/lib.rs new file mode 100644 index 000000000..63f161778 --- /dev/null +++ b/validator/src/lib.rs @@ -0,0 +1,74 @@ +pub use solana_core::test_validator; +use { + log::*, + std::{env, process::exit, thread::JoinHandle}, +}; + +#[cfg(unix)] +fn redirect_stderr(filename: &str) { + use std::{fs::OpenOptions, os::unix::io::AsRawFd}; + match OpenOptions::new() + .write(true) + .create(true) + .append(true) + .open(filename) + { + Ok(file) => unsafe { + libc::dup2(file.as_raw_fd(), libc::STDERR_FILENO); + }, + Err(err) => eprintln!("Unable to open {}: {}", filename, err), + } +} + +pub fn start_logger(logfile: Option) -> Option> { + // Default to RUST_BACKTRACE=1 for more informative validator logs + if env::var_os("RUST_BACKTRACE").is_none() { + env::set_var("RUST_BACKTRACE", "1") + } + + let logger_thread = match logfile { + None => None, + Some(logfile) => { + #[cfg(unix)] + { + let signals = signal_hook::iterator::Signals::new(&[signal_hook::SIGUSR1]) + .unwrap_or_else(|err| { + eprintln!("Unable to register SIGUSR1 handler: {:?}", err); + exit(1); + }); + + redirect_stderr(&logfile); + Some(std::thread::spawn(move || { + for signal in signals.forever() { + info!( + "received SIGUSR1 ({}), reopening log file: {:?}", + signal, logfile + ); + redirect_stderr(&logfile); + } + })) + } + #[cfg(not(unix))] + { + println!("logging to a file is not supported on this platform"); + () + } + } + }; + + solana_logger::setup_with_default( + &[ + "solana=info,solana_runtime::message_processor=error", /* info logging for all solana modules */ + "rpc=trace", /* json_rpc request/response logging */ + ] + .join(","), + ); + + logger_thread +} + +pub fn port_validator(port: String) -> Result<(), String> { + port.parse::() + .map(|_| ()) + .map_err(|e| format!("{:?}", e)) +} diff --git a/validator/src/main.rs b/validator/src/main.rs index bb733b5de..a093d8272 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -21,7 +21,7 @@ use solana_core::{ gossip_service::GossipService, rpc::JsonRpcConfig, rpc_pubsub_service::PubSubConfig, - validator::{Validator, ValidatorConfig}, + validator::{is_snapshot_config_invalid, Validator, ValidatorConfig}, }; use solana_download_utils::{download_genesis_if_missing, download_snapshot}; use solana_ledger::blockstore_db::BlockstoreRecoveryMode; @@ -39,6 +39,7 @@ use solana_sdk::{ pubkey::Pubkey, signature::{Keypair, Signer}, }; +use solana_validator::start_logger; use std::{ collections::HashSet, env, @@ -51,16 +52,10 @@ use std::{ atomic::{AtomicBool, Ordering}, Arc, }, - thread::{sleep, JoinHandle}, + thread::sleep, time::{Duration, Instant}, }; -fn port_validator(port: String) -> Result<(), String> { - port.parse::() - .map(|_| ()) - .map_err(|e| format!("{:?}", e)) -} - fn port_range_validator(port_range: String) -> Result<(), String> { if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) { if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH { @@ -482,73 +477,6 @@ fn download_then_check_genesis_hash( Ok(genesis_config.hash()) } -fn is_snapshot_config_invalid( - snapshot_interval_slots: u64, - accounts_hash_interval_slots: u64, -) -> bool { - snapshot_interval_slots != 0 - && (snapshot_interval_slots < accounts_hash_interval_slots - || snapshot_interval_slots % accounts_hash_interval_slots != 0) -} - -#[cfg(unix)] -fn redirect_stderr(filename: &str) { - use std::{fs::OpenOptions, os::unix::io::AsRawFd}; - match OpenOptions::new() - .write(true) - .create(true) - .append(true) - .open(filename) - { - Ok(file) => unsafe { - libc::dup2(file.as_raw_fd(), libc::STDERR_FILENO); - }, - Err(err) => eprintln!("Unable to open {}: {}", filename, err), - } -} - -fn start_logger(logfile: Option) -> Option> { - let logger_thread = match logfile { - None => None, - Some(logfile) => { - #[cfg(unix)] - { - let signals = signal_hook::iterator::Signals::new(&[signal_hook::SIGUSR1]) - .unwrap_or_else(|err| { - eprintln!("Unable to register SIGUSR1 handler: {:?}", err); - exit(1); - }); - - redirect_stderr(&logfile); - Some(std::thread::spawn(move || { - for signal in signals.forever() { - info!( - "received SIGUSR1 ({}), reopening log file: {:?}", - signal, logfile - ); - redirect_stderr(&logfile); - } - })) - } - #[cfg(not(unix))] - { - println!("logging to a file is not supported on this platform"); - () - } - } - }; - - solana_logger::setup_with_default( - &[ - "solana=info,solana_runtime::message_processor=error", /* info logging for all solana modules */ - "rpc=trace", /* json_rpc request/response logging */ - ] - .join(","), - ); - - logger_thread -} - fn verify_reachable_ports( node: &Node, cluster_entrypoint: &ContactInfo, @@ -988,8 +916,8 @@ pub fn main() { .long("rpc-port") .value_name("PORT") .takes_value(true) - .validator(port_validator) - .help("Use this port for JSON RPC, the next port for the RPC websocket, and then third port for the RPC banks API"), + .validator(solana_validator::port_validator) + .help("Use this port for JSON RPC and the next port for the RPC websocket"), ) .arg( Arg::with_name("private_rpc") @@ -1715,11 +1643,6 @@ pub fn main() { let use_progress_bar = logfile.is_none(); let _logger_thread = start_logger(logfile); - // Default to RUST_BACKTRACE=1 for more informative validator logs - if env::var_os("RUST_BACKTRACE").is_none() { - env::set_var("RUST_BACKTRACE", "1") - } - let gossip_host = matches .value_of("gossip_host") .map(|gossip_host| { @@ -1820,17 +1743,3 @@ pub fn main() { validator.join().expect("validator exit"); info!("Validator exiting.."); } - -#[cfg(test)] -pub mod tests { - use super::*; - - #[test] - fn test_interval_check() { - assert!(!is_snapshot_config_invalid(0, 100)); - assert!(is_snapshot_config_invalid(1, 100)); - assert!(is_snapshot_config_invalid(230, 100)); - assert!(!is_snapshot_config_invalid(500, 100)); - assert!(!is_snapshot_config_invalid(5, 5)); - } -}