From cbb9ac19b9152831e01ab295b769d2c2a1a55bf2 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Thu, 21 Jan 2021 13:16:40 -0800 Subject: [PATCH] Add ability to clone accounts from an RPC endpoint --- clap-utils/src/input_validators.rs | 4 +- core/src/test_validator.rs | 24 +++++++++ validator/src/bin/solana-test-validator.rs | 63 +++++++++++++++++++--- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/clap-utils/src/input_validators.rs b/clap-utils/src/input_validators.rs index cad78a723f..5727200170 100644 --- a/clap-utils/src/input_validators.rs +++ b/clap-utils/src/input_validators.rs @@ -164,8 +164,8 @@ where } } -pub fn normalize_to_url_if_moniker(url_or_moniker: &str) -> String { - match url_or_moniker { +pub fn normalize_to_url_if_moniker>(url_or_moniker: T) -> String { + match url_or_moniker.as_ref() { "m" | "mainnet-beta" => "https://api.mainnet-beta.solana.com", "t" | "testnet" => "https://testnet.solana.com", "d" | "devnet" => "https://devnet.solana.com", diff --git a/core/src/test_validator.rs b/core/src/test_validator.rs index da6897dc95..bf9a017158 100644 --- a/core/src/test_validator.rs +++ b/core/src/test_validator.rs @@ -84,6 +84,30 @@ impl TestValidatorGenesis { self } + pub fn add_accounts(&mut self, accounts: T) -> &mut Self + where + T: IntoIterator, + { + for (address, account) in accounts { + self.add_account(address, account); + } + self + } + + pub fn clone_accounts(&mut self, addresses: T, rpc_client: &RpcClient) -> &mut Self + where + T: IntoIterator, + { + for address in addresses { + info!("Fetching {}...", address); + let account = rpc_client.get_account(&address).unwrap_or_else(|err| { + panic!("Failed to fetch {}: {}", address, err); + }); + self.add_account(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, diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index efc55f49df..a6ce15a37d 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -1,9 +1,14 @@ use { - clap::{value_t_or_exit, App, Arg}, + clap::{value_t, value_t_or_exit, App, Arg}, console::style, fd_lock::FdLock, indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}, - solana_clap_utils::{input_parsers::pubkey_of, input_validators::is_pubkey}, + solana_clap_utils::{ + input_parsers::{pubkey_of, pubkeys_of}, + input_validators::{ + is_pubkey, is_pubkey_or_keypair, is_url_or_moniker, normalize_to_url_if_moniker, + }, + }, solana_client::{client_error, rpc_client::RpcClient, rpc_request}, solana_core::rpc::JsonRpcConfig, solana_faucet::faucet::{run_local_faucet_with_port, FAUCET_PORT}, @@ -19,6 +24,7 @@ use { }, solana_validator::{start_logger, test_validator::*}, std::{ + collections::HashSet, fs, io, net::{IpAddr, Ipv4Addr, SocketAddr}, path::{Path, PathBuf}, @@ -70,6 +76,18 @@ fn main() { arg } }) + .arg( + Arg::with_name("json_rpc_url") + .short("u") + .long("url") + .value_name("URL_OR_MONIKER") + .takes_value(true) + .validator(is_url_or_moniker) + .help( + "URL for Solana's JSON RPC or moniker (or their first letter): \ + [mainnet-beta, testnet, devnet, localhost]", + ), + ) .arg( Arg::with_name("mint_address") .long("mint") @@ -78,7 +96,8 @@ fn main() { .takes_value(true) .help( "Address of the mint account that will receive tokens \ - created at genesis [default: client keypair]", + created at genesis. If the ledger already exists then \ + this parameter is silently ignored [default: client keypair]", ), ) .arg( @@ -132,7 +151,25 @@ fn main() { .takes_value(true) .number_of_values(2) .multiple(true) - .help("Add a BPF program to the genesis configuration"), + .help( + "Add a BPF program to the genesis configuration. \ + If the ledger already exists then this parameter is silently ignored", + ), + ) + .arg( + Arg::with_name("clone_account") + .long("clone") + .short("c") + .value_name("ADDRESS") + .takes_value(true) + .validator(is_pubkey_or_keypair) + .multiple(true) + .requires("json_rpc_url") + .help( + "Copy an account from the cluster referenced by the --url argument the \ + genesis configuration. \ + If the ledger already exists then this parameter is silently ignored", + ), ) .get_matches(); @@ -142,6 +179,8 @@ fn main() { solana_cli_config::Config::default() }; + let json_rpc_url = value_t!(matches, "json_rpc_url", String).map(normalize_to_url_if_moniker); + let mint_address = pubkey_of(&matches, "mint_address").unwrap_or_else(|| { read_keypair_file(&cli_config.keypair_path) .unwrap_or_else(|_| Keypair::new()) @@ -194,6 +233,10 @@ fn main() { } } + let clone_accounts: HashSet<_> = pubkeys_of(&matches, "clone_account") + .map(|v| v.into_iter().collect()) + .unwrap_or_default(); + if !ledger_path.exists() { fs::create_dir(&ledger_path).unwrap_or_else(|err| { eprintln!( @@ -285,7 +328,8 @@ fn main() { None }; - TestValidatorGenesis::default() + let mut genesis = TestValidatorGenesis::default(); + genesis .ledger_path(&ledger_path) .add_account( faucet_keypair.pubkey(), @@ -298,8 +342,13 @@ fn main() { ..JsonRpcConfig::default() }) .rpc_port(rpc_port) - .add_programs_with_path(&programs) - .start_with_mint_address(mint_address) + .add_programs_with_path(&programs); + + if !clone_accounts.is_empty() { + let rpc_client = RpcClient::new(json_rpc_url.unwrap()); + genesis.clone_accounts(clone_accounts, &rpc_client); + } + genesis.start_with_mint_address(mint_address) } .unwrap_or_else(|err| { eprintln!("Error: failed to start validator: {}", err);