Browse Source

Initial solana-test-validator command-line program

Michael Vines 5 years ago
parent
commit
0a9ff1dc9d

+ 4 - 1
Cargo.lock

@@ -4014,6 +4014,7 @@ dependencies = [
  "solana-metrics",
  "solana-metrics",
  "solana-net-utils",
  "solana-net-utils",
  "solana-perf",
  "solana-perf",
+ "solana-program-test",
  "solana-rayon-threadlimit",
  "solana-rayon-threadlimit",
  "solana-runtime",
  "solana-runtime",
  "solana-sdk",
  "solana-sdk",
@@ -5071,12 +5072,14 @@ dependencies = [
  "chrono",
  "chrono",
  "clap",
  "clap",
  "console",
  "console",
+ "indicatif",
  "libc",
  "libc",
  "log 0.4.8",
  "log 0.4.8",
  "rand 0.7.3",
  "rand 0.7.3",
  "serde_json",
  "serde_json",
  "signal-hook",
  "signal-hook",
  "solana-clap-utils",
  "solana-clap-utils",
+ "solana-cli-config",
  "solana-client",
  "solana-client",
  "solana-core",
  "solana-core",
  "solana-download-utils",
  "solana-download-utils",
@@ -5090,7 +5093,7 @@ dependencies = [
  "solana-sdk",
  "solana-sdk",
  "solana-version",
  "solana-version",
  "solana-vote-program",
  "solana-vote-program",
- "solana-vote-signer",
+ "symlink",
 ]
 ]
 
 
 [[package]]
 [[package]]

+ 3 - 2
cli/tests/deploy.rs

@@ -21,10 +21,11 @@ fn test_cli_deploy_program() {
     pathbuf.push("noop");
     pathbuf.push("noop");
     pathbuf.set_extension("so");
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());

+ 22 - 6
cli/tests/nonce.rs

@@ -22,13 +22,21 @@ use std::sync::mpsc::channel;
 
 
 #[test]
 #[test]
 fn test_nonce() {
 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]
 #[test]
 fn test_nonce_with_seed() {
 fn test_nonce_with_seed() {
+    let mint_keypair = Keypair::new();
     full_battery_tests(
     full_battery_tests(
-        TestValidator::with_no_fees(),
+        TestValidator::with_no_fees(mint_keypair.pubkey()),
+        mint_keypair,
         Some(String::from("seed")),
         Some(String::from("seed")),
         false,
         false,
     );
     );
@@ -36,16 +44,23 @@ fn test_nonce_with_seed() {
 
 
 #[test]
 #[test]
 fn test_nonce_with_authority() {
 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(
 fn full_battery_tests(
     test_validator: TestValidator,
     test_validator: TestValidator,
+    mint_keypair: Keypair,
     seed: Option<String>,
     seed: Option<String>,
     use_nonce_authority: bool,
     use_nonce_authority: bool,
 ) {
 ) {
     let (sender, receiver) = channel();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());
@@ -202,10 +217,11 @@ fn full_battery_tests(
 #[test]
 #[test]
 fn test_create_account_with_seed() {
 fn test_create_account_with_seed() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();
     let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();

+ 7 - 3
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_client::rpc_client::RpcClient;
 use solana_core::test_validator::TestValidator;
 use solana_core::test_validator::TestValidator;
 use solana_faucet::faucet::run_local_faucet;
 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;
 use std::sync::mpsc::channel;
 
 
 #[test]
 #[test]
 fn test_cli_request_airdrop() {
 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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let mut bob_config = CliConfig::recent_for_tests();
     let mut bob_config = CliConfig::recent_for_tests();

+ 30 - 20
cli/tests/stake.rs

@@ -26,9 +26,10 @@ use std::sync::mpsc::channel;
 
 
 #[test]
 #[test]
 fn test_stake_delegation_force() {
 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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     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() {
 fn test_seed_stake_delegation_and_deactivation() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     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() {
 fn test_stake_delegation_and_deactivation() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     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() {
 fn test_offline_stake_delegation_and_deactivation() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     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() {
 fn test_nonced_stake_delegation_and_deactivation() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());
@@ -519,9 +524,10 @@ fn test_nonced_stake_delegation_and_deactivation() {
 fn test_stake_authorize() {
 fn test_stake_authorize() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());
@@ -792,9 +798,10 @@ fn test_stake_authorize_with_fee_payer() {
     solana_logger::setup();
     solana_logger::setup();
     const SIG_FEE: u64 = 42;
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());
@@ -917,9 +924,10 @@ fn test_stake_authorize_with_fee_payer() {
 fn test_stake_split() {
 fn test_stake_split() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());
@@ -1061,9 +1069,10 @@ fn test_stake_split() {
 fn test_stake_set_lockup() {
 fn test_stake_set_lockup() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     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() {
 fn test_offline_nonced_create_stake_account_and_withdraw() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());

+ 9 - 6
cli/tests/transfer.rs

@@ -22,10 +22,11 @@ use std::sync::mpsc::channel;
 #[test]
 #[test]
 fn test_transfer() {
 fn test_transfer() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());
@@ -243,10 +244,11 @@ fn test_transfer() {
 #[test]
 #[test]
 fn test_transfer_multisession_signing() {
 fn test_transfer_multisession_signing() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let to_pubkey = Pubkey::new(&[1u8; 32]);
     let to_pubkey = Pubkey::new(&[1u8; 32]);
@@ -363,10 +365,11 @@ fn test_transfer_multisession_signing() {
 #[test]
 #[test]
 fn test_transfer_all() {
 fn test_transfer_all() {
     solana_logger::setup();
     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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());

+ 3 - 2
cli/tests/vote.rs

@@ -19,9 +19,10 @@ use std::sync::mpsc::channel;
 
 
 #[test]
 #[test]
 fn test_vote_authorize_and_withdraw() {
 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();
     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 faucet_addr = receiver.recv().unwrap();
 
 
     let rpc_client = RpcClient::new(test_validator.rpc_url());
     let rpc_client = RpcClient::new(test_validator.rpc_url());

+ 1 - 0
core/Cargo.toml

@@ -58,6 +58,7 @@ solana-metrics = { path = "../metrics", version = "1.5.0" }
 solana-measure = { path = "../measure", version = "1.5.0" }
 solana-measure = { path = "../measure", version = "1.5.0" }
 solana-net-utils = { path = "../net-utils", version = "1.5.0" }
 solana-net-utils = { path = "../net-utils", version = "1.5.0" }
 solana-perf = { path = "../perf", 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-runtime = { path = "../runtime", version = "1.5.0" }
 solana-sdk = { path = "../sdk", version = "1.5.0" }
 solana-sdk = { path = "../sdk", version = "1.5.0" }
 solana-frozen-abi = { path = "../frozen-abi", version = "1.5.0" }
 solana-frozen-abi = { path = "../frozen-abi", version = "1.5.0" }

+ 193 - 102
core/src/test_validator.rs

@@ -2,188 +2,279 @@ use {
     crate::{
     crate::{
         cluster_info::Node,
         cluster_info::Node,
         gossip_service::discover_cluster,
         gossip_service::discover_cluster,
+        rpc::JsonRpcConfig,
         validator::{Validator, ValidatorConfig},
         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::{
     solana_sdk::{
         fee_calculator::FeeRateGovernor,
         fee_calculator::FeeRateGovernor,
-        hash::Hash,
         native_token::sol_to_lamports,
         native_token::sol_to_lamports,
         pubkey::Pubkey,
         pubkey::Pubkey,
         rent::Rent,
         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 fee_rate_governor: FeeRateGovernor,
-    pub mint_lamports: u64,
+    pub mint_address: Pubkey,
     pub rent: Rent,
     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 {
 pub struct TestValidator {
-    validator: Validator,
     ledger_path: PathBuf,
     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,
     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 {
 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<PathBuf, Box<dyn std::error::Error>> {
+        let TestValidatorGenesisConfig {
             fee_rate_governor,
             fee_rate_governor,
-            mint_lamports,
+            mint_address,
             rent,
             rent,
-            validator_identity_keypair,
-            validator_identity_lamports,
-            validator_stake_lamports,
         } = config;
         } = 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,
             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_stake_lamports,
             validator_identity_lamports,
             validator_identity_lamports,
+            fee_rate_governor,
+            rent,
             solana_sdk::genesis_config::ClusterType::Development,
             solana_sdk::genesis_config::ClusterType::Development,
+            initial_accounts,
         );
         );
 
 
-        genesis_config.rent = rent;
-        genesis_config.fee_rate_governor = fee_rate_governor;
+        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 (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_config);
+        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(),
+        )?;
 
 
-        let config = ValidatorConfig {
-            rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub)),
-            ..ValidatorConfig::default()
-        };
+        Ok(ledger_path)
+    }
+
+    /// Starts a TestValidator at the provided ledger directory
+    pub fn start(
+        ledger_path: &Path,
+        config: TestValidatorStartConfig,
+    ) -> Result<Self, Box<dyn std::error::Error>> {
+        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 = vote_account_keypair.pubkey();
+        let vote_account_address = validator_vote_account.pubkey();
         let rpc_url = format!("http://{}:{}", node.info.rpc.ip(), node.info.rpc.port());
         let rpc_url = format!("http://{}:{}", node.info.rpc.ip(), node.info.rpc.port());
         let rpc_pubsub_url = format!("ws://{}/", node.info.rpc_pubsub);
         let rpc_pubsub_url = format!("ws://{}/", node.info.rpc_pubsub);
         let tpu = node.info.tpu;
         let tpu = node.info.tpu;
         let gossip = node.info.gossip;
         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(
         let validator = Validator::new(
             node,
             node,
-            &validator_identity_keypair,
+            &Arc::new(validator_identity_keypair),
             &ledger_path,
             &ledger_path,
-            &vote_account_keypair.pubkey(),
-            vec![Arc::new(vote_account_keypair)],
+            &validator_vote_account.pubkey(),
+            vec![Arc::new(validator_vote_account)],
             None,
             None,
-            &config,
+            &validator_config,
         );
         );
 
 
         // Needed to avoid panics in `solana-responder-gossip` in tests that create a number of
         // Needed to avoid panics in `solana-responder-gossip` in tests that create a number of
         // test validators concurrently...
         // test validators concurrently...
         discover_cluster(&gossip, 1).expect("TestValidator startup failed");
         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,
             validator,
             vote_account_address,
             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) {
     pub fn close(self) {
         self.validator.close().unwrap();
         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 {
     pub fn tpu(&self) -> &SocketAddr {
         &self.tpu
         &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 {
     pub fn rpc_url(&self) -> String {
         self.rpc_url.clone()
         self.rpc_url.clone()
     }
     }
 
 
+    /// Return the test validator's JSON RPC PubSub URL
     pub fn rpc_pubsub_url(&self) -> String {
     pub fn rpc_pubsub_url(&self) -> String {
         self.rpc_pubsub_url.clone()
         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 {
     pub fn vote_account_address(&self) -> Pubkey {
         self.vote_account_address
         self.vote_account_address
     }
     }

+ 25 - 0
core/src/validator.rs

@@ -490,6 +490,13 @@ impl Validator {
 
 
         let (snapshot_packager_service, snapshot_config_and_package_sender) =
         let (snapshot_packager_service, snapshot_config_and_package_sender) =
             if let Some(snapshot_config) = config.snapshot_config.clone() {
             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
                 // Start a snapshot packaging service
                 let (sender, receiver) = channel();
                 let (sender, receiver) = channel();
                 let snapshot_packager_service =
                 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)]
 #[cfg(test)]
 mod tests {
 mod tests {
     use super::*;
     use super::*;
@@ -1388,4 +1404,13 @@ mod tests {
             rpc_override_health_check
             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));
+    }
 }
 }

+ 7 - 4
core/tests/client.rs

@@ -12,8 +12,11 @@ use solana_runtime::{
     genesis_utils::{create_genesis_config, GenesisConfigInfo},
     genesis_utils::{create_genesis_config, GenesisConfigInfo},
 };
 };
 use solana_sdk::{
 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::{
 use std::{
     net::{IpAddr, SocketAddr},
     net::{IpAddr, SocketAddr},
@@ -30,8 +33,8 @@ use systemstat::Ipv4Addr;
 fn test_rpc_client() {
 fn test_rpc_client() {
     solana_logger::setup();
     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();
     let bob_pubkey = solana_sdk::pubkey::new_rand();
 
 

+ 14 - 7
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_core::{rpc_pubsub::gen_client::Client as PubsubClient, test_validator::TestValidator};
 use solana_sdk::{
 use solana_sdk::{
-    commitment_config::CommitmentConfig, hash::Hash, signature::Signer, system_transaction,
+    commitment_config::CommitmentConfig,
+    hash::Hash,
+    signature::{Keypair, Signer},
+    system_transaction,
     transaction::Transaction,
     transaction::Transaction,
 };
 };
 use std::{
 use std::{
@@ -52,8 +55,8 @@ fn post_rpc(request: Value, rpc_url: &str) -> Value {
 fn test_rpc_send_tx() {
 fn test_rpc_send_tx() {
     solana_logger::setup();
     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 rpc_url = test_validator.rpc_url();
 
 
     let bob_pubkey = solana_sdk::pubkey::new_rand();
     let bob_pubkey = solana_sdk::pubkey::new_rand();
@@ -113,7 +116,8 @@ fn test_rpc_send_tx() {
 fn test_rpc_invalid_requests() {
 fn test_rpc_invalid_requests() {
     solana_logger::setup();
     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 rpc_url = test_validator.rpc_url();
 
 
     let bob_pubkey = solana_sdk::pubkey::new_rand();
     let bob_pubkey = solana_sdk::pubkey::new_rand();
@@ -145,12 +149,15 @@ fn test_rpc_invalid_requests() {
 fn test_rpc_subscriptions() {
 fn test_rpc_subscriptions() {
     solana_logger::setup();
     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();
     let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
     transactions_socket.connect(test_validator.tpu()).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
     // Create transaction signatures to subscribe to
     let transactions: Vec<Transaction> = (0..1000)
     let transactions: Vec<Transaction> = (0..1000)
         .map(|_| {
         .map(|_| {
@@ -158,7 +165,7 @@ fn test_rpc_subscriptions() {
                 &alice,
                 &alice,
                 &solana_sdk::pubkey::new_rand(),
                 &solana_sdk::pubkey::new_rand(),
                 1,
                 1,
-                test_validator.genesis_hash(),
+                recent_blockhash,
             )
             )
         })
         })
         .collect();
         .collect();

+ 1 - 2
ledger/src/genesis_utils.rs

@@ -1,6 +1,5 @@
 pub use solana_runtime::genesis_utils::{
 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
 // same as genesis_config::create_genesis_config, but with bootstrap_validator staking logic

+ 3 - 29
program-test/src/lib.rs

@@ -39,6 +39,7 @@ use {
 
 
 // Export types so test clients can limit their solana crate dependencies
 // Export types so test clients can limit their solana crate dependencies
 pub use solana_banks_client::BanksClient;
 pub use solana_banks_client::BanksClient;
+pub mod programs;
 
 
 #[macro_use]
 #[macro_use]
 extern crate solana_bpf_loader_program;
 extern crate solana_bpf_loader_program;
@@ -358,24 +359,6 @@ fn read_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
     file_data
     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 {
 pub struct ProgramTest {
     accounts: Vec<(Pubkey, Account)>,
     accounts: Vec<(Pubkey, Account)>,
     builtins: Vec<Builtin>,
     builtins: Vec<Builtin>,
@@ -614,17 +597,8 @@ impl ProgramTest {
         }
         }
 
 
         // Add commonly-used SPL programs as a convenience to the user
         // 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
         // User-supplied additional builtins

+ 38 - 0
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()
+}

+ 53 - 37
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!(!voting_keypairs.is_empty());
     assert_eq!(voting_keypairs.len(), stakes.len());
     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_lamports,
+        &mint_keypair.pubkey(),
         &voting_keypairs[0].borrow().node_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(),
         &voting_keypairs[0].borrow().stake_keypair.pubkey(),
         stakes[0],
         stakes[0],
         VALIDATOR_LAMPORTS,
         VALIDATOR_LAMPORTS,
+        FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
+        Rent::free(),               // most tests don't expect rent
         cluster_type,
         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..]) {
     for (validator_voting_keypairs, stake) in voting_keypairs[1..].iter().zip(&stakes[1..]) {
         let node_pubkey = validator_voting_keypairs.borrow().node_keypair.pubkey();
         let node_pubkey = validator_voting_keypairs.borrow().node_keypair.pubkey();
         let vote_pubkey = validator_voting_keypairs.borrow().vote_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_pubkey: &Pubkey,
     validator_stake_lamports: u64,
     validator_stake_lamports: u64,
 ) -> GenesisConfigInfo {
 ) -> 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_lamports,
+        &mint_keypair.pubkey(),
         validator_pubkey,
         validator_pubkey,
-        &Keypair::new(),
+        &voting_keypair.pubkey(),
         &solana_sdk::pubkey::new_rand(),
         &solana_sdk::pubkey::new_rand(),
         validator_stake_lamports,
         validator_stake_lamports,
         VALIDATOR_LAMPORTS,
         VALIDATOR_LAMPORTS,
+        FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
+        Rent::free(),               // most tests don't expect rent
         ClusterType::Development,
         ClusterType::Development,
-    )
+        vec![],
+    );
+
+    GenesisConfigInfo {
+        genesis_config,
+        voting_keypair,
+        mint_keypair,
+    }
 }
 }
 
 
 pub fn activate_all_features(genesis_config: &mut GenesisConfig) {
 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(
 pub fn create_genesis_config_with_leader_ex(
     mint_lamports: u64,
     mint_lamports: u64,
+    mint_pubkey: &Pubkey,
     validator_pubkey: &Pubkey,
     validator_pubkey: &Pubkey,
-    validator_vote_account_keypair: &Keypair,
+    validator_vote_account_pubkey: &Pubkey,
     validator_stake_account_pubkey: &Pubkey,
     validator_stake_account_pubkey: &Pubkey,
     validator_stake_lamports: u64,
     validator_stake_lamports: u64,
     validator_lamports: u64,
     validator_lamports: u64,
+    fee_rate_governor: FeeRateGovernor,
+    rent: Rent,
     cluster_type: ClusterType,
     cluster_type: ClusterType,
-) -> GenesisConfigInfo {
-    let mint_keypair = Keypair::new();
+    mut initial_accounts: Vec<(Pubkey, Account)>,
+) -> GenesisConfig {
     let validator_vote_account = vote_state::create_account(
     let validator_vote_account = vote_state::create_account(
-        &validator_vote_account_keypair.pubkey(),
+        &validator_vote_account_pubkey,
         &validator_pubkey,
         &validator_pubkey,
         0,
         0,
         validator_stake_lamports,
         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(
     let validator_stake_account = stake_state::create_account(
         validator_stake_account_pubkey,
         validator_stake_account_pubkey,
-        &validator_vote_account_keypair.pubkey(),
+        &validator_vote_account_pubkey,
         &validator_vote_account,
         &validator_vote_account,
         &rent,
         &rent,
         validator_stake_lamports,
         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 {
     let mut genesis_config = GenesisConfig {
-        accounts,
+        accounts: initial_accounts.iter().cloned().collect(),
         fee_rate_governor,
         fee_rate_governor,
         rent,
         rent,
         cluster_type,
         cluster_type,
@@ -206,9 +226,5 @@ pub fn create_genesis_config_with_leader_ex(
         activate_all_features(&mut genesis_config);
         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
 }
 }

+ 1 - 0
scripts/cargo-install-all.sh

@@ -95,6 +95,7 @@ else
     solana-stake-monitor
     solana-stake-monitor
     solana-stake-o-matic
     solana-stake-o-matic
     solana-sys-tuner
     solana-sys-tuner
+    solana-test-validator
     solana-tokens
     solana-tokens
     solana-validator
     solana-validator
     solana-watchtower
     solana-watchtower

+ 0 - 3
sdk/src/rpc_port.rs

@@ -3,6 +3,3 @@ pub const DEFAULT_RPC_PORT: u16 = 8899;
 
 
 /// Default port number for JSON RPC pubsub
 /// Default port number for JSON RPC pubsub
 pub const DEFAULT_RPC_PUBSUB_PORT: u16 = 8900;
 pub const DEFAULT_RPC_PUBSUB_PORT: u16 = 8900;
-
-/// Default port number for Banks RPC API
-pub const DEFAULT_RPC_BANKS_PORT: u16 = 8901;

+ 17 - 17
tokens/src/commands.rs

@@ -1038,7 +1038,7 @@ mod tests {
     use solana_core::test_validator::TestValidator;
     use solana_core::test_validator::TestValidator;
     use solana_sdk::{
     use solana_sdk::{
         clock::DEFAULT_MS_PER_SLOT,
         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;
     use solana_stake_program::stake_instruction::StakeInstruction;
 
 
@@ -1057,8 +1057,8 @@ mod tests {
 
 
     #[test]
     #[test]
     fn test_process_token_allocations() {
     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 url = test_validator.rpc_url();
 
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
@@ -1069,8 +1069,8 @@ mod tests {
 
 
     #[test]
     #[test]
     fn test_process_transfer_amount_allocations() {
     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 url = test_validator.rpc_url();
 
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
@@ -1081,8 +1081,8 @@ mod tests {
 
 
     #[test]
     #[test]
     fn test_process_stake_allocations() {
     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 url = test_validator.rpc_url();
 
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
@@ -1398,8 +1398,8 @@ mod tests {
         let fees = 10_000;
         let fees = 10_000;
         let fees_in_sol = lamports_to_sol(fees);
         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 url = test_validator.rpc_url();
 
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
@@ -1485,8 +1485,8 @@ mod tests {
     fn test_check_payer_balances_distribute_tokens_separate_payers() {
     fn test_check_payer_balances_distribute_tokens_separate_payers() {
         let fees = 10_000;
         let fees = 10_000;
         let fees_in_sol = lamports_to_sol(fees);
         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 url = test_validator.rpc_url();
 
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
@@ -1598,8 +1598,8 @@ mod tests {
     fn test_check_payer_balances_distribute_stakes_single_payer() {
     fn test_check_payer_balances_distribute_stakes_single_payer() {
         let fees = 10_000;
         let fees = 10_000;
         let fees_in_sol = lamports_to_sol(fees);
         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 url = test_validator.rpc_url();
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
         test_validator_block_0_fee_workaround(&client);
         test_validator_block_0_fee_workaround(&client);
@@ -1707,8 +1707,8 @@ mod tests {
     fn test_check_payer_balances_distribute_stakes_separate_payers() {
     fn test_check_payer_balances_distribute_stakes_separate_payers() {
         let fees = 10_000;
         let fees = 10_000;
         let fees_in_sol = lamports_to_sol(fees);
         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 url = test_validator.rpc_url();
 
 
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
@@ -2025,8 +2025,8 @@ mod tests {
 
 
     #[test]
     #[test]
     fn test_distribute_allocations_dump_db() {
     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 url = test_validator.rpc_url();
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
         let client = RpcClient::new_with_commitment(url, CommitmentConfig::recent());
 
 

+ 4 - 2
tokens/tests/commands.rs

@@ -1,15 +1,17 @@
 use solana_client::rpc_client::RpcClient;
 use solana_client::rpc_client::RpcClient;
 use solana_core::test_validator::TestValidator;
 use solana_core::test_validator::TestValidator;
+use solana_sdk::signature::{Keypair, Signer};
 use solana_tokens::commands::test_process_distribute_tokens_with_client;
 use solana_tokens::commands::test_process_distribute_tokens_with_client;
 
 
 #[test]
 #[test]
 fn test_process_distribute_with_rpc_client() {
 fn test_process_distribute_with_rpc_client() {
     solana_logger::setup();
     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());
     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();
     test_validator.close();
 }
 }

+ 4 - 8
validator/Cargo.toml

@@ -7,15 +7,18 @@ version = "1.5.0"
 repository = "https://github.com/solana-labs/solana"
 repository = "https://github.com/solana-labs/solana"
 license = "Apache-2.0"
 license = "Apache-2.0"
 homepage = "https://solana.com/"
 homepage = "https://solana.com/"
+default-run = "solana-validator"
 
 
 [dependencies]
 [dependencies]
 clap = "2.33.1"
 clap = "2.33.1"
 chrono = { version = "0.4.11", features = ["serde"] }
 chrono = { version = "0.4.11", features = ["serde"] }
 console = "0.11.3"
 console = "0.11.3"
+indicatif = "0.15.0"
 log = "0.4.8"
 log = "0.4.8"
 rand = "0.7.0"
 rand = "0.7.0"
 serde_json = "1.0.56"
 serde_json = "1.0.56"
 solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
 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-client = { path = "../client", version = "1.5.0" }
 solana-core = { path = "../core", version = "1.5.0" }
 solana-core = { path = "../core", version = "1.5.0" }
 solana-download-utils = { path = "../download-utils", 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-sdk = { path = "../sdk", version = "1.5.0" }
 solana-version = { path = "../version", version = "1.5.0" }
 solana-version = { path = "../version", version = "1.5.0" }
 solana-vote-program = { path = "../programs/vote", 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]
 [target."cfg(unix)".dependencies]
 libc = "0.2.72"
 libc = "0.2.72"
 signal-hook = "0.1.15"
 signal-hook = "0.1.15"
 
 
-#[[bin]]
-#name = "solana-validator"
-#path = "src/main.rs"
-#
-#[lib]
-#name = "solana_validator"
-
 [package.metadata.docs.rs]
 [package.metadata.docs.rs]
 targets = ["x86_64-unknown-linux-gnu"]
 targets = ["x86_64-unknown-linux-gnu"]

+ 5 - 0
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 -- "$@"

+ 262 - 0
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();
+}

+ 74 - 0
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<String>) -> Option<JoinHandle<()>> {
+    // 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::<u16>()
+        .map(|_| ())
+        .map_err(|e| format!("{:?}", e))
+}

+ 5 - 96
validator/src/main.rs

@@ -21,7 +21,7 @@ use solana_core::{
     gossip_service::GossipService,
     gossip_service::GossipService,
     rpc::JsonRpcConfig,
     rpc::JsonRpcConfig,
     rpc_pubsub_service::PubSubConfig,
     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_download_utils::{download_genesis_if_missing, download_snapshot};
 use solana_ledger::blockstore_db::BlockstoreRecoveryMode;
 use solana_ledger::blockstore_db::BlockstoreRecoveryMode;
@@ -39,6 +39,7 @@ use solana_sdk::{
     pubkey::Pubkey,
     pubkey::Pubkey,
     signature::{Keypair, Signer},
     signature::{Keypair, Signer},
 };
 };
+use solana_validator::start_logger;
 use std::{
 use std::{
     collections::HashSet,
     collections::HashSet,
     env,
     env,
@@ -51,16 +52,10 @@ use std::{
         atomic::{AtomicBool, Ordering},
         atomic::{AtomicBool, Ordering},
         Arc,
         Arc,
     },
     },
-    thread::{sleep, JoinHandle},
+    thread::sleep,
     time::{Duration, Instant},
     time::{Duration, Instant},
 };
 };
 
 
-fn port_validator(port: String) -> Result<(), String> {
-    port.parse::<u16>()
-        .map(|_| ())
-        .map_err(|e| format!("{:?}", e))
-}
-
 fn port_range_validator(port_range: String) -> Result<(), String> {
 fn port_range_validator(port_range: String) -> Result<(), String> {
     if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
     if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) {
         if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
         if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH {
@@ -482,73 +477,6 @@ fn download_then_check_genesis_hash(
     Ok(genesis_config.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<String>) -> Option<JoinHandle<()>> {
-    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(
 fn verify_reachable_ports(
     node: &Node,
     node: &Node,
     cluster_entrypoint: &ContactInfo,
     cluster_entrypoint: &ContactInfo,
@@ -988,8 +916,8 @@ pub fn main() {
                 .long("rpc-port")
                 .long("rpc-port")
                 .value_name("PORT")
                 .value_name("PORT")
                 .takes_value(true)
                 .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(
             Arg::with_name("private_rpc")
             Arg::with_name("private_rpc")
@@ -1715,11 +1643,6 @@ pub fn main() {
     let use_progress_bar = logfile.is_none();
     let use_progress_bar = logfile.is_none();
     let _logger_thread = start_logger(logfile);
     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
     let gossip_host = matches
         .value_of("gossip_host")
         .value_of("gossip_host")
         .map(|gossip_host| {
         .map(|gossip_host| {
@@ -1820,17 +1743,3 @@ pub fn main() {
     validator.join().expect("validator exit");
     validator.join().expect("validator exit");
     info!("Validator exiting..");
     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));
-    }
-}