Skip to content

Commit 91d0d0c

Browse files
authored
cli: add feature revoke (#4361)
* cli: add feature revoke * use get_account from rpc * add changelog entry
1 parent 766cd68 commit 91d0d0c

File tree

5 files changed

+136
-0
lines changed

5 files changed

+136
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Release channels have their own copy of this changelog:
1717
* Changes
1818
* CLI:
1919
* 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.
20+
* Add new command `solana feature revoke` for revoking pending feature activations. When a feature is activated, `solana feature revoke <feature-keypair> <cluster>` 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.
2021
* Unhide `--accounts-db-access-storages-method` for agave-validator and agave-ledger-tool and change default to `file`
2122
* 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)
2223

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ solana-epoch-rewards = { path = "sdk/epoch-rewards", version = "=2.2.0" }
490490
solana-epoch-rewards-hasher = { path = "sdk/epoch-rewards-hasher", version = "=2.2.0" }
491491
solana-epoch-schedule = { path = "sdk/epoch-schedule", version = "=2.2.0" }
492492
solana-faucet = { path = "faucet", version = "=2.2.0" }
493+
solana-feature-gate-client = "0.0.2"
493494
solana-feature-gate-interface = { path = "sdk/feature-gate-interface", version = "=2.2.0" }
494495
solana-feature-set = { path = "sdk/feature-set", version = "=2.2.0" }
495496
solana-fee-calculator = { path = "sdk/fee-calculator", version = "=2.2.0" }

cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ solana-compute-budget = { workspace = true }
3939
solana-config-program = { workspace = true }
4040
solana-connection-cache = { workspace = true }
4141
solana-decode-error = { workspace = true }
42+
solana-feature-gate-client = { workspace = true }
4243
solana-feature-set = { workspace = true }
4344
solana-loader-v4-program = { workspace = true }
4445
solana-logger = { workspace = true }

cli/src/feature.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ use {
1414
input_validators::*, keypair::*,
1515
},
1616
solana_cli_output::{cli_version::CliVersion, QuietDisplay, VerboseDisplay},
17+
solana_feature_gate_client::{
18+
errors::SolanaFeatureGateError, instructions::RevokePendingActivation,
19+
},
1720
solana_feature_set::FEATURE_NAMES,
1821
solana_remote_wallet::remote_wallet::RemoteWalletManager,
1922
solana_rpc_client::rpc_client::RpcClient,
@@ -27,10 +30,12 @@ use {
2730
epoch_schedule::EpochSchedule,
2831
feature::{self, Feature},
2932
genesis_config::ClusterType,
33+
incinerator,
3034
message::Message,
3135
pubkey::Pubkey,
3236
stake_history::Epoch,
3337
system_instruction::SystemError,
38+
system_program,
3439
transaction::Transaction,
3540
},
3641
std::{cmp::Ordering, collections::HashMap, fmt, rc::Rc, str::FromStr},
@@ -57,6 +62,11 @@ pub enum FeatureCliCommand {
5762
force: ForceActivation,
5863
fee_payer: SignerIndex,
5964
},
65+
Revoke {
66+
feature: Pubkey,
67+
cluster: ClusterType,
68+
fee_payer: SignerIndex,
69+
},
6070
}
6171

6272
#[derive(Serialize, Deserialize, PartialEq, Eq)]
@@ -481,6 +491,26 @@ impl FeatureSubCommands for App<'_, '_> {
481491
.help("Override activation sanity checks. Don't use this flag"),
482492
)
483493
.arg(fee_payer_arg()),
494+
)
495+
.subcommand(
496+
SubCommand::with_name("revoke")
497+
.about("Revoke a pending runtime feature")
498+
.arg(
499+
Arg::with_name("feature")
500+
.value_name("FEATURE_KEYPAIR")
501+
.validator(is_valid_signer)
502+
.index(1)
503+
.required(true)
504+
.help("The signer for the feature to revoke"),
505+
)
506+
.arg(
507+
Arg::with_name("cluster")
508+
.value_name("CLUSTER")
509+
.possible_values(&ClusterType::STRINGS)
510+
.required(true)
511+
.help("The cluster to revoke the feature on"),
512+
)
513+
.arg(fee_payer_arg()),
484514
),
485515
)
486516
}
@@ -534,6 +564,31 @@ pub fn parse_feature_subcommand(
534564
signers: signer_info.signers,
535565
}
536566
}
567+
("revoke", Some(matches)) => {
568+
let cluster = value_t_or_exit!(matches, "cluster", ClusterType);
569+
let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?;
570+
let (fee_payer, fee_payer_pubkey) =
571+
signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
572+
573+
let signer_info = default_signer.generate_unique_signers(
574+
vec![fee_payer, feature_signer],
575+
matches,
576+
wallet_manager,
577+
)?;
578+
579+
let feature = feature.unwrap();
580+
581+
known_feature(&feature)?;
582+
583+
CliCommandInfo {
584+
command: CliCommand::Feature(FeatureCliCommand::Revoke {
585+
feature,
586+
cluster,
587+
fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
588+
}),
589+
signers: signer_info.signers,
590+
}
591+
}
537592
("status", Some(matches)) => {
538593
let mut features = if let Some(features) = pubkeys_of(matches, "features") {
539594
for feature in &features {
@@ -572,6 +627,11 @@ pub fn process_feature_subcommand(
572627
force,
573628
fee_payer,
574629
} => process_activate(rpc_client, config, *feature, *cluster, *force, *fee_payer),
630+
FeatureCliCommand::Revoke {
631+
feature,
632+
cluster,
633+
fee_payer,
634+
} => process_revoke(rpc_client, config, *feature, *cluster, *fee_payer),
575635
}
576636
}
577637

@@ -997,3 +1057,62 @@ fn process_activate(
9971057
);
9981058
log_instruction_custom_error::<SystemError>(result, config)
9991059
}
1060+
1061+
fn process_revoke(
1062+
rpc_client: &RpcClient,
1063+
config: &CliConfig,
1064+
feature_id: Pubkey,
1065+
cluster: ClusterType,
1066+
fee_payer: SignerIndex,
1067+
) -> ProcessResult {
1068+
check_rpc_genesis_hash(&cluster, rpc_client)?;
1069+
1070+
let fee_payer = config.signers[fee_payer];
1071+
let account = rpc_client.get_account(&feature_id).ok();
1072+
1073+
match account.and_then(status_from_account) {
1074+
Some(CliFeatureStatus::Pending) => (),
1075+
Some(CliFeatureStatus::Active(..)) => {
1076+
return Err(format!("{feature_id} has already been fully activated").into());
1077+
}
1078+
Some(CliFeatureStatus::Inactive) | None => {
1079+
return Err(format!("{feature_id} has not been submitted for activation").into());
1080+
}
1081+
}
1082+
1083+
let blockhash = rpc_client.get_latest_blockhash()?;
1084+
let (message, _) = resolve_spend_tx_and_check_account_balance(
1085+
rpc_client,
1086+
false,
1087+
SpendAmount::Some(0),
1088+
&blockhash,
1089+
&fee_payer.pubkey(),
1090+
ComputeUnitLimit::Default,
1091+
|_lamports| {
1092+
Message::new(
1093+
&[RevokePendingActivation {
1094+
feature: feature_id,
1095+
incinerator: incinerator::id(),
1096+
system_program: system_program::id(),
1097+
}
1098+
.instruction()],
1099+
Some(&fee_payer.pubkey()),
1100+
)
1101+
},
1102+
config.commitment,
1103+
)?;
1104+
let mut transaction = Transaction::new_unsigned(message);
1105+
transaction.try_sign(&config.signers, blockhash)?;
1106+
1107+
println!(
1108+
"Revoking {} ({})",
1109+
FEATURE_NAMES.get(&feature_id).unwrap(),
1110+
feature_id
1111+
);
1112+
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1113+
&transaction,
1114+
config.commitment,
1115+
config.send_transaction_config,
1116+
);
1117+
log_instruction_custom_error::<SolanaFeatureGateError>(result, config)
1118+
}

0 commit comments

Comments
 (0)