From b8d72cfe5b062658643e9f8645716a6d6e1c34bc Mon Sep 17 00:00:00 2001 From: Joe Caulfield Date: Thu, 9 Jan 2025 13:30:41 +0800 Subject: [PATCH 1/3] cli: add feature revoke --- Cargo.lock | 14 ++++++ Cargo.toml | 1 + cli/Cargo.toml | 1 + cli/src/feature.rs | 123 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index f1b6412e1a021f..d8e515aa57e774 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6534,6 +6534,7 @@ dependencies = [ "solana-connection-cache", "solana-decode-error", "solana-faucet", + "solana-feature-gate-client", "solana-feature-set", "solana-loader-v4-program", "solana-logger", @@ -7256,6 +7257,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "solana-feature-gate-client" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1056507c534839b5cd1b1010ffedb9e8c92313269786fb5066ff53b30326dc3" +dependencies = [ + "borsh 0.10.3", + "num-derive", + "num-traits", + "solana-program", + "thiserror 2.0.11", +] + [[package]] name = "solana-feature-gate-interface" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index 7e1fa38779f382..572786d00074ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -489,6 +489,7 @@ solana-epoch-rewards = { path = "sdk/epoch-rewards", version = "=2.2.0" } solana-epoch-rewards-hasher = { path = "sdk/epoch-rewards-hasher", version = "=2.2.0" } solana-epoch-schedule = { path = "sdk/epoch-schedule", version = "=2.2.0" } solana-faucet = { path = "faucet", version = "=2.2.0" } +solana-feature-gate-client = "0.0.2" solana-feature-gate-interface = { path = "sdk/feature-gate-interface", version = "=2.2.0" } solana-feature-set = { path = "sdk/feature-set", version = "=2.2.0" } solana-fee-calculator = { path = "sdk/fee-calculator", version = "=2.2.0" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 3bce8f4eead116..753a95449feb69 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -38,6 +38,7 @@ solana-compute-budget = { workspace = true } solana-config-program = { workspace = true } solana-connection-cache = { workspace = true } solana-decode-error = { workspace = true } +solana-feature-gate-client = { workspace = true } solana-feature-set = { workspace = true } solana-loader-v4-program = { workspace = true } solana-logger = { workspace = true } diff --git a/cli/src/feature.rs b/cli/src/feature.rs index 0e1fdfd12ab1a4..e043e8b6c373a4 100644 --- a/cli/src/feature.rs +++ b/cli/src/feature.rs @@ -14,6 +14,9 @@ use { input_validators::*, keypair::*, }, solana_cli_output::{cli_version::CliVersion, QuietDisplay, VerboseDisplay}, + solana_feature_gate_client::{ + errors::SolanaFeatureGateError, instructions::RevokePendingActivation, + }, solana_feature_set::FEATURE_NAMES, solana_remote_wallet::remote_wallet::RemoteWalletManager, solana_rpc_client::rpc_client::RpcClient, @@ -27,10 +30,12 @@ use { epoch_schedule::EpochSchedule, feature::{self, Feature}, genesis_config::ClusterType, + incinerator, message::Message, pubkey::Pubkey, stake_history::Epoch, system_instruction::SystemError, + system_program, transaction::Transaction, }, std::{cmp::Ordering, collections::HashMap, fmt, rc::Rc, str::FromStr}, @@ -57,6 +62,11 @@ pub enum FeatureCliCommand { force: ForceActivation, fee_payer: SignerIndex, }, + Revoke { + feature: Pubkey, + cluster: ClusterType, + fee_payer: SignerIndex, + }, } #[derive(Serialize, Deserialize, PartialEq, Eq)] @@ -481,6 +491,26 @@ impl FeatureSubCommands for App<'_, '_> { .help("Override activation sanity checks. Don't use this flag"), ) .arg(fee_payer_arg()), + ) + .subcommand( + SubCommand::with_name("revoke") + .about("Revoke a pending runtime feature") + .arg( + Arg::with_name("feature") + .value_name("FEATURE_KEYPAIR") + .validator(is_valid_signer) + .index(1) + .required(true) + .help("The signer for the feature to revoke"), + ) + .arg( + Arg::with_name("cluster") + .value_name("CLUSTER") + .possible_values(&ClusterType::STRINGS) + .required(true) + .help("The cluster to revoke the feature on"), + ) + .arg(fee_payer_arg()), ), ) } @@ -534,6 +564,31 @@ pub fn parse_feature_subcommand( signers: signer_info.signers, } } + ("revoke", 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)?; + + let signer_info = default_signer.generate_unique_signers( + vec![fee_payer, feature_signer], + matches, + wallet_manager, + )?; + + let feature = feature.unwrap(); + + known_feature(&feature)?; + + CliCommandInfo { + command: CliCommand::Feature(FeatureCliCommand::Revoke { + feature, + cluster, + fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(), + }), + signers: signer_info.signers, + } + } ("status", Some(matches)) => { let mut features = if let Some(features) = pubkeys_of(matches, "features") { for feature in &features { @@ -572,6 +627,11 @@ pub fn process_feature_subcommand( force, fee_payer, } => process_activate(rpc_client, config, *feature, *cluster, *force, *fee_payer), + FeatureCliCommand::Revoke { + feature, + cluster, + fee_payer, + } => process_revoke(rpc_client, config, *feature, *cluster, *fee_payer), } } @@ -997,3 +1057,66 @@ fn process_activate( ); log_instruction_custom_error::(result, config) } + +fn process_revoke( + rpc_client: &RpcClient, + config: &CliConfig, + feature_id: Pubkey, + cluster: ClusterType, + 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])? + .into_iter() + .next() + .unwrap(); + + match account.and_then(status_from_account) { + Some(CliFeatureStatus::Pending) => (), + Some(CliFeatureStatus::Active(..)) => { + return Err(format!("{feature_id} has already been fully activated").into()); + } + Some(CliFeatureStatus::Inactive) | None => { + return Err(format!("{feature_id} has not been submitted for activation").into()); + } + } + + let blockhash = rpc_client.get_latest_blockhash()?; + let (message, _) = resolve_spend_tx_and_check_account_balance( + rpc_client, + false, + SpendAmount::Some(0), + &blockhash, + &fee_payer.pubkey(), + ComputeUnitLimit::Default, + |_lamports| { + Message::new( + &[RevokePendingActivation { + feature: feature_id, + incinerator: incinerator::id(), + system_program: system_program::id(), + } + .instruction()], + Some(&fee_payer.pubkey()), + ) + }, + config.commitment, + )?; + let mut transaction = Transaction::new_unsigned(message); + transaction.try_sign(&config.signers, blockhash)?; + + println!( + "Revoking {} ({})", + FEATURE_NAMES.get(&feature_id).unwrap(), + feature_id + ); + let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config( + &transaction, + config.commitment, + config.send_transaction_config, + ); + log_instruction_custom_error::(result, config) +} From e80af9e5c8b2907f3d1764a53d35e2a466d454d9 Mon Sep 17 00:00:00 2001 From: Joe Caulfield Date: Thu, 16 Jan 2025 11:52:47 +0800 Subject: [PATCH 2/3] use get_account from rpc --- cli/src/feature.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cli/src/feature.rs b/cli/src/feature.rs index e043e8b6c373a4..ff55a6f27bb73f 100644 --- a/cli/src/feature.rs +++ b/cli/src/feature.rs @@ -1068,11 +1068,7 @@ fn process_revoke( check_rpc_genesis_hash(&cluster, rpc_client)?; let fee_payer = config.signers[fee_payer]; - let account = rpc_client - .get_multiple_accounts(&[feature_id])? - .into_iter() - .next() - .unwrap(); + let account = rpc_client.get_account(&feature_id).ok(); match account.and_then(status_from_account) { Some(CliFeatureStatus::Pending) => (), From a51f5a7702ff07f2fad84b0e2245e05fcb33490f Mon Sep 17 00:00:00 2001 From: Joe Caulfield Date: Thu, 16 Jan 2025 23:22:22 +0800 Subject: [PATCH 3/3] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e40ad716dd1f58..3ca15cf53af6a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Release channels have their own copy of this changelog: * Changes * CLI: * Add global `--skip-preflight` option for skipping preflight checks on all transactions sent through RPC. This flag, along with `--use-rpc`, can improve success rate with program deployments using the public RPC nodes. + * Add new command `solana feature revoke` for revoking pending feature activations. When a feature is activated, `solana feature revoke ` can be used to deallocate and reassign the account to the System program, undoing the operation. This can only be done before the feature becomes active. * Unhide `--accounts-db-access-storages-method` for agave-validator and agave-ledger-tool and change default to `file` * Remove tracer stats from banking-trace. `banking-trace` directory should be cleared when restarting on v2.2 for first time. It will not break if not cleared, but the file will be a mix of new/old format. (#4043)