Add required cluster argument to solana feature activate subcommand (#32693)
This extra argument serves as an extra guardrail to ensure that the feature key holder is activating the feature on the intended cluster, and not solely using a default or config-file stored RPC url to determine which cluster the feature will be activated on.
This commit is contained in:
parent
7ca45ae159
commit
1844c423a7
|
@ -3,7 +3,7 @@ use {
|
||||||
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
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,
|
console::style,
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
solana_clap_utils::{
|
solana_clap_utils::{
|
||||||
|
@ -19,6 +19,7 @@ use {
|
||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
feature::{self, Feature},
|
feature::{self, Feature},
|
||||||
feature_set::FEATURE_NAMES,
|
feature_set::FEATURE_NAMES,
|
||||||
|
genesis_config::ClusterType,
|
||||||
message::Message,
|
message::Message,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
|
@ -43,6 +44,7 @@ pub enum FeatureCliCommand {
|
||||||
},
|
},
|
||||||
Activate {
|
Activate {
|
||||||
feature: Pubkey,
|
feature: Pubkey,
|
||||||
|
cluster: ClusterType,
|
||||||
force: ForceActivation,
|
force: ForceActivation,
|
||||||
fee_payer: SignerIndex,
|
fee_payer: SignerIndex,
|
||||||
},
|
},
|
||||||
|
@ -385,6 +387,25 @@ pub struct CliSoftwareVersionStats {
|
||||||
rpc_percent: f32,
|
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<dyn std::error::Error>> {
|
||||||
|
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 {
|
pub trait FeatureSubCommands {
|
||||||
fn feature_subcommands(self) -> Self;
|
fn feature_subcommands(self) -> Self;
|
||||||
}
|
}
|
||||||
|
@ -423,6 +444,13 @@ impl FeatureSubCommands for App<'_, '_> {
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("The signer for the feature to activate"),
|
.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(
|
||||||
Arg::with_name("force")
|
Arg::with_name("force")
|
||||||
.long("yolo")
|
.long("yolo")
|
||||||
|
@ -453,6 +481,7 @@ pub fn parse_feature_subcommand(
|
||||||
) -> Result<CliCommandInfo, CliError> {
|
) -> Result<CliCommandInfo, CliError> {
|
||||||
let response = match matches.subcommand() {
|
let response = match matches.subcommand() {
|
||||||
("activate", Some(matches)) => {
|
("activate", Some(matches)) => {
|
||||||
|
let cluster = value_t_or_exit!(matches, "cluster", ClusterType);
|
||||||
let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?;
|
let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?;
|
||||||
let (fee_payer, fee_payer_pubkey) =
|
let (fee_payer, fee_payer_pubkey) =
|
||||||
signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
|
signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
|
||||||
|
@ -476,6 +505,7 @@ pub fn parse_feature_subcommand(
|
||||||
CliCommandInfo {
|
CliCommandInfo {
|
||||||
command: CliCommand::Feature(FeatureCliCommand::Activate {
|
command: CliCommand::Feature(FeatureCliCommand::Activate {
|
||||||
feature,
|
feature,
|
||||||
|
cluster,
|
||||||
force,
|
force,
|
||||||
fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
|
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),
|
} => process_status(rpc_client, config, features, *display_all),
|
||||||
FeatureCliCommand::Activate {
|
FeatureCliCommand::Activate {
|
||||||
feature,
|
feature,
|
||||||
|
cluster,
|
||||||
force,
|
force,
|
||||||
fee_payer,
|
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,
|
rpc_client: &RpcClient,
|
||||||
config: &CliConfig,
|
config: &CliConfig,
|
||||||
feature_id: Pubkey,
|
feature_id: Pubkey,
|
||||||
|
cluster: ClusterType,
|
||||||
force: ForceActivation,
|
force: ForceActivation,
|
||||||
fee_payer: SignerIndex,
|
fee_payer: SignerIndex,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
|
check_rpc_genesis_hash(&cluster, rpc_client)?;
|
||||||
|
|
||||||
let fee_payer = config.signers[fee_payer];
|
let fee_payer = config.signers[fee_payer];
|
||||||
let account = rpc_client
|
let account = rpc_client
|
||||||
.get_multiple_accounts(&[feature_id])?
|
.get_multiple_accounts(&[feature_id])?
|
||||||
|
|
|
@ -51,6 +51,22 @@ pub enum ClusterType {
|
||||||
|
|
||||||
impl ClusterType {
|
impl ClusterType {
|
||||||
pub const STRINGS: [&'static str; 4] = ["development", "devnet", "testnet", "mainnet-beta"];
|
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<Hash> {
|
||||||
|
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 {
|
impl FromStr for ClusterType {
|
||||||
|
|
Loading…
Reference in New Issue