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

View File

@ -2,188 +2,279 @@ use {
crate::{
cluster_info::Node,
gossip_service::discover_cluster,
rpc::JsonRpcConfig,
validator::{Validator, ValidatorConfig},
},
solana_ledger::create_new_tmp_ledger,
solana_ledger::{blockstore::create_new_ledger, create_new_tmp_ledger},
solana_runtime::{
bank_forks::{CompressionType, SnapshotConfig, SnapshotVersion},
genesis_utils::create_genesis_config_with_leader_ex,
hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
},
solana_sdk::{
fee_calculator::FeeRateGovernor,
hash::Hash,
native_token::sol_to_lamports,
pubkey::Pubkey,
rent::Rent,
signature::{Keypair, Signer},
signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
},
std::{
fs::remove_dir_all,
net::SocketAddr,
path::{Path, PathBuf},
sync::Arc,
},
std::{fs::remove_dir_all, net::SocketAddr, path::PathBuf, sync::Arc},
};
pub struct TestValidatorConfig {
pub struct TestValidatorGenesisConfig {
pub fee_rate_governor: FeeRateGovernor,
pub mint_lamports: u64,
pub mint_address: Pubkey,
pub rent: Rent,
pub validator_identity_keypair: Keypair,
pub validator_identity_lamports: u64,
pub validator_stake_lamports: u64,
}
impl Default for TestValidatorConfig {
fn default() -> Self {
Self {
fee_rate_governor: FeeRateGovernor::default(),
mint_lamports: sol_to_lamports(500_000_000.),
rent: Rent::default(),
validator_identity_keypair: Keypair::new(),
validator_identity_lamports: sol_to_lamports(500.),
validator_stake_lamports: sol_to_lamports(1.),
}
}
#[derive(Default)]
pub struct TestValidatorStartConfig {
pub rpc_config: JsonRpcConfig,
pub rpc_ports: Option<(u16, u16)>, // (JsonRpc, JsonRpcPubSub), None == random ports
}
pub struct TestValidator {
validator: Validator,
ledger_path: PathBuf,
preserve_ledger: bool,
genesis_hash: Hash,
mint_keypair: Keypair,
vote_account_address: Pubkey,
tpu: SocketAddr,
rpc_url: String,
rpc_pubsub_url: String,
}
impl Default for TestValidator {
fn default() -> Self {
Self::new(TestValidatorConfig::default())
}
rpc_url: String,
tpu: SocketAddr,
gossip: SocketAddr,
validator: Validator,
vote_account_address: Pubkey,
}
impl TestValidator {
pub fn with_no_fees() -> Self {
Self::new(TestValidatorConfig {
fee_rate_governor: FeeRateGovernor::new(0, 0),
rent: Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
..Rent::default()
/// The default test validator is intended to be generically suitable for unit testing.
///
/// It uses a unique temporary ledger that is deleted on `close` and randomly assigned ports.
/// All test tokens will be minted into `mint_address`
///
/// This function panics on initialization failure.
pub fn new(mint_address: Pubkey) -> Self {
let ledger_path = Self::initialize_ledger(
None,
TestValidatorGenesisConfig {
fee_rate_governor: FeeRateGovernor::default(),
mint_address,
rent: Rent::default(),
},
..TestValidatorConfig::default()
})
)
.unwrap();
Self::start(&ledger_path, TestValidatorStartConfig::default()).unwrap()
}
pub fn with_custom_fees(target_lamports_per_signature: u64) -> Self {
Self::new(TestValidatorConfig {
fee_rate_governor: FeeRateGovernor::new(target_lamports_per_signature, 0),
rent: Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
..Rent::default()
/// Create a `TestValidator` with no transaction fees and minimal rent.
///
/// This function panics on initialization failure.
pub fn with_no_fees(mint_address: Pubkey) -> Self {
let ledger_path = Self::initialize_ledger(
None,
TestValidatorGenesisConfig {
fee_rate_governor: FeeRateGovernor::new(0, 0),
mint_address,
rent: Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
..Rent::default()
},
},
..TestValidatorConfig::default()
})
)
.unwrap();
Self::start(&ledger_path, TestValidatorStartConfig::default()).unwrap()
}
pub fn new(config: TestValidatorConfig) -> Self {
use solana_ledger::genesis_utils::{
create_genesis_config_with_leader_ex, GenesisConfigInfo,
};
/// Create a `TestValidator` with custom transaction fees and minimal rent.
///
/// This function panics on initialization failure.
pub fn with_custom_fees(mint_address: Pubkey, target_lamports_per_signature: u64) -> Self {
let ledger_path = Self::initialize_ledger(
None,
TestValidatorGenesisConfig {
fee_rate_governor: FeeRateGovernor::new(target_lamports_per_signature, 0),
mint_address,
rent: Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
..Rent::default()
},
},
)
.unwrap();
Self::start(&ledger_path, TestValidatorStartConfig::default()).unwrap()
}
let TestValidatorConfig {
/// Initialize the test validator's ledger directory
///
/// If `ledger_path` is `None`, a temporary ledger will be created. Otherwise the ledger will
/// be initialized in the provided directory.
///
/// Returns the path to the ledger directory.
pub fn initialize_ledger(
ledger_path: Option<&Path>,
config: TestValidatorGenesisConfig,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
let TestValidatorGenesisConfig {
fee_rate_governor,
mint_lamports,
mint_address,
rent,
validator_identity_keypair,
validator_identity_lamports,
validator_stake_lamports,
} = config;
let validator_identity_keypair = Arc::new(validator_identity_keypair);
let node = Node::new_localhost_with_pubkey(&validator_identity_keypair.pubkey());
let validator_identity_keypair = Keypair::new();
let validator_vote_account = Keypair::new();
let validator_stake_account = Keypair::new();
let validator_identity_lamports = sol_to_lamports(500.);
let validator_stake_lamports = sol_to_lamports(1_000_000.);
let mint_lamports = sol_to_lamports(500_000_000.);
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
voting_keypair: vote_account_keypair,
} = create_genesis_config_with_leader_ex(
let initial_accounts = solana_program_test::programs::spl_programs(&rent);
let genesis_config = create_genesis_config_with_leader_ex(
mint_lamports,
&node.info.id,
&Keypair::new(),
&Keypair::new().pubkey(),
&mint_address,
&validator_identity_keypair.pubkey(),
&validator_vote_account.pubkey(),
&validator_stake_account.pubkey(),
validator_stake_lamports,
validator_identity_lamports,
fee_rate_governor,
rent,
solana_sdk::genesis_config::ClusterType::Development,
initial_accounts,
);
genesis_config.rent = rent;
genesis_config.fee_rate_governor = fee_rate_governor;
let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_config);
let config = ValidatorConfig {
rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub)),
..ValidatorConfig::default()
let ledger_path = match ledger_path {
None => create_new_tmp_ledger!(&genesis_config).0,
Some(ledger_path) => {
let _ = create_new_ledger(
ledger_path,
&genesis_config,
MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
solana_ledger::blockstore_db::AccessType::PrimaryOnly,
)
.map_err(|err| {
format!(
"Failed to create ledger at {}: {}",
ledger_path.display(),
err
)
})?;
ledger_path.to_path_buf()
}
};
let vote_account_address = vote_account_keypair.pubkey();
write_keypair_file(
&validator_identity_keypair,
ledger_path.join("validator-keypair.json").to_str().unwrap(),
)?;
write_keypair_file(
&validator_vote_account,
ledger_path
.join("vote-account-keypair.json")
.to_str()
.unwrap(),
)?;
Ok(ledger_path)
}
/// Starts a TestValidator at the provided ledger directory
pub fn start(
ledger_path: &Path,
config: TestValidatorStartConfig,
) -> Result<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_pubsub_url = format!("ws://{}/", node.info.rpc_pubsub);
let tpu = node.info.tpu;
let gossip = node.info.gossip;
let validator_config = ValidatorConfig {
rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub)),
rpc_config: config.rpc_config,
accounts_hash_interval_slots: 100,
account_paths: vec![ledger_path.join("accounts")],
poh_verify: false, // Skip PoH verification of ledger on startup for speed
snapshot_config: Some(SnapshotConfig {
snapshot_interval_slots: 100,
snapshot_path: ledger_path.join("snapshot"),
snapshot_package_output_path: ledger_path.to_path_buf(),
compression: CompressionType::NoCompression,
snapshot_version: SnapshotVersion::default(),
}),
..ValidatorConfig::default()
};
let validator = Validator::new(
node,
&validator_identity_keypair,
&Arc::new(validator_identity_keypair),
&ledger_path,
&vote_account_keypair.pubkey(),
vec![Arc::new(vote_account_keypair)],
&validator_vote_account.pubkey(),
vec![Arc::new(validator_vote_account)],
None,
&config,
&validator_config,
);
// Needed to avoid panics in `solana-responder-gossip` in tests that create a number of
// test validators concurrently...
discover_cluster(&gossip, 1).expect("TestValidator startup failed");
TestValidator {
Ok(TestValidator {
ledger_path: ledger_path.to_path_buf(),
rpc_pubsub_url,
rpc_url,
gossip,
tpu,
validator,
vote_account_address,
mint_keypair,
ledger_path,
genesis_hash: blockhash,
tpu,
rpc_url,
rpc_pubsub_url,
preserve_ledger: false,
}
})
}
/// Stop the test validator and delete its ledger directory
pub fn close(self) {
self.validator.close().unwrap();
if !self.preserve_ledger {
remove_dir_all(&self.ledger_path).unwrap();
}
remove_dir_all(&self.ledger_path).unwrap();
}
/// Return the test validator's TPU address
pub fn tpu(&self) -> &SocketAddr {
&self.tpu
}
pub fn mint_keypair(&self) -> Keypair {
Keypair::from_bytes(&self.mint_keypair.to_bytes()).unwrap()
/// Return the test validator's Gossip address
pub fn gossip(&self) -> &SocketAddr {
&self.gossip
}
/// Return the test validator's JSON RPC URL
pub fn rpc_url(&self) -> String {
self.rpc_url.clone()
}
/// Return the test validator's JSON RPC PubSub URL
pub fn rpc_pubsub_url(&self) -> String {
self.rpc_pubsub_url.clone()
}
pub fn genesis_hash(&self) -> Hash {
self.genesis_hash
}
/// Return the vote account address of the validator
pub fn vote_account_address(&self) -> Pubkey {
self.vote_account_address
}

View File

@ -490,6 +490,13 @@ impl Validator {
let (snapshot_packager_service, snapshot_config_and_package_sender) =
if let Some(snapshot_config) = config.snapshot_config.clone() {
if is_snapshot_config_invalid(
snapshot_config.snapshot_interval_slots,
config.accounts_hash_interval_slots,
) {
error!("Snapshot config is invalid");
}
// Start a snapshot packaging service
let (sender, receiver) = channel();
let snapshot_packager_service =
@ -1218,6 +1225,15 @@ fn cleanup_accounts_path(account_path: &std::path::Path) {
}
}
pub fn is_snapshot_config_invalid(
snapshot_interval_slots: u64,
accounts_hash_interval_slots: u64,
) -> bool {
snapshot_interval_slots != 0
&& (snapshot_interval_slots < accounts_hash_interval_slots
|| snapshot_interval_slots % accounts_hash_interval_slots != 0)
}
#[cfg(test)]
mod tests {
use super::*;
@ -1388,4 +1404,13 @@ mod tests {
rpc_override_health_check
));
}
#[test]
fn test_interval_check() {
assert!(!is_snapshot_config_invalid(0, 100));
assert!(is_snapshot_config_invalid(1, 100));
assert!(is_snapshot_config_invalid(230, 100));
assert!(!is_snapshot_config_invalid(500, 100));
assert!(!is_snapshot_config_invalid(5, 5));
}
}