diff --git a/cli/src/feature.rs b/cli/src/feature.rs index a59613c4d2..a5972bca7a 100644 --- a/cli/src/feature.rs +++ b/cli/src/feature.rs @@ -3,7 +3,7 @@ use { cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult}, spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount}, }, - clap::{App, AppSettings, Arg, ArgMatches, SubCommand}, + clap::{value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand}, console::style, serde::{Deserialize, Serialize}, solana_clap_utils::{ @@ -19,6 +19,7 @@ use { epoch_schedule::EpochSchedule, feature::{self, Feature}, feature_set::FEATURE_NAMES, + genesis_config::ClusterType, message::Message, pubkey::Pubkey, transaction::Transaction, @@ -43,6 +44,7 @@ pub enum FeatureCliCommand { }, Activate { feature: Pubkey, + cluster: ClusterType, force: ForceActivation, fee_payer: SignerIndex, }, @@ -385,6 +387,25 @@ pub struct CliSoftwareVersionStats { rpc_percent: f32, } +/// Check an RPC's reported genesis hash against the ClusterType's known genesis hash +fn check_rpc_genesis_hash( + cluster_type: &ClusterType, + rpc_client: &RpcClient, +) -> Result<(), Box> { + if let Some(genesis_hash) = cluster_type.get_genesis_hash() { + let rpc_genesis_hash = rpc_client.get_genesis_hash()?; + if rpc_genesis_hash != genesis_hash { + return Err(format!( + "The genesis hash for the specified cluster {cluster_type:?} does not match the \ + genesis hash reported by the specified RPC. Cluster genesis hash: {genesis_hash}, \ + RPC reported genesis hash: {rpc_genesis_hash}" + ) + .into()); + } + } + Ok(()) +} + pub trait FeatureSubCommands { fn feature_subcommands(self) -> Self; } @@ -423,6 +444,13 @@ impl FeatureSubCommands for App<'_, '_> { .required(true) .help("The signer for the feature to activate"), ) + .arg( + Arg::with_name("cluster") + .value_name("CLUSTER") + .possible_values(&ClusterType::STRINGS) + .required(true) + .help("The cluster to activate the feature on"), + ) .arg( Arg::with_name("force") .long("yolo") @@ -453,6 +481,7 @@ pub fn parse_feature_subcommand( ) -> Result { let response = match matches.subcommand() { ("activate", Some(matches)) => { + let cluster = value_t_or_exit!(matches, "cluster", ClusterType); let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?; let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; @@ -476,6 +505,7 @@ pub fn parse_feature_subcommand( CliCommandInfo { command: CliCommand::Feature(FeatureCliCommand::Activate { feature, + cluster, force, fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), }), @@ -519,9 +549,10 @@ pub fn process_feature_subcommand( } => process_status(rpc_client, config, features, *display_all), FeatureCliCommand::Activate { feature, + cluster, force, fee_payer, - } => process_activate(rpc_client, config, *feature, *force, *fee_payer), + } => process_activate(rpc_client, config, *feature, *cluster, *force, *fee_payer), } } @@ -855,9 +886,12 @@ fn process_activate( rpc_client: &RpcClient, config: &CliConfig, feature_id: Pubkey, + cluster: ClusterType, force: ForceActivation, fee_payer: SignerIndex, ) -> ProcessResult { + check_rpc_genesis_hash(&cluster, rpc_client)?; + let fee_payer = config.signers[fee_payer]; let account = rpc_client .get_multiple_accounts(&[feature_id])? diff --git a/sdk/src/genesis_config.rs b/sdk/src/genesis_config.rs index 16bf0f2e30..05ba706a62 100644 --- a/sdk/src/genesis_config.rs +++ b/sdk/src/genesis_config.rs @@ -51,6 +51,22 @@ pub enum ClusterType { impl ClusterType { pub const STRINGS: [&'static str; 4] = ["development", "devnet", "testnet", "mainnet-beta"]; + + /// Get the known genesis hash for this ClusterType + pub fn get_genesis_hash(&self) -> Option { + match self { + Self::MainnetBeta => { + Some(Hash::from_str("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d").unwrap()) + } + Self::Testnet => { + Some(Hash::from_str("4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY").unwrap()) + } + Self::Devnet => { + Some(Hash::from_str("EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG").unwrap()) + } + Self::Development => None, + } + } } impl FromStr for ClusterType {