diff --git a/core/src/validator.rs b/core/src/validator.rs index 85f3a7c4e7abca..0227cda2d3b33c 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -297,6 +297,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, } @@ -897,7 +898,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, @@ -934,6 +935,7 @@ impl Validator { ip_echo_server, validator_exit: config.validator_exit.clone(), cluster_info, + bank_forks, accountsdb_repl_service, accountsdb_plugin_service, } @@ -975,6 +977,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 1cd605ecc39477..e35b2d9cb73870 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::{ @@ -830,6 +830,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 e330f2a81a3378..21cb533a146cf3 100644 --- a/validator/src/admin_rpc_service.rs +++ b/validator/src/admin_rpc_service.rs @@ -10,8 +10,10 @@ use { consensus::Tower, tower_storage::TowerStorage, validator::ValidatorStartProgress, }, solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo}, + solana_runtime::bank_forks::BankForks, solana_sdk::{ exit::Exit, + pubkey::Pubkey, signature::{read_keypair_file, Keypair, Signer}, }, std::{ @@ -24,6 +26,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, @@ -31,11 +40,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", + )) + } + } +} + #[derive(Debug, Deserialize, Serialize)] pub struct AdminRpcContactInfo { pub id: String, @@ -132,7 +156,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<()>; #[rpc(meta, name = "contactInfo")] fn contact_info(&self, meta: Self::Metadata) -> Result; @@ -212,7 +241,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| { @@ -222,35 +256,55 @@ 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", - )) - } + }) } fn contact_info(&self, meta: Self::Metadata) -> Result { - if let Some(cluster_info) = meta.cluster_info.read().unwrap().as_ref() { - Ok(cluster_info.my_contact_info().into()) - } else { - Err(jsonrpc_core::error::Error::invalid_params( - "Retry once validator start up is complete", - )) - } + meta.with_post_init(|post_init| Ok(post_init.cluster_info.my_contact_info().into())) } } diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index 799a3936df78bb..4264250d3201c0 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -589,7 +589,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 { @@ -601,7 +601,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(), }, ); @@ -695,7 +695,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 70f51b56f220b2..86c43a5c98654e 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -1678,6 +1678,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") ) @@ -1849,6 +1855,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| { @@ -1862,7 +1869,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| { @@ -2519,7 +2526,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 { @@ -2528,7 +2535,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(), }, ); @@ -2673,7 +2680,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(|_| {