diff --git a/core/src/validator.rs b/core/src/validator.rs index db368b5607..c17cccc243 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -294,6 +294,7 @@ pub struct Validator { tvu: Tvu, ip_echo_server: Option, pub cluster_info: Arc, + pub bank_forks: Arc>, accountsdb_repl_service: Option, accountsdb_plugin_service: Option, } @@ -911,7 +912,7 @@ impl Validator { &exit, node.info.shred_version, vote_tracker, - bank_forks, + bank_forks.clone(), verified_vote_sender, gossip_verified_vote_hash_sender, replay_vote_receiver, @@ -947,6 +948,7 @@ impl Validator { ip_echo_server, validator_exit: config.validator_exit.clone(), cluster_info, + bank_forks, accountsdb_repl_service, accountsdb_plugin_service, } @@ -988,6 +990,7 @@ impl Validator { } pub fn join(self) { + drop(self.bank_forks); drop(self.cluster_info); self.poh_service.join().expect("poh_service"); diff --git a/test-validator/src/lib.rs b/test-validator/src/lib.rs index 4dae60c36a..27f0eb785c 100644 --- a/test-validator/src/lib.rs +++ b/test-validator/src/lib.rs @@ -16,7 +16,7 @@ use { solana_net_utils::PortRange, solana_rpc::{rpc::JsonRpcConfig, rpc_pubsub_service::PubSubConfig}, solana_runtime::{ - genesis_utils::create_genesis_config_with_leader_ex, + bank_forks::BankForks, genesis_utils::create_genesis_config_with_leader_ex, hardened_unpack::MAX_GENESIS_ARCHIVE_UNPACKED_SIZE, snapshot_config::SnapshotConfig, }, solana_sdk::{ @@ -793,6 +793,10 @@ impl TestValidator { pub fn cluster_info(&self) -> Arc { self.validator.as_ref().unwrap().cluster_info.clone() } + + pub fn bank_forks(&self) -> Arc> { + self.validator.as_ref().unwrap().bank_forks.clone() + } } impl Drop for TestValidator { diff --git a/validator/src/admin_rpc_service.rs b/validator/src/admin_rpc_service.rs index 6a3abc515f..653beb9b13 100644 --- a/validator/src/admin_rpc_service.rs +++ b/validator/src/admin_rpc_service.rs @@ -9,8 +9,10 @@ use { consensus::Tower, tower_storage::TowerStorage, validator::ValidatorStartProgress, }, solana_gossip::cluster_info::ClusterInfo, + solana_runtime::bank_forks::BankForks, solana_sdk::{ exit::Exit, + pubkey::Pubkey, signature::{read_keypair_file, Keypair, Signer}, }, std::{ @@ -22,6 +24,13 @@ use { }, }; +#[derive(Clone)] +pub struct AdminRpcRequestMetadataPostInit { + pub cluster_info: Arc, + pub bank_forks: Arc>, + pub vote_account: Pubkey, +} + #[derive(Clone)] pub struct AdminRpcRequestMetadata { pub rpc_addr: Option, @@ -29,11 +38,26 @@ pub struct AdminRpcRequestMetadata { pub start_progress: Arc>, pub validator_exit: Arc>, pub authorized_voter_keypairs: Arc>>>, - pub cluster_info: Arc>>>, pub tower_storage: Arc, + pub post_init: Arc>>, } impl Metadata for AdminRpcRequestMetadata {} +impl AdminRpcRequestMetadata { + fn with_post_init(&self, func: F) -> Result<()> + where + F: FnOnce(&AdminRpcRequestMetadataPostInit) -> Result<()>, + { + if let Some(post_init) = self.post_init.read().unwrap().as_ref() { + func(post_init) + } else { + Err(jsonrpc_core::error::Error::invalid_params( + "Retry once validator start up is complete", + )) + } + } +} + #[rpc] pub trait AdminRpc { type Metadata; @@ -60,7 +84,12 @@ pub trait AdminRpc { fn remove_all_authorized_voters(&self, meta: Self::Metadata) -> Result<()>; #[rpc(meta, name = "setIdentity")] - fn set_identity(&self, meta: Self::Metadata, keypair_file: String) -> Result<()>; + fn set_identity( + &self, + meta: Self::Metadata, + keypair_file: String, + require_tower: bool, + ) -> Result<()>; } pub struct AdminRpcImpl; @@ -137,7 +166,12 @@ impl AdminRpc for AdminRpcImpl { Ok(()) } - fn set_identity(&self, meta: Self::Metadata, keypair_file: String) -> Result<()> { + fn set_identity( + &self, + meta: Self::Metadata, + keypair_file: String, + require_tower: bool, + ) -> Result<()> { debug!("set_identity request received"); let identity_keypair = read_keypair_file(&keypair_file).map_err(|err| { @@ -147,25 +181,51 @@ impl AdminRpc for AdminRpcImpl { )) })?; - // Ensure a Tower exists for the new identity and exit gracefully. - // ReplayStage will be less forgiving if it fails to load the new tower. - Tower::restore(meta.tower_storage.as_ref(), &identity_keypair.pubkey()).map_err(|err| { - jsonrpc_core::error::Error::invalid_params(format!( - "Unable to load tower file for new identity: {}", - err - )) - })?; + meta.with_post_init(|post_init| { + // Ensure a Tower exists for the new identity and exit gracefully. + // ReplayStage will be less forgiving if it fails to load the new tower. + if let Err(err) = + Tower::restore(meta.tower_storage.as_ref(), &identity_keypair.pubkey()).map_err( + |err| { + jsonrpc_core::error::Error::invalid_params(format!( + "Unable to load tower file for identity {}: {}", + identity_keypair.pubkey(), + err + )) + }, + ) + { + if require_tower { + return Err(err); + } + + let root_bank = post_init.bank_forks.read().unwrap().root_bank(); + let mut tower = Tower::new( + &identity_keypair.pubkey(), + &post_init.vote_account, + root_bank.slot(), + &root_bank, + ); + // Forge a single vote to pacify `Tower::adjust_lockouts_after_replay` when its called + // by replay_stage + tower.record_bank_vote(&root_bank, &post_init.vote_account); + tower + .save(meta.tower_storage.as_ref(), &identity_keypair) + .map_err(|err| { + jsonrpc_core::error::Error::invalid_params(format!( + "Unable to create default tower file for ephemeral identity: {}", + err + )) + })?; + } - if let Some(cluster_info) = meta.cluster_info.read().unwrap().as_ref() { solana_metrics::set_host_id(identity_keypair.pubkey().to_string()); - cluster_info.set_keypair(Arc::new(identity_keypair)); - warn!("Identity set to {}", cluster_info.id()); + post_init + .cluster_info + .set_keypair(Arc::new(identity_keypair)); + warn!("Identity set to {}", post_init.cluster_info.id()); Ok(()) - } else { - Err(jsonrpc_core::error::Error::invalid_params( - "Retry once validator start up is complete", - )) - } + }) } } diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index 81573ee726..f96607b51e 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -588,7 +588,7 @@ fn main() { let tower_storage = Arc::new(FileTowerStorage::new(ledger_path.clone())); - let admin_service_cluster_info = Arc::new(RwLock::new(None)); + let admin_service_post_init = Arc::new(RwLock::new(None)); admin_rpc_service::run( &ledger_path, admin_rpc_service::AdminRpcRequestMetadata { @@ -600,7 +600,7 @@ fn main() { start_time: std::time::SystemTime::now(), validator_exit: genesis.validator_exit.clone(), authorized_voter_keypairs: genesis.authorized_voter_keypairs.clone(), - cluster_info: admin_service_cluster_info.clone(), + post_init: admin_service_post_init.clone(), tower_storage: tower_storage.clone(), }, ); @@ -694,7 +694,12 @@ fn main() { match genesis.start_with_mint_address(mint_address, socket_addr_space) { Ok(test_validator) => { - *admin_service_cluster_info.write().unwrap() = Some(test_validator.cluster_info()); + *admin_service_post_init.write().unwrap() = + Some(admin_rpc_service::AdminRpcRequestMetadataPostInit { + bank_forks: test_validator.bank_forks(), + cluster_info: test_validator.cluster_info(), + vote_account: test_validator.vote_account_address(), + }); if let Some(dashboard) = dashboard { dashboard.run(Duration::from_millis(250)); } diff --git a/validator/src/main.rs b/validator/src/main.rs index ba16c90a25..b671991534 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -1723,6 +1723,12 @@ pub fn main() { .validator(is_keypair) .help("Validator identity keypair") ) + .arg( + clap::Arg::with_name("require_tower") + .long("require-tower") + .takes_value(false) + .help("Refuse to set the validator identity if saved tower state is not found"), + ) .after_help("Note: the new identity only applies to the \ currently running validator instance") ) @@ -1874,6 +1880,7 @@ pub fn main() { return; } ("set-identity", Some(subcommand_matches)) => { + let require_tower = subcommand_matches.is_present("require_tower"); let identity_keypair = value_t_or_exit!(subcommand_matches, "identity", String); let identity_keypair = fs::canonicalize(&identity_keypair).unwrap_or_else(|err| { @@ -1887,7 +1894,7 @@ pub fn main() { .block_on(async move { admin_client .await? - .set_identity(identity_keypair.display().to_string()) + .set_identity(identity_keypair.display().to_string(), require_tower) .await }) .unwrap_or_else(|err| { @@ -2531,7 +2538,7 @@ pub fn main() { let _ledger_write_guard = lock_ledger(&ledger_path, &mut ledger_lock); let start_progress = Arc::new(RwLock::new(ValidatorStartProgress::default())); - let admin_service_cluster_info = Arc::new(RwLock::new(None)); + let admin_service_post_init = Arc::new(RwLock::new(None)); admin_rpc_service::run( &ledger_path, admin_rpc_service::AdminRpcRequestMetadata { @@ -2540,7 +2547,7 @@ pub fn main() { validator_exit: validator_config.validator_exit.clone(), start_progress: start_progress.clone(), authorized_voter_keypairs: authorized_voter_keypairs.clone(), - cluster_info: admin_service_cluster_info.clone(), + post_init: admin_service_post_init.clone(), tower_storage: validator_config.tower_storage.clone(), }, ); @@ -2685,7 +2692,12 @@ pub fn main() { start_progress, socket_addr_space, ); - *admin_service_cluster_info.write().unwrap() = Some(validator.cluster_info.clone()); + *admin_service_post_init.write().unwrap() = + Some(admin_rpc_service::AdminRpcRequestMetadataPostInit { + bank_forks: validator.bank_forks.clone(), + cluster_info: validator.cluster_info.clone(), + vote_account, + }); if let Some(filename) = init_complete_file { File::create(filename).unwrap_or_else(|_| {