Rework TestValidator API to be more like ProgramTest
This commit is contained in:
parent
5237da4e01
commit
9f2d154588
|
@ -5094,6 +5094,7 @@ dependencies = [
|
||||||
name = "solana-validator"
|
name = "solana-validator"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.12.3",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"console",
|
"console",
|
||||||
|
|
|
@ -13,35 +13,165 @@ use {
|
||||||
hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
|
hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE,
|
||||||
},
|
},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
|
account::Account,
|
||||||
clock::DEFAULT_MS_PER_SLOT,
|
clock::DEFAULT_MS_PER_SLOT,
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
fee_calculator::FeeRateGovernor,
|
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||||
|
hash::Hash,
|
||||||
native_token::sol_to_lamports,
|
native_token::sol_to_lamports,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
|
signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
fs::remove_dir_all,
|
collections::HashMap, fs::remove_dir_all, net::SocketAddr, path::PathBuf, sync::Arc,
|
||||||
net::SocketAddr,
|
thread::sleep, time::Duration,
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::Arc,
|
|
||||||
thread::sleep,
|
|
||||||
time::Duration,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TestValidatorGenesisConfig {
|
#[derive(Clone)]
|
||||||
pub fee_rate_governor: FeeRateGovernor,
|
pub struct ProgramInfo {
|
||||||
pub mint_address: Pubkey,
|
pub program_id: Pubkey,
|
||||||
pub rent: Rent,
|
pub loader: Pubkey,
|
||||||
|
pub program_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct TestValidatorStartConfig {
|
pub struct TestValidatorGenesis {
|
||||||
pub preserve_ledger: bool,
|
fee_rate_governor: FeeRateGovernor,
|
||||||
pub rpc_config: JsonRpcConfig,
|
ledger_path: Option<PathBuf>,
|
||||||
pub rpc_ports: Option<(u16, u16)>, // (JsonRpc, JsonRpcPubSub), None == random ports
|
rent: Rent,
|
||||||
|
rpc_config: JsonRpcConfig,
|
||||||
|
rpc_ports: Option<(u16, u16)>, // (JsonRpc, JsonRpcPubSub), None == random ports
|
||||||
|
accounts: HashMap<Pubkey, Account>,
|
||||||
|
programs: Vec<ProgramInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestValidatorGenesis {
|
||||||
|
pub fn ledger_path<P: Into<PathBuf>>(&mut self, ledger_path: P) -> &mut Self {
|
||||||
|
self.ledger_path = Some(ledger_path.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fee_rate_governor(&mut self, fee_rate_governor: FeeRateGovernor) -> &mut Self {
|
||||||
|
self.fee_rate_governor = fee_rate_governor;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rent(&mut self, rent: Rent) -> &mut Self {
|
||||||
|
self.rent = rent;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rpc_config(&mut self, rpc_config: JsonRpcConfig) -> &mut Self {
|
||||||
|
self.rpc_config = rpc_config;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rpc_port(&mut self, rpc_port: u16) -> &mut Self {
|
||||||
|
self.rpc_ports = Some((rpc_port, rpc_port + 1));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an account to the test environment
|
||||||
|
pub fn add_account(&mut self, address: Pubkey, account: Account) -> &mut Self {
|
||||||
|
self.accounts.insert(address, account);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an account to the test environment with the account data in the provided `filename`
|
||||||
|
pub fn add_account_with_file_data(
|
||||||
|
&mut self,
|
||||||
|
address: Pubkey,
|
||||||
|
lamports: u64,
|
||||||
|
owner: Pubkey,
|
||||||
|
filename: &str,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.add_account(
|
||||||
|
address,
|
||||||
|
Account {
|
||||||
|
lamports,
|
||||||
|
data: solana_program_test::read_file(
|
||||||
|
solana_program_test::find_file(filename).unwrap_or_else(|| {
|
||||||
|
panic!("Unable to locate {}", filename);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
owner,
|
||||||
|
executable: false,
|
||||||
|
rent_epoch: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an account to the test environment with the account data in the provided as a base 64
|
||||||
|
/// string
|
||||||
|
pub fn add_account_with_base64_data(
|
||||||
|
&mut self,
|
||||||
|
address: Pubkey,
|
||||||
|
lamports: u64,
|
||||||
|
owner: Pubkey,
|
||||||
|
data_base64: &str,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.add_account(
|
||||||
|
address,
|
||||||
|
Account {
|
||||||
|
lamports,
|
||||||
|
data: base64::decode(data_base64)
|
||||||
|
.unwrap_or_else(|err| panic!("Failed to base64 decode: {}", err)),
|
||||||
|
owner,
|
||||||
|
executable: false,
|
||||||
|
rent_epoch: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a BPF program to the test environment.
|
||||||
|
///
|
||||||
|
/// `program_name` will also used to locate the BPF shared object in the current or fixtures
|
||||||
|
/// directory.
|
||||||
|
pub fn add_program(&mut self, program_name: &str, program_id: Pubkey) -> &mut Self {
|
||||||
|
let program_path = solana_program_test::find_file(&format!("{}.so", program_name))
|
||||||
|
.unwrap_or_else(|| panic!("Unable to locate program {}", program_name));
|
||||||
|
|
||||||
|
self.programs.push(ProgramInfo {
|
||||||
|
program_id,
|
||||||
|
loader: solana_sdk::bpf_loader::id(),
|
||||||
|
program_path,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a list of programs to the test environment.
|
||||||
|
///pub fn add_programs_with_path<'a>(&'a mut self, programs: &[ProgramInfo]) -> &'a mut Self {
|
||||||
|
pub fn add_programs_with_path(&mut self, programs: &[ProgramInfo]) -> &mut Self {
|
||||||
|
for program in programs {
|
||||||
|
self.programs.push(program.clone());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a test validator with the address of the mint account that will receive tokens
|
||||||
|
/// created at genesis.
|
||||||
|
///
|
||||||
|
pub fn start_with_mint_address(
|
||||||
|
&self,
|
||||||
|
mint_address: Pubkey,
|
||||||
|
) -> Result<TestValidator, Box<dyn std::error::Error>> {
|
||||||
|
TestValidator::start(mint_address, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a test validator
|
||||||
|
///
|
||||||
|
/// Returns a new `TestValidator` as well as the keypair for the mint account that will receive tokens
|
||||||
|
/// created at genesis.
|
||||||
|
///
|
||||||
|
/// This function panics on initialization failure.
|
||||||
|
pub fn start(&self) -> (TestValidator, Keypair) {
|
||||||
|
let mint_keypair = Keypair::new();
|
||||||
|
TestValidator::start(mint_keypair.pubkey(), self)
|
||||||
|
.map(|test_validator| (test_validator, mint_keypair))
|
||||||
|
.expect("Test validator failed to start")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TestValidator {
|
pub struct TestValidator {
|
||||||
|
@ -56,106 +186,92 @@ pub struct TestValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestValidator {
|
impl TestValidator {
|
||||||
/// The default test validator is intended to be generically suitable for unit testing.
|
/// Create and start a `TestValidator` with no transaction fees and minimal rent.
|
||||||
///
|
|
||||||
/// 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(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
Self::start(&ledger_path, TestValidatorStartConfig::default()).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a `TestValidator` with no transaction fees and minimal rent.
|
|
||||||
///
|
///
|
||||||
/// This function panics on initialization failure.
|
/// This function panics on initialization failure.
|
||||||
pub fn with_no_fees(mint_address: Pubkey) -> Self {
|
pub fn with_no_fees(mint_address: Pubkey) -> Self {
|
||||||
let ledger_path = Self::initialize_ledger(
|
TestValidatorGenesis::default()
|
||||||
None,
|
.fee_rate_governor(FeeRateGovernor::new(0, 0))
|
||||||
TestValidatorGenesisConfig {
|
.rent(Rent {
|
||||||
fee_rate_governor: FeeRateGovernor::new(0, 0),
|
lamports_per_byte_year: 1,
|
||||||
mint_address,
|
exemption_threshold: 1.0,
|
||||||
rent: Rent {
|
..Rent::default()
|
||||||
lamports_per_byte_year: 1,
|
})
|
||||||
exemption_threshold: 1.0,
|
.start_with_mint_address(mint_address)
|
||||||
..Rent::default()
|
.expect("validator start failed")
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
Self::start(&ledger_path, TestValidatorStartConfig::default()).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `TestValidator` with custom transaction fees and minimal rent.
|
/// Create and start a `TestValidator` with custom transaction fees and minimal rent.
|
||||||
///
|
///
|
||||||
/// This function panics on initialization failure.
|
/// This function panics on initialization failure.
|
||||||
pub fn with_custom_fees(mint_address: Pubkey, target_lamports_per_signature: u64) -> Self {
|
pub fn with_custom_fees(mint_address: Pubkey, target_lamports_per_signature: u64) -> Self {
|
||||||
let ledger_path = Self::initialize_ledger(
|
TestValidatorGenesis::default()
|
||||||
None,
|
.fee_rate_governor(FeeRateGovernor::new(target_lamports_per_signature, 0))
|
||||||
TestValidatorGenesisConfig {
|
.rent(Rent {
|
||||||
fee_rate_governor: FeeRateGovernor::new(target_lamports_per_signature, 0),
|
lamports_per_byte_year: 1,
|
||||||
mint_address,
|
exemption_threshold: 1.0,
|
||||||
rent: Rent {
|
..Rent::default()
|
||||||
lamports_per_byte_year: 1,
|
})
|
||||||
exemption_threshold: 1.0,
|
.start_with_mint_address(mint_address)
|
||||||
..Rent::default()
|
.expect("validator start failed")
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
Self::start(&ledger_path, TestValidatorStartConfig::default()).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the test validator's ledger directory
|
/// Initialize the ledger directory
|
||||||
///
|
///
|
||||||
/// If `ledger_path` is `None`, a temporary ledger will be created. Otherwise the ledger will
|
/// If `ledger_path` is `None`, a temporary ledger will be created. Otherwise the ledger will
|
||||||
/// be initialized in the provided directory.
|
/// be initialized in the provided directory if it doesn't already exist.
|
||||||
///
|
///
|
||||||
/// Returns the path to the ledger directory.
|
/// Returns the path to the ledger directory.
|
||||||
pub fn initialize_ledger(
|
fn initialize_ledger(
|
||||||
ledger_path: Option<&Path>,
|
mint_address: Pubkey,
|
||||||
config: TestValidatorGenesisConfig,
|
config: &TestValidatorGenesis,
|
||||||
) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
let TestValidatorGenesisConfig {
|
let validator_identity = Keypair::new();
|
||||||
fee_rate_governor,
|
|
||||||
mint_address,
|
|
||||||
rent,
|
|
||||||
} = config;
|
|
||||||
|
|
||||||
let validator_identity_keypair = Keypair::new();
|
|
||||||
let validator_vote_account = Keypair::new();
|
let validator_vote_account = Keypair::new();
|
||||||
let validator_stake_account = Keypair::new();
|
let validator_stake_account = Keypair::new();
|
||||||
let validator_identity_lamports = sol_to_lamports(500.);
|
let validator_identity_lamports = sol_to_lamports(500.);
|
||||||
let validator_stake_lamports = sol_to_lamports(1_000_000.);
|
let validator_stake_lamports = sol_to_lamports(1_000_000.);
|
||||||
let mint_lamports = sol_to_lamports(500_000_000.);
|
let mint_lamports = sol_to_lamports(500_000_000.);
|
||||||
|
|
||||||
let initial_accounts = solana_program_test::programs::spl_programs(&rent);
|
let mut accounts = config.accounts.clone();
|
||||||
|
for (address, account) in solana_program_test::programs::spl_programs(&config.rent) {
|
||||||
|
accounts.entry(address).or_insert(account);
|
||||||
|
}
|
||||||
|
for program in &config.programs {
|
||||||
|
let data = solana_program_test::read_file(&program.program_path);
|
||||||
|
accounts.insert(
|
||||||
|
program.program_id,
|
||||||
|
Account {
|
||||||
|
lamports: Rent::default().minimum_balance(data.len()).min(1),
|
||||||
|
data,
|
||||||
|
owner: program.loader,
|
||||||
|
executable: true,
|
||||||
|
rent_epoch: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let genesis_config = create_genesis_config_with_leader_ex(
|
let genesis_config = create_genesis_config_with_leader_ex(
|
||||||
mint_lamports,
|
mint_lamports,
|
||||||
&mint_address,
|
&mint_address,
|
||||||
&validator_identity_keypair.pubkey(),
|
&validator_identity.pubkey(),
|
||||||
&validator_vote_account.pubkey(),
|
&validator_vote_account.pubkey(),
|
||||||
&validator_stake_account.pubkey(),
|
&validator_stake_account.pubkey(),
|
||||||
validator_stake_lamports,
|
validator_stake_lamports,
|
||||||
validator_identity_lamports,
|
validator_identity_lamports,
|
||||||
fee_rate_governor,
|
config.fee_rate_governor.clone(),
|
||||||
rent,
|
config.rent,
|
||||||
solana_sdk::genesis_config::ClusterType::Development,
|
solana_sdk::genesis_config::ClusterType::Development,
|
||||||
initial_accounts,
|
accounts.into_iter().collect(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let ledger_path = match ledger_path {
|
let ledger_path = match &config.ledger_path {
|
||||||
None => create_new_tmp_ledger!(&genesis_config).0,
|
None => create_new_tmp_ledger!(&genesis_config).0,
|
||||||
Some(ledger_path) => {
|
Some(ledger_path) => {
|
||||||
|
if ledger_path.join("validator-keypair.json").exists() {
|
||||||
|
return Ok(ledger_path.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
let _ = create_new_ledger(
|
let _ = create_new_ledger(
|
||||||
ledger_path,
|
ledger_path,
|
||||||
&genesis_config,
|
&genesis_config,
|
||||||
|
@ -174,7 +290,7 @@ impl TestValidator {
|
||||||
};
|
};
|
||||||
|
|
||||||
write_keypair_file(
|
write_keypair_file(
|
||||||
&validator_identity_keypair,
|
&validator_identity,
|
||||||
ledger_path.join("validator-keypair.json").to_str().unwrap(),
|
ledger_path.join("validator-keypair.json").to_str().unwrap(),
|
||||||
)?;
|
)?;
|
||||||
write_keypair_file(
|
write_keypair_file(
|
||||||
|
@ -189,11 +305,14 @@ impl TestValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts a TestValidator at the provided ledger directory
|
/// Starts a TestValidator at the provided ledger directory
|
||||||
pub fn start(
|
fn start(
|
||||||
ledger_path: &Path,
|
mint_address: Pubkey,
|
||||||
config: TestValidatorStartConfig,
|
config: &TestValidatorGenesis,
|
||||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let validator_identity_keypair =
|
let preserve_ledger = config.ledger_path.is_some();
|
||||||
|
let ledger_path = TestValidator::initialize_ledger(mint_address, config)?;
|
||||||
|
|
||||||
|
let validator_identity =
|
||||||
read_keypair_file(ledger_path.join("validator-keypair.json").to_str().unwrap())?;
|
read_keypair_file(ledger_path.join("validator-keypair.json").to_str().unwrap())?;
|
||||||
let validator_vote_account = read_keypair_file(
|
let validator_vote_account = read_keypair_file(
|
||||||
ledger_path
|
ledger_path
|
||||||
|
@ -202,7 +321,7 @@ impl TestValidator {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut node = Node::new_localhost_with_pubkey(&validator_identity_keypair.pubkey());
|
let mut node = Node::new_localhost_with_pubkey(&validator_identity.pubkey());
|
||||||
if let Some((rpc, rpc_pubsub)) = config.rpc_ports {
|
if let Some((rpc, rpc_pubsub)) = config.rpc_ports {
|
||||||
node.info.rpc = SocketAddr::new(node.info.gossip.ip(), rpc);
|
node.info.rpc = SocketAddr::new(node.info.gossip.ip(), rpc);
|
||||||
node.info.rpc_pubsub = SocketAddr::new(node.info.gossip.ip(), rpc_pubsub);
|
node.info.rpc_pubsub = SocketAddr::new(node.info.gossip.ip(), rpc_pubsub);
|
||||||
|
@ -216,7 +335,7 @@ impl TestValidator {
|
||||||
|
|
||||||
let validator_config = ValidatorConfig {
|
let validator_config = ValidatorConfig {
|
||||||
rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub)),
|
rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub)),
|
||||||
rpc_config: config.rpc_config,
|
rpc_config: config.rpc_config.clone(),
|
||||||
accounts_hash_interval_slots: 100,
|
accounts_hash_interval_slots: 100,
|
||||||
account_paths: vec![ledger_path.join("accounts")],
|
account_paths: vec![ledger_path.join("accounts")],
|
||||||
poh_verify: false, // Skip PoH verification of ledger on startup for speed
|
poh_verify: false, // Skip PoH verification of ledger on startup for speed
|
||||||
|
@ -232,7 +351,7 @@ impl TestValidator {
|
||||||
|
|
||||||
let validator = Some(Validator::new(
|
let validator = Some(Validator::new(
|
||||||
node,
|
node,
|
||||||
&Arc::new(validator_identity_keypair),
|
&Arc::new(validator_identity),
|
||||||
&ledger_path,
|
&ledger_path,
|
||||||
&validator_vote_account.pubkey(),
|
&validator_vote_account.pubkey(),
|
||||||
vec![Arc::new(validator_vote_account)],
|
vec![Arc::new(validator_vote_account)],
|
||||||
|
@ -244,12 +363,12 @@ impl TestValidator {
|
||||||
// test validators concurrently...
|
// test validators concurrently...
|
||||||
discover_cluster(&gossip, 1).expect("TestValidator startup failed");
|
discover_cluster(&gossip, 1).expect("TestValidator startup failed");
|
||||||
|
|
||||||
// This is a hack to delay until the single gossip commitment fees are non-zero for test
|
// This is a hack to delay until the fees are non-zero for test consistency
|
||||||
// consistency
|
// (fees from genesis are zero until the first block with a transaction in it is completed
|
||||||
// (fees from genesis are zero until the first block with a transaction in it is completed)
|
// due to a bug in the Bank)
|
||||||
{
|
{
|
||||||
let rpc_client =
|
let rpc_client =
|
||||||
RpcClient::new_with_commitment(rpc_url.clone(), CommitmentConfig::single_gossip());
|
RpcClient::new_with_commitment(rpc_url.clone(), CommitmentConfig::recent());
|
||||||
let fee_rate_governor = rpc_client
|
let fee_rate_governor = rpc_client
|
||||||
.get_fee_rate_governor()
|
.get_fee_rate_governor()
|
||||||
.expect("get_fee_rate_governor")
|
.expect("get_fee_rate_governor")
|
||||||
|
@ -268,8 +387,8 @@ impl TestValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(TestValidator {
|
Ok(TestValidator {
|
||||||
ledger_path: ledger_path.to_path_buf(),
|
ledger_path,
|
||||||
preserve_ledger: false,
|
preserve_ledger,
|
||||||
rpc_pubsub_url,
|
rpc_pubsub_url,
|
||||||
rpc_url,
|
rpc_url,
|
||||||
gossip,
|
gossip,
|
||||||
|
@ -279,30 +398,42 @@ impl TestValidator {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the test validator's TPU address
|
/// Return the validator's TPU address
|
||||||
pub fn tpu(&self) -> &SocketAddr {
|
pub fn tpu(&self) -> &SocketAddr {
|
||||||
&self.tpu
|
&self.tpu
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the test validator's Gossip address
|
/// Return the validator's Gossip address
|
||||||
pub fn gossip(&self) -> &SocketAddr {
|
pub fn gossip(&self) -> &SocketAddr {
|
||||||
&self.gossip
|
&self.gossip
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the test validator's JSON RPC URL
|
/// Return the 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
|
/// Return the 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the vote account address of the validator
|
/// Return the validator's vote account address
|
||||||
pub fn vote_account_address(&self) -> Pubkey {
|
pub fn vote_account_address(&self) -> Pubkey {
|
||||||
self.vote_account_address
|
self.vote_account_address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return an RpcClient for the validator. As a convenience, also return a recent blockhash and
|
||||||
|
/// associated fee calculator
|
||||||
|
pub fn rpc_client(&self) -> (RpcClient, Hash, FeeCalculator) {
|
||||||
|
let rpc_client =
|
||||||
|
RpcClient::new_with_commitment(self.rpc_url.clone(), CommitmentConfig::recent());
|
||||||
|
let (recent_blockhash, fee_calculator) = rpc_client
|
||||||
|
.get_recent_blockhash()
|
||||||
|
.expect("get_recent_blockhash");
|
||||||
|
|
||||||
|
(rpc_client, recent_blockhash, fee_calculator)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TestValidator {
|
impl Drop for TestValidator {
|
||||||
|
|
|
@ -256,14 +256,14 @@ pub fn request_airdrop_transaction(
|
||||||
Ok(transaction)
|
Ok(transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For integration tests. Listens on random open port and reports port to Sender.
|
pub fn run_local_faucet_with_port(
|
||||||
pub fn run_local_faucet(
|
|
||||||
faucet_keypair: Keypair,
|
faucet_keypair: Keypair,
|
||||||
sender: Sender<SocketAddr>,
|
sender: Sender<SocketAddr>,
|
||||||
per_time_cap: Option<u64>,
|
per_time_cap: Option<u64>,
|
||||||
|
port: u16,
|
||||||
) {
|
) {
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let faucet_addr = socketaddr!(0, 0);
|
let faucet_addr = socketaddr!(0, port);
|
||||||
let faucet = Arc::new(Mutex::new(Faucet::new(
|
let faucet = Arc::new(Mutex::new(Faucet::new(
|
||||||
faucet_keypair,
|
faucet_keypair,
|
||||||
None,
|
None,
|
||||||
|
@ -274,6 +274,15 @@ pub fn run_local_faucet(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For integration tests. Listens on random open port and reports port to Sender.
|
||||||
|
pub fn run_local_faucet(
|
||||||
|
faucet_keypair: Keypair,
|
||||||
|
sender: Sender<SocketAddr>,
|
||||||
|
per_time_cap: Option<u64>,
|
||||||
|
) {
|
||||||
|
run_local_faucet_with_port(faucet_keypair, sender, per_time_cap, 0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_faucet(
|
pub fn run_faucet(
|
||||||
faucet: Arc<Mutex<Faucet>>,
|
faucet: Arc<Mutex<Faucet>>,
|
||||||
faucet_addr: SocketAddr,
|
faucet_addr: SocketAddr,
|
||||||
|
|
|
@ -338,7 +338,17 @@ impl program_stubs::SyscallStubs for SyscallStubs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_file(filename: &str, search_path: &[PathBuf]) -> Option<PathBuf> {
|
pub fn find_file(filename: &str) -> Option<PathBuf> {
|
||||||
|
let mut search_path = vec![];
|
||||||
|
if let Ok(bpf_out_dir) = std::env::var("BPF_OUT_DIR") {
|
||||||
|
search_path.push(PathBuf::from(bpf_out_dir));
|
||||||
|
}
|
||||||
|
search_path.push(PathBuf::from("tests/fixtures"));
|
||||||
|
if let Ok(dir) = std::env::current_dir() {
|
||||||
|
search_path.push(dir);
|
||||||
|
}
|
||||||
|
trace!("search path: {:?}", search_path);
|
||||||
|
|
||||||
for path in search_path {
|
for path in search_path {
|
||||||
let candidate = path.join(&filename);
|
let candidate = path.join(&filename);
|
||||||
if candidate.exists() {
|
if candidate.exists() {
|
||||||
|
@ -348,7 +358,7 @@ fn find_file(filename: &str, search_path: &[PathBuf]) -> Option<PathBuf> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
|
pub fn read_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let mut file = File::open(path)
|
let mut file = File::open(path)
|
||||||
.unwrap_or_else(|err| panic!("Failed to open \"{}\": {}", path.display(), err));
|
.unwrap_or_else(|err| panic!("Failed to open \"{}\": {}", path.display(), err));
|
||||||
|
@ -364,7 +374,6 @@ pub struct ProgramTest {
|
||||||
builtins: Vec<Builtin>,
|
builtins: Vec<Builtin>,
|
||||||
bpf_compute_max_units: Option<u64>,
|
bpf_compute_max_units: Option<u64>,
|
||||||
prefer_bpf: bool,
|
prefer_bpf: bool,
|
||||||
search_path: Vec<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProgramTest {
|
impl Default for ProgramTest {
|
||||||
|
@ -388,25 +397,13 @@ impl Default for ProgramTest {
|
||||||
solana_runtime::system_instruction_processor=trace,\
|
solana_runtime::system_instruction_processor=trace,\
|
||||||
solana_program_test=info",
|
solana_program_test=info",
|
||||||
);
|
);
|
||||||
let mut prefer_bpf = false;
|
let prefer_bpf = std::env::var("BPF_OUT_DIR").is_ok();
|
||||||
|
|
||||||
let mut search_path = vec![];
|
|
||||||
if let Ok(bpf_out_dir) = std::env::var("BPF_OUT_DIR") {
|
|
||||||
prefer_bpf = true;
|
|
||||||
search_path.push(PathBuf::from(bpf_out_dir));
|
|
||||||
}
|
|
||||||
search_path.push(PathBuf::from("tests/fixtures"));
|
|
||||||
if let Ok(dir) = std::env::current_dir() {
|
|
||||||
search_path.push(dir);
|
|
||||||
}
|
|
||||||
debug!("search path: {:?}", search_path);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
accounts: vec![],
|
accounts: vec![],
|
||||||
builtins: vec![],
|
builtins: vec![],
|
||||||
bpf_compute_max_units: None,
|
bpf_compute_max_units: None,
|
||||||
prefer_bpf,
|
prefer_bpf,
|
||||||
search_path,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -449,7 +446,7 @@ impl ProgramTest {
|
||||||
address,
|
address,
|
||||||
Account {
|
Account {
|
||||||
lamports,
|
lamports,
|
||||||
data: read_file(find_file(filename, &self.search_path).unwrap_or_else(|| {
|
data: read_file(find_file(filename).unwrap_or_else(|| {
|
||||||
panic!("Unable to locate {}", filename);
|
panic!("Unable to locate {}", filename);
|
||||||
})),
|
})),
|
||||||
owner,
|
owner,
|
||||||
|
@ -495,7 +492,7 @@ impl ProgramTest {
|
||||||
process_instruction: Option<ProcessInstructionWithContext>,
|
process_instruction: Option<ProcessInstructionWithContext>,
|
||||||
) {
|
) {
|
||||||
let loader = solana_program::bpf_loader::id();
|
let loader = solana_program::bpf_loader::id();
|
||||||
let program_file = find_file(&format!("{}.so", program_name), &self.search_path);
|
let program_file = find_file(&format!("{}.so", program_name));
|
||||||
|
|
||||||
if process_instruction.is_none() && program_file.is_none() {
|
if process_instruction.is_none() && program_file.is_none() {
|
||||||
panic!("Unable to add program {} ({})", program_name, program_id);
|
panic!("Unable to add program {} ({})", program_name, program_id);
|
||||||
|
|
|
@ -10,6 +10,7 @@ homepage = "https://solana.com/"
|
||||||
default-run = "solana-validator"
|
default-run = "solana-validator"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
base64 = "0.12.3"
|
||||||
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"
|
||||||
|
|
|
@ -5,19 +5,25 @@ use {
|
||||||
solana_clap_utils::{input_parsers::pubkey_of, input_validators::is_pubkey},
|
solana_clap_utils::{input_parsers::pubkey_of, input_validators::is_pubkey},
|
||||||
solana_client::{client_error, rpc_client::RpcClient},
|
solana_client::{client_error, rpc_client::RpcClient},
|
||||||
solana_core::rpc::JsonRpcConfig,
|
solana_core::rpc::JsonRpcConfig,
|
||||||
|
solana_faucet::faucet::{run_local_faucet_with_port, FAUCET_PORT},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
|
account::Account,
|
||||||
clock::{Slot, DEFAULT_TICKS_PER_SLOT, MS_PER_TICK},
|
clock::{Slot, DEFAULT_TICKS_PER_SLOT, MS_PER_TICK},
|
||||||
commitment_config::CommitmentConfig,
|
commitment_config::CommitmentConfig,
|
||||||
fee_calculator::FeeRateGovernor,
|
native_token::sol_to_lamports,
|
||||||
rent::Rent,
|
pubkey::Pubkey,
|
||||||
rpc_port,
|
rpc_port,
|
||||||
signature::{read_keypair_file, Signer},
|
signature::{read_keypair_file, write_keypair_file, Keypair, Signer},
|
||||||
|
system_program,
|
||||||
},
|
},
|
||||||
solana_validator::{start_logger, test_validator::*},
|
solana_validator::{start_logger, test_validator::*},
|
||||||
std::{
|
std::{
|
||||||
|
fs,
|
||||||
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::exit,
|
process::exit,
|
||||||
thread::sleep,
|
sync::mpsc::channel,
|
||||||
|
thread,
|
||||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -47,7 +53,8 @@ fn println_name_value(name: &str, value: &str) {
|
||||||
fn main() {
|
fn main() {
|
||||||
let default_rpc_port = rpc_port::DEFAULT_RPC_PORT.to_string();
|
let default_rpc_port = rpc_port::DEFAULT_RPC_PORT.to_string();
|
||||||
|
|
||||||
let matches = App::new("solana-test-validator").about("Test Validator")
|
let matches = App::new("solana-test-validator")
|
||||||
|
.about("Test Validator")
|
||||||
.version(solana_version::version!())
|
.version(solana_version::version!())
|
||||||
.arg({
|
.arg({
|
||||||
let arg = Arg::with_name("config_file")
|
let arg = Arg::with_name("config_file")
|
||||||
|
@ -68,7 +75,10 @@ fn main() {
|
||||||
.value_name("PUBKEY")
|
.value_name("PUBKEY")
|
||||||
.validator(is_pubkey)
|
.validator(is_pubkey)
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Address of the mint account that will receive all the initial tokens [default: client keypair]"),
|
.help(
|
||||||
|
"Address of the mint account that will receive tokens \
|
||||||
|
created at genesis [default: client keypair]",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("ledger_path")
|
Arg::with_name("ledger_path")
|
||||||
|
@ -86,14 +96,14 @@ fn main() {
|
||||||
.long("quiet")
|
.long("quiet")
|
||||||
.takes_value(false)
|
.takes_value(false)
|
||||||
.conflicts_with("log")
|
.conflicts_with("log")
|
||||||
.help("Quiet mode: suppress normal output")
|
.help("Quiet mode: suppress normal output"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("log")
|
Arg::with_name("log")
|
||||||
.long("log")
|
.long("log")
|
||||||
.takes_value(false)
|
.takes_value(false)
|
||||||
.conflicts_with("quiet")
|
.conflicts_with("quiet")
|
||||||
.help("Log mode: stream the validator log")
|
.help("Log mode: stream the validator log"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("rpc_port")
|
Arg::with_name("rpc_port")
|
||||||
|
@ -104,6 +114,15 @@ fn main() {
|
||||||
.validator(solana_validator::port_validator)
|
.validator(solana_validator::port_validator)
|
||||||
.help("Use this port for JSON RPC and the next port for the RPC websocket"),
|
.help("Use this port for JSON RPC and the next port for the RPC websocket"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("bpf_program")
|
||||||
|
.long("bpf-program")
|
||||||
|
.value_name("ADDRESS BPF_PROGRAM.SO")
|
||||||
|
.takes_value(true)
|
||||||
|
.number_of_values(2)
|
||||||
|
.multiple(true)
|
||||||
|
.help("Add a BPF program to the genesis configuration"),
|
||||||
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let cli_config = if let Some(config_file) = matches.value_of("config_file") {
|
let cli_config = if let Some(config_file) = matches.value_of("config_file") {
|
||||||
|
@ -132,42 +151,56 @@ fn main() {
|
||||||
} else {
|
} else {
|
||||||
Output::Dashboard
|
Output::Dashboard
|
||||||
};
|
};
|
||||||
|
let rpc_port = value_t_or_exit!(matches, "rpc_port", u16);
|
||||||
|
let faucet_addr = Some(SocketAddr::new(
|
||||||
|
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||||
|
FAUCET_PORT,
|
||||||
|
));
|
||||||
|
|
||||||
let rpc_ports = {
|
let mut programs = vec![];
|
||||||
let rpc_port = value_t_or_exit!(matches, "rpc_port", u16);
|
if let Some(values) = matches.values_of("bpf_program") {
|
||||||
(rpc_port, rpc_port + 1)
|
let values: Vec<&str> = values.collect::<Vec<_>>();
|
||||||
};
|
for address_program in values.chunks(2) {
|
||||||
|
match address_program {
|
||||||
|
[address, program] => {
|
||||||
|
let address = address.parse::<Pubkey>().unwrap_or_else(|err| {
|
||||||
|
eprintln!("Error: invalid address {}: {}", address, err);
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
if !ledger_path.exists() {
|
let program_path = PathBuf::from(program);
|
||||||
let _progress_bar = if output == Output::Dashboard {
|
if !program_path.exists() {
|
||||||
println_name_value("Mint address:", &mint_address.to_string());
|
eprintln!(
|
||||||
let progress_bar = new_spinner_progress_bar();
|
"Error: program file does not exist: {}",
|
||||||
progress_bar.set_message("Creating ledger...");
|
program_path.display()
|
||||||
Some(progress_bar)
|
);
|
||||||
} else {
|
exit(1);
|
||||||
None
|
}
|
||||||
};
|
|
||||||
|
|
||||||
TestValidator::initialize_ledger(
|
programs.push(ProgramInfo {
|
||||||
Some(&ledger_path),
|
program_id: address,
|
||||||
TestValidatorGenesisConfig {
|
loader: solana_sdk::bpf_loader::id(),
|
||||||
mint_address,
|
program_path,
|
||||||
fee_rate_governor: FeeRateGovernor::default(),
|
});
|
||||||
rent: Rent::default(),
|
}
|
||||||
},
|
_ => unreachable!(),
|
||||||
)
|
}
|
||||||
.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 validator_log_symlink = ledger_path.join("validator.log");
|
||||||
let logfile = if output != Output::Log {
|
let logfile = if output != Output::Log {
|
||||||
|
if !ledger_path.exists() {
|
||||||
|
fs::create_dir(&ledger_path).unwrap_or_else(|err| {
|
||||||
|
eprintln!(
|
||||||
|
"Error: Unable to create directory {}: {}",
|
||||||
|
ledger_path.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let validator_log_with_timestamp = format!(
|
let validator_log_with_timestamp = format!(
|
||||||
"validator-{}.log",
|
"validator-{}.log",
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
|
@ -176,7 +209,7 @@ fn main() {
|
||||||
.as_millis()
|
.as_millis()
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = std::fs::remove_file(&validator_log_symlink);
|
let _ = fs::remove_file(&validator_log_symlink);
|
||||||
symlink::symlink_file(&validator_log_with_timestamp, &validator_log_symlink).unwrap();
|
symlink::symlink_file(&validator_log_with_timestamp, &validator_log_symlink).unwrap();
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
|
@ -189,11 +222,35 @@ fn main() {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let _logger_thread = start_logger(logfile);
|
let _logger_thread = start_logger(logfile);
|
||||||
|
|
||||||
|
let faucet_lamports = sol_to_lamports(1_000_000.);
|
||||||
|
let faucet_keypair_file = ledger_path.join("faucet-keypair.json");
|
||||||
|
if !faucet_keypair_file.exists() {
|
||||||
|
write_keypair_file(&Keypair::new(), faucet_keypair_file.to_str().unwrap()).unwrap_or_else(
|
||||||
|
|err| {
|
||||||
|
eprintln!(
|
||||||
|
"Error: Failed to write {}: {}",
|
||||||
|
faucet_keypair_file.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let faucet_keypair =
|
||||||
|
read_keypair_file(faucet_keypair_file.to_str().unwrap()).unwrap_or_else(|err| {
|
||||||
|
eprintln!(
|
||||||
|
"Error: Failed to read {}: {}",
|
||||||
|
faucet_keypair_file.display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
let test_validator = {
|
let test_validator = {
|
||||||
let _progress_bar = if output == Output::Dashboard {
|
let _progress_bar = if output == Output::Dashboard {
|
||||||
|
println_name_value("Mint address:", &mint_address.to_string());
|
||||||
println_name_value("Ledger location:", &format!("{}", ledger_path.display()));
|
println_name_value("Ledger location:", &format!("{}", ledger_path.display()));
|
||||||
println_name_value("Log:", &format!("{}", validator_log_symlink.display()));
|
println_name_value("Log:", &format!("{}", validator_log_symlink.display()));
|
||||||
let progress_bar = new_spinner_progress_bar();
|
let progress_bar = new_spinner_progress_bar();
|
||||||
|
@ -203,24 +260,33 @@ fn main() {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
TestValidator::start(
|
TestValidatorGenesis::default()
|
||||||
&ledger_path,
|
.ledger_path(ledger_path)
|
||||||
TestValidatorStartConfig {
|
.add_account(
|
||||||
preserve_ledger: true,
|
faucet_keypair.pubkey(),
|
||||||
rpc_config: JsonRpcConfig {
|
Account::new(faucet_lamports, 0, &system_program::id()),
|
||||||
enable_validator_exit: true,
|
)
|
||||||
enable_rpc_transaction_history: true,
|
.rpc_config(JsonRpcConfig {
|
||||||
..JsonRpcConfig::default()
|
enable_validator_exit: true,
|
||||||
},
|
enable_rpc_transaction_history: true,
|
||||||
rpc_ports: Some(rpc_ports),
|
faucet_addr,
|
||||||
},
|
..JsonRpcConfig::default()
|
||||||
)
|
})
|
||||||
|
.rpc_port(rpc_port)
|
||||||
|
.add_programs_with_path(&programs)
|
||||||
|
.start_with_mint_address(mint_address)
|
||||||
}
|
}
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
eprintln!("Error: failed to start validator: {}", err);
|
eprintln!("Error: failed to start validator: {}", err);
|
||||||
exit(1);
|
exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(faucet_addr) = &faucet_addr {
|
||||||
|
let (sender, receiver) = channel();
|
||||||
|
run_local_faucet_with_port(faucet_keypair, sender, None, faucet_addr.port());
|
||||||
|
receiver.recv().expect("run faucet");
|
||||||
|
}
|
||||||
|
|
||||||
if output == Output::Dashboard {
|
if output == Output::Dashboard {
|
||||||
println_name_value("JSON RPC URL:", &test_validator.rpc_url());
|
println_name_value("JSON RPC URL:", &test_validator.rpc_url());
|
||||||
println_name_value(
|
println_name_value(
|
||||||
|
@ -229,9 +295,12 @@ fn main() {
|
||||||
);
|
);
|
||||||
println_name_value("Gossip Address:", &test_validator.gossip().to_string());
|
println_name_value("Gossip Address:", &test_validator.gossip().to_string());
|
||||||
println_name_value("TPU Address:", &test_validator.tpu().to_string());
|
println_name_value("TPU Address:", &test_validator.tpu().to_string());
|
||||||
|
if let Some(faucet_addr) = &faucet_addr {
|
||||||
|
println_name_value("Faucet Address:", &faucet_addr.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
let progress_bar = new_spinner_progress_bar();
|
let progress_bar = new_spinner_progress_bar();
|
||||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
let rpc_client = test_validator.rpc_client().0;
|
||||||
|
|
||||||
fn get_validator_stats(rpc_client: &RpcClient) -> client_error::Result<(Slot, Slot, u64)> {
|
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 max_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::max())?;
|
||||||
|
@ -253,7 +322,7 @@ fn main() {
|
||||||
progress_bar.set_message(&format!("{}", err));
|
progress_bar.set_message(&format!("{}", err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sleep(Duration::from_millis(
|
thread::sleep(Duration::from_millis(
|
||||||
MS_PER_TICK * DEFAULT_TICKS_PER_SLOT / 2,
|
MS_PER_TICK * DEFAULT_TICKS_PER_SLOT / 2,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue