Initial solana-test-validator command-line program

This commit is contained in:
Michael Vines 2020-12-08 23:18:27 -08:00 committed by mergify[bot]
parent 13db3eca9f
commit 0a9ff1dc9d
25 changed files with 786 additions and 348 deletions

5
Cargo.lock generated
View File

@ -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]]

View File

@ -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());

View File

@ -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();

View File

@ -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();

View File

@ -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());

View File

@ -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());

View File

@ -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());

View File

@ -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" }

View File

@ -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 { #[derive(Default)]
fn default() -> Self { pub struct TestValidatorStartConfig {
Self { pub rpc_config: JsonRpcConfig,
fee_rate_governor: FeeRateGovernor::default(), pub rpc_ports: Option<(u16, u16)>, // (JsonRpc, JsonRpcPubSub), None == random ports
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.),
}
}
} }
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,
} rpc_url: String,
tpu: SocketAddr,
impl Default for TestValidator { gossip: SocketAddr,
fn default() -> Self { validator: Validator,
Self::new(TestValidatorConfig::default()) vote_account_address: Pubkey,
}
} }
impl TestValidator { impl TestValidator {
pub fn with_no_fees() -> Self { /// The default test validator is intended to be generically suitable for unit testing.
Self::new(TestValidatorConfig { ///
fee_rate_governor: FeeRateGovernor::new(0, 0), /// It uses a unique temporary ledger that is deleted on `close` and randomly assigned ports.
rent: Rent { /// All test tokens will be minted into `mint_address`
lamports_per_byte_year: 1, ///
exemption_threshold: 1.0, /// This function panics on initialization failure.
..Rent::default() 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 { /// Create a `TestValidator` with no transaction fees and minimal rent.
Self::new(TestValidatorConfig { ///
fee_rate_governor: FeeRateGovernor::new(target_lamports_per_signature, 0), /// This function panics on initialization failure.
rent: Rent { pub fn with_no_fees(mint_address: Pubkey) -> Self {
lamports_per_byte_year: 1, let ledger_path = Self::initialize_ledger(
exemption_threshold: 1.0, None,
..Rent::default() 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 { /// Create a `TestValidator` with custom transaction fees and minimal rent.
use solana_ledger::genesis_utils::{ ///
create_genesis_config_with_leader_ex, GenesisConfigInfo, /// 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 { let initial_accounts = solana_program_test::programs::spl_programs(&rent);
mut genesis_config, let genesis_config = create_genesis_config_with_leader_ex(
mint_keypair,
voting_keypair: vote_account_keypair,
} = create_genesis_config_with_leader_ex(
mint_lamports, mint_lamports,
&node.info.id, &mint_address,
&Keypair::new(), &validator_identity_keypair.pubkey(),
&Keypair::new().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; let ledger_path = match ledger_path {
genesis_config.fee_rate_governor = fee_rate_governor; None => create_new_tmp_ledger!(&genesis_config).0,
Some(ledger_path) => {
let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_config); let _ = create_new_ledger(
ledger_path,
let config = ValidatorConfig { &genesis_config,
rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub)), MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
..ValidatorConfig::default() 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<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 = 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(), &validator_vote_account.pubkey(),
vec![Arc::new(vote_account_keypair)], 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 { /// Return the test validator's Gossip address
Keypair::from_bytes(&self.mint_keypair.to_bytes()).unwrap() 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 { /// Return the vote account address of the validator
self.genesis_hash
}
pub fn vote_account_address(&self) -> Pubkey { pub fn vote_account_address(&self) -> Pubkey {
self.vote_account_address self.vote_account_address
} }

View File

@ -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));
}
} }

View File

@ -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, commitment_config::CommitmentConfig,
signature::Signer, system_transaction, 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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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();

View File

@ -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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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();

View File

@ -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, bootstrap_validator_stake_lamports, create_genesis_config_with_leader, GenesisConfigInfo,
create_genesis_config_with_leader_ex, 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

View File

@ -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() { for (program_id, account) in programs::spl_programs(&Rent::default()).iter() {
bank.store_account( bank.store_account(program_id, &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,
},
)
} }
// User-supplied additional builtins // User-supplied additional builtins

View File

@ -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()
}

View File

@ -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 { mut initial_accounts: Vec<(Pubkey, Account)>,
let mint_keypair = Keypair::new(); ) -> 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 = [ initial_accounts.push((
( *mint_pubkey,
mint_keypair.pubkey(), Account::new(mint_lamports, 0, &system_program::id()),
Account::new(mint_lamports, 0, &system_program::id()), ));
), initial_accounts.push((
( *validator_pubkey,
*validator_pubkey, Account::new(validator_lamports, 0, &system_program::id()),
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));
validator_vote_account_keypair.pubkey(),
validator_vote_account,
),
(*validator_stake_account_pubkey, validator_stake_account),
]
.iter()
.cloned()
.collect();
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
genesis_config,
mint_keypair,
voting_keypair: Keypair::from_bytes(&validator_vote_account_keypair.to_bytes()).unwrap(),
}
} }

View File

@ -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

View File

@ -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;

View File

@ -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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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 = Keypair::new();
let alice = test_validator.mint_keypair(); 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 = Keypair::new();
let sender_keypair = test_validator.mint_keypair(); 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());

View File

@ -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();
} }

View File

@ -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"]

View File

@ -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 -- "$@"

View File

@ -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
validator/src/lib.rs Normal file
View File

@ -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))
}

View File

@ -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) .validator(solana_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"), .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));
}
}