diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index 919d183fd1..e4740548d6 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -106,7 +106,7 @@ struct SkippedSlotsInfo { pub struct ReplayStageConfig { pub my_pubkey: Pubkey, pub vote_account: Pubkey, - pub authorized_voter_keypairs: Vec>, + pub authorized_voter_keypairs: Arc>>>, pub exit: Arc, pub subscriptions: Arc, pub leader_schedule_cache: Arc, @@ -531,7 +531,7 @@ impl ReplayStage { &mut tower, &mut progress, &vote_account, - &authorized_voter_keypairs, + &authorized_voter_keypairs.read().unwrap(), &cluster_info, &blockstore, &leader_schedule_cache, diff --git a/core/src/test_validator.rs b/core/src/test_validator.rs index 4029209218..895fc7737d 100644 --- a/core/src/test_validator.rs +++ b/core/src/test_validator.rs @@ -56,6 +56,7 @@ pub struct TestValidatorGenesis { epoch_schedule: Option, pub validator_exit: Arc>, pub start_progress: Arc>, + pub authorized_voter_keypairs: Arc>>>, } impl TestValidatorGenesis { @@ -399,6 +400,16 @@ impl TestValidator { let mut rpc_config = config.rpc_config.clone(); rpc_config.identity_pubkey = validator_identity.pubkey(); + { + let mut authorized_voter_keypairs = config.authorized_voter_keypairs.write().unwrap(); + if !authorized_voter_keypairs + .iter() + .any(|x| x.pubkey() == vote_account_address) + { + authorized_voter_keypairs.push(Arc::new(validator_vote_account)) + } + } + let validator_config = ValidatorConfig { rpc_addrs: Some(( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), node.info.rpc.port()), @@ -434,8 +445,8 @@ impl TestValidator { node, &Arc::new(validator_identity), &ledger_path, - &validator_vote_account.pubkey(), - vec![Arc::new(validator_vote_account)], + &vote_account_address, + config.authorized_voter_keypairs.clone(), vec![], &validator_config, true, // should_check_duplicate_instance diff --git a/core/src/tvu.rs b/core/src/tvu.rs index bbe8efaddb..eea4b27524 100644 --- a/core/src/tvu.rs +++ b/core/src/tvu.rs @@ -100,7 +100,7 @@ impl Tvu { #[allow(clippy::new_ret_no_self, clippy::too_many_arguments)] pub fn new( vote_account: &Pubkey, - authorized_voter_keypairs: Vec>, + authorized_voter_keypairs: Arc>>>, bank_forks: &Arc>, cluster_info: &Arc, sockets: Sockets, @@ -389,7 +389,7 @@ pub mod tests { let tower = Tower::new_with_key(&target1_keypair.pubkey()); let tvu = Tvu::new( &vote_keypair.pubkey(), - vec![Arc::new(vote_keypair)], + Arc::new(RwLock::new(vec![Arc::new(vote_keypair)])), &bank_forks, &cref1, { diff --git a/core/src/validator.rs b/core/src/validator.rs index 1bc8ddf024..0ac60a6bd1 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -297,7 +297,7 @@ impl Validator { identity_keypair: &Arc, ledger_path: &Path, vote_account: &Pubkey, - mut authorized_voter_keypairs: Vec>, + authorized_voter_keypairs: Arc>>>, cluster_entrypoints: Vec, config: &ValidatorConfig, should_check_duplicate_instance: bool, @@ -311,12 +311,13 @@ impl Validator { if config.voting_disabled { warn!("voting disabled"); - authorized_voter_keypairs.clear(); + authorized_voter_keypairs.write().unwrap().clear(); } else { - for authorized_voter_keypair in &authorized_voter_keypairs { + for authorized_voter_keypair in authorized_voter_keypairs.read().unwrap().iter() { warn!("authorized voter: {}", authorized_voter_keypair.pubkey()); } } + report_target_features(); for cluster_entrypoint in &cluster_entrypoints { @@ -1559,7 +1560,7 @@ mod tests { &Arc::new(validator_keypair), &validator_ledger_path, &voting_keypair.pubkey(), - vec![voting_keypair.clone()], + Arc::new(RwLock::new(vec![voting_keypair.clone()])), vec![leader_node.info], &config, true, // should_check_duplicate_instance @@ -1635,7 +1636,7 @@ mod tests { &Arc::new(validator_keypair), &validator_ledger_path, &vote_account_keypair.pubkey(), - vec![Arc::new(vote_account_keypair)], + Arc::new(RwLock::new(vec![Arc::new(vote_account_keypair)])), vec![leader_node.info.clone()], &config, true, // should_check_duplicate_instance diff --git a/local-cluster/src/local_cluster.rs b/local-cluster/src/local_cluster.rs index 39d5f0a455..fa4c18b12f 100644 --- a/local-cluster/src/local_cluster.rs +++ b/local-cluster/src/local_cluster.rs @@ -213,7 +213,7 @@ impl LocalCluster { &leader_keypair, &leader_ledger_path, &leader_vote_keypair.pubkey(), - vec![leader_vote_keypair.clone()], + Arc::new(RwLock::new(vec![leader_vote_keypair.clone()])), vec![], &leader_config, true, // should_check_duplicate_instance @@ -355,7 +355,7 @@ impl LocalCluster { &validator_keypair, &ledger_path, &voting_keypair.pubkey(), - vec![voting_keypair.clone()], + Arc::new(RwLock::new(vec![voting_keypair.clone()])), vec![self.entry_point_info.clone()], &config, true, // should_check_duplicate_instance @@ -670,7 +670,7 @@ impl Cluster for LocalCluster { &validator_info.keypair, &validator_info.ledger_path, &validator_info.voting_keypair.pubkey(), - vec![validator_info.voting_keypair.clone()], + Arc::new(RwLock::new(vec![validator_info.voting_keypair.clone()])), entry_point_info .map(|entry_point_info| vec![entry_point_info]) .unwrap_or_default(), diff --git a/validator/src/admin_rpc_service.rs b/validator/src/admin_rpc_service.rs index 475645ff82..ec139d1967 100644 --- a/validator/src/admin_rpc_service.rs +++ b/validator/src/admin_rpc_service.rs @@ -6,6 +6,7 @@ use { jsonrpc_server_utils::tokio, log::*, solana_core::validator::{ValidatorExit, ValidatorStartProgress}, + solana_sdk::signature::{read_keypair_file, Keypair, Signer}, std::{ net::SocketAddr, path::Path, @@ -21,6 +22,7 @@ pub struct AdminRpcRequestMetadata { pub start_time: SystemTime, pub start_progress: Arc>, pub validator_exit: Arc>, + pub authorized_voter_keypairs: Arc>>>, } impl Metadata for AdminRpcRequestMetadata {} @@ -42,6 +44,12 @@ pub trait AdminRpc { #[rpc(meta, name = "startProgress")] fn start_progress(&self, meta: Self::Metadata) -> Result; + + #[rpc(meta, name = "addAuthorizedVoter")] + fn add_authorized_voter(&self, meta: Self::Metadata, keypair_file: String) -> Result<()>; + + #[rpc(meta, name = "removeAllAuthorizedVoters")] + fn remove_all_authorized_voters(&self, meta: Self::Metadata) -> Result<()>; } pub struct AdminRpcImpl; @@ -78,6 +86,39 @@ impl AdminRpc for AdminRpcImpl { debug!("start_progress admin rpc request received"); Ok(*meta.start_progress.read().unwrap()) } + + fn add_authorized_voter(&self, meta: Self::Metadata, keypair_file: String) -> Result<()> { + debug!("add_authorized_voter request received"); + + let authorized_voter = read_keypair_file(keypair_file) + .map_err(|err| jsonrpc_core::error::Error::invalid_params(format!("{}", err)))?; + + let mut authorized_voter_keypairs = meta.authorized_voter_keypairs.write().unwrap(); + + if authorized_voter_keypairs + .iter() + .any(|x| x.pubkey() == authorized_voter.pubkey()) + { + Err(jsonrpc_core::error::Error::invalid_params( + "Authorized voter already present", + )) + } else { + authorized_voter_keypairs.push(Arc::new(authorized_voter)); + Ok(()) + } + } + + fn remove_all_authorized_voters(&self, meta: Self::Metadata) -> Result<()> { + debug!("remove_all_authorized_voters received"); + let mut a = meta.authorized_voter_keypairs.write().unwrap(); + + error!("authorized_voter_keypairs pre len: {}", a.len()); + a.clear(); + error!("authorized_voter_keypairs post len: {}", a.len()); + + //meta.authorized_voter_keypairs.write().unwrap().clear(); + Ok(()) + } } // Start the Admin RPC interface diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index 85cd93cc90..5a4b31983e 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -403,6 +403,7 @@ fn main() { start_progress: genesis.start_progress.clone(), start_time: std::time::SystemTime::now(), validator_exit: genesis.validator_exit.clone(), + authorized_voter_keypairs: genesis.authorized_voter_keypairs.clone(), }, ); let dashboard = if output == Output::Dashboard { diff --git a/validator/src/main.rs b/validator/src/main.rs index afefd4581a..234cb43bf2 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -11,7 +11,8 @@ use { solana_clap_utils::{ input_parsers::{keypair_of, keypairs_of, pubkey_of, value_of}, input_validators::{ - is_keypair_or_ask_keyword, is_parsable, is_pubkey, is_pubkey_or_keypair, is_slot, + is_keypair, is_keypair_or_ask_keyword, is_parsable, is_pubkey, is_pubkey_or_keypair, + is_slot, }, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, }, @@ -804,7 +805,7 @@ fn rpc_bootstrap( ledger_path: &Path, snapshot_output_dir: &Path, vote_account: &Pubkey, - authorized_voter_keypairs: &[Arc], + authorized_voter_keypairs: Arc>>>, cluster_entrypoints: &[ContactInfo], validator_config: &mut ValidatorConfig, bootstrap_config: RpcBootstrapConfig, @@ -969,6 +970,8 @@ fn rpc_bootstrap( &identity_keypair.pubkey(), &vote_account, &authorized_voter_keypairs + .read() + .unwrap() .iter() .map(|k| k.pubkey()) .collect::>(), @@ -1766,6 +1769,32 @@ pub fn main() { .help("Minimum time that the validator should not be leader before restarting") ) ) + .subcommand( + SubCommand::with_name("authorized-voter") + .about("Adjust the validator authorized voters") + .setting(AppSettings::SubcommandRequiredElseHelp) + .setting(AppSettings::InferSubcommands) + .subcommand( + SubCommand::with_name("add") + .about("Add an authorized voter") + .arg( + Arg::with_name("authorized_voter_keypair") + .index(1) + .value_name("KEYPAIR") + .takes_value(true) + .validator(is_keypair) + .help("Keypair of the authorized voter to add"), + ) + .after_help("Note: the new authorized voter only applies to the \ + currently running validator instance") + ) + .subcommand( + SubCommand::with_name("remove-all") + .about("Remove all authorized voters") + .after_help("Note: the removal only applies to the \ + currently running validator instance") + ) + ) .subcommand( SubCommand::with_name("init") .about("Initialize the ledger directory then exit") @@ -1810,6 +1839,43 @@ pub fn main() { let operation = match matches.subcommand() { ("", _) | ("run", _) => Operation::Run, + ("authorized-voter", Some(authorized_voter_subcommand_matches)) => { + match authorized_voter_subcommand_matches.subcommand() { + ("add", Some(subcommand_matches)) => { + let authorized_voter_keypair = + value_t_or_exit!(subcommand_matches, "authorized_voter_keypair", String); + println!("Adding authorized voter: {}", authorized_voter_keypair); + + let admin_client = admin_rpc_service::connect(&ledger_path); + admin_rpc_service::runtime() + .block_on(async move { + admin_client + .await? + .add_authorized_voter(authorized_voter_keypair) + .await + }) + .unwrap_or_else(|err| { + println!("addAuthorizedVoter request failed: {}", err); + exit(1); + }); + return; + } + ("remove-all", _) => { + let admin_client = admin_rpc_service::connect(&ledger_path); + admin_rpc_service::runtime() + .block_on(async move { + admin_client.await?.remove_all_authorized_voters().await + }) + .unwrap_or_else(|err| { + println!("removeAllAuthorizedVoters request failed: {}", err); + exit(1); + }); + println!("All authorized voters removed"); + return; + } + _ => unreachable!(), + } + } ("init", _) => Operation::Initialize, ("exit", Some(subcommand_matches)) => { let min_idle_time = value_t_or_exit!(subcommand_matches, "min_idle_time", usize); @@ -1874,6 +1940,7 @@ pub fn main() { let authorized_voter_keypairs = keypairs_of(&matches, "authorized_voter_keypairs") .map(|keypairs| keypairs.into_iter().map(Arc::new).collect()) .unwrap_or_else(|| vec![identity_keypair.clone()]); + let authorized_voter_keypairs = Arc::new(RwLock::new(authorized_voter_keypairs)); let init_complete_file = matches.value_of("init_complete_file"); @@ -2261,6 +2328,7 @@ pub fn main() { start_time: std::time::SystemTime::now(), validator_exit: validator_config.validator_exit.clone(), start_progress: start_progress.clone(), + authorized_voter_keypairs: authorized_voter_keypairs.clone(), }, ); @@ -2371,7 +2439,7 @@ pub fn main() { &ledger_path, &snapshot_output_dir, &vote_account, - &authorized_voter_keypairs, + authorized_voter_keypairs.clone(), &cluster_entrypoints, &mut validator_config, rpc_bootstrap_config, @@ -2388,6 +2456,7 @@ pub fn main() { info!("Validator ledger initialization complete"); return; } + let validator = Validator::new( node, &identity_keypair,