2021-12-03 09:00:31 -08:00
|
|
|
use {
|
|
|
|
crate::{
|
|
|
|
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
|
|
|
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
|
|
|
},
|
|
|
|
clap::{App, AppSettings, Arg, ArgMatches, SubCommand},
|
|
|
|
console::style,
|
|
|
|
serde::{Deserialize, Serialize},
|
|
|
|
solana_clap_utils::{input_parsers::*, input_validators::*, keypair::*},
|
|
|
|
solana_cli_output::{QuietDisplay, VerboseDisplay},
|
|
|
|
solana_client::{client_error::ClientError, rpc_client::RpcClient},
|
|
|
|
solana_remote_wallet::remote_wallet::RemoteWalletManager,
|
|
|
|
solana_sdk::{
|
|
|
|
account::Account,
|
|
|
|
clock::Slot,
|
|
|
|
feature::{self, Feature},
|
|
|
|
feature_set::FEATURE_NAMES,
|
|
|
|
message::Message,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
transaction::Transaction,
|
|
|
|
},
|
|
|
|
std::{
|
|
|
|
cmp::Ordering,
|
|
|
|
collections::{HashMap, HashSet},
|
|
|
|
fmt,
|
|
|
|
sync::Arc,
|
|
|
|
},
|
2021-10-21 23:51:24 -07:00
|
|
|
};
|
2020-09-23 13:36:34 -07:00
|
|
|
|
2021-01-22 11:56:51 -08:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
|
|
pub enum ForceActivation {
|
|
|
|
No,
|
|
|
|
Almost,
|
|
|
|
Yes,
|
|
|
|
}
|
|
|
|
|
2020-09-23 13:36:34 -07:00
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
pub enum FeatureCliCommand {
|
2021-01-22 11:56:51 -08:00
|
|
|
Status {
|
|
|
|
features: Vec<Pubkey>,
|
|
|
|
},
|
|
|
|
Activate {
|
|
|
|
feature: Pubkey,
|
|
|
|
force: ForceActivation,
|
|
|
|
},
|
2020-09-23 13:36:34 -07:00
|
|
|
}
|
|
|
|
|
2021-12-13 04:42:57 -08:00
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
2020-09-29 12:26:08 -07:00
|
|
|
#[serde(rename_all = "camelCase", tag = "status", content = "sinceSlot")]
|
|
|
|
pub enum CliFeatureStatus {
|
|
|
|
Inactive,
|
|
|
|
Pending,
|
|
|
|
Active(Slot),
|
|
|
|
}
|
|
|
|
|
2021-12-13 04:42:57 -08:00
|
|
|
impl PartialOrd for CliFeatureStatus {
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
|
|
Some(self.cmp(other))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Ord for CliFeatureStatus {
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
match (self, other) {
|
|
|
|
(Self::Inactive, Self::Inactive) => Ordering::Equal,
|
|
|
|
(Self::Inactive, _) => Ordering::Greater,
|
|
|
|
(_, Self::Inactive) => Ordering::Less,
|
|
|
|
(Self::Pending, Self::Pending) => Ordering::Equal,
|
|
|
|
(Self::Pending, _) => Ordering::Greater,
|
|
|
|
(_, Self::Pending) => Ordering::Less,
|
|
|
|
(Self::Active(self_active_slot), Self::Active(other_active_slot)) => {
|
|
|
|
self_active_slot.cmp(other_active_slot)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
2020-09-29 12:26:08 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliFeature {
|
|
|
|
pub id: String,
|
|
|
|
pub description: String,
|
|
|
|
#[serde(flatten)]
|
|
|
|
pub status: CliFeatureStatus,
|
|
|
|
}
|
|
|
|
|
2021-12-13 04:42:57 -08:00
|
|
|
impl PartialOrd for CliFeature {
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
|
|
Some(self.cmp(other))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Ord for CliFeature {
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
match self.status.cmp(&other.status) {
|
|
|
|
Ordering::Equal => self.id.cmp(&other.id),
|
|
|
|
ordering => ordering,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-29 12:26:08 -07:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliFeatures {
|
|
|
|
pub features: Vec<CliFeature>,
|
|
|
|
pub feature_activation_allowed: bool,
|
|
|
|
#[serde(skip)]
|
|
|
|
pub inactive: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for CliFeatures {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
if self.features.len() > 1 {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"{}",
|
|
|
|
style(format!(
|
2021-02-08 12:08:29 -08:00
|
|
|
"{:<44} | {:<27} | {}",
|
2021-02-04 20:23:01 -08:00
|
|
|
"Feature", "Status", "Description"
|
2020-09-29 12:26:08 -07:00
|
|
|
))
|
|
|
|
.bold()
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
for feature in &self.features {
|
|
|
|
writeln!(
|
|
|
|
f,
|
2021-02-08 12:08:29 -08:00
|
|
|
"{:<44} | {:<27} | {}",
|
2020-09-29 12:26:08 -07:00
|
|
|
feature.id,
|
|
|
|
match feature.status {
|
|
|
|
CliFeatureStatus::Inactive => style("inactive".to_string()).red(),
|
|
|
|
CliFeatureStatus::Pending => style("activation pending".to_string()).yellow(),
|
|
|
|
CliFeatureStatus::Active(activation_slot) =>
|
2021-12-13 04:42:57 -08:00
|
|
|
style(format!("active since slot {:>9}", activation_slot)).green(),
|
2021-02-04 20:23:01 -08:00
|
|
|
},
|
|
|
|
feature.description,
|
2020-09-29 12:26:08 -07:00
|
|
|
)?;
|
|
|
|
}
|
|
|
|
if self.inactive && !self.feature_activation_allowed {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"{}",
|
|
|
|
style("\nFeature activation is not allowed at this time")
|
|
|
|
.bold()
|
|
|
|
.red()
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QuietDisplay for CliFeatures {}
|
|
|
|
impl VerboseDisplay for CliFeatures {}
|
|
|
|
|
2020-09-23 13:36:34 -07:00
|
|
|
pub trait FeatureSubCommands {
|
|
|
|
fn feature_subcommands(self) -> Self;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FeatureSubCommands for App<'_, '_> {
|
|
|
|
fn feature_subcommands(self) -> Self {
|
|
|
|
self.subcommand(
|
|
|
|
SubCommand::with_name("feature")
|
|
|
|
.about("Runtime feature management")
|
|
|
|
.setting(AppSettings::SubcommandRequiredElseHelp)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("status")
|
|
|
|
.about("Query runtime feature status")
|
|
|
|
.arg(
|
2020-09-24 13:30:38 -07:00
|
|
|
Arg::with_name("features")
|
2020-09-23 13:36:34 -07:00
|
|
|
.value_name("ADDRESS")
|
|
|
|
.validator(is_valid_pubkey)
|
|
|
|
.index(1)
|
2020-09-24 13:30:38 -07:00
|
|
|
.multiple(true)
|
|
|
|
.help("Feature status to query [default: all known features]"),
|
2020-09-23 13:36:34 -07:00
|
|
|
),
|
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
SubCommand::with_name("activate")
|
|
|
|
.about("Activate a runtime feature")
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("feature")
|
|
|
|
.value_name("FEATURE_KEYPAIR")
|
|
|
|
.validator(is_valid_signer)
|
|
|
|
.index(1)
|
2020-09-24 13:30:38 -07:00
|
|
|
.required(true)
|
2020-09-23 13:36:34 -07:00
|
|
|
.help("The signer for the feature to activate"),
|
2021-01-22 11:56:51 -08:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::with_name("force")
|
|
|
|
.long("yolo")
|
|
|
|
.hidden(true)
|
|
|
|
.multiple(true)
|
|
|
|
.help("Override activation sanity checks. Don't use this flag"),
|
2020-09-23 13:36:34 -07:00
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn known_feature(feature: &Pubkey) -> Result<(), CliError> {
|
|
|
|
if FEATURE_NAMES.contains_key(feature) {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(CliError::BadParameter(format!(
|
|
|
|
"Unknown feature: {}",
|
|
|
|
feature
|
|
|
|
)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-24 13:30:38 -07:00
|
|
|
pub fn parse_feature_subcommand(
|
2020-09-23 13:36:34 -07:00
|
|
|
matches: &ArgMatches<'_>,
|
|
|
|
default_signer: &DefaultSigner,
|
|
|
|
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
|
|
|
) -> Result<CliCommandInfo, CliError> {
|
|
|
|
let response = match matches.subcommand() {
|
|
|
|
("activate", Some(matches)) => {
|
|
|
|
let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?;
|
|
|
|
let mut signers = vec![default_signer.signer_from_path(matches, wallet_manager)?];
|
2021-01-22 11:56:51 -08:00
|
|
|
|
|
|
|
let force = match matches.occurrences_of("force") {
|
|
|
|
2 => ForceActivation::Yes,
|
|
|
|
1 => ForceActivation::Almost,
|
|
|
|
_ => ForceActivation::No,
|
|
|
|
};
|
|
|
|
|
2020-09-23 13:36:34 -07:00
|
|
|
signers.push(feature_signer.unwrap());
|
|
|
|
let feature = feature.unwrap();
|
|
|
|
|
|
|
|
known_feature(&feature)?;
|
|
|
|
|
|
|
|
CliCommandInfo {
|
2021-01-22 11:56:51 -08:00
|
|
|
command: CliCommand::Feature(FeatureCliCommand::Activate { feature, force }),
|
2020-09-23 13:36:34 -07:00
|
|
|
signers,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
("status", Some(matches)) => {
|
2020-09-24 13:30:38 -07:00
|
|
|
let mut features = if let Some(features) = pubkeys_of(matches, "features") {
|
|
|
|
for feature in &features {
|
|
|
|
known_feature(feature)?;
|
|
|
|
}
|
|
|
|
features
|
2020-09-23 13:36:34 -07:00
|
|
|
} else {
|
|
|
|
FEATURE_NAMES.keys().cloned().collect()
|
|
|
|
};
|
|
|
|
features.sort();
|
|
|
|
CliCommandInfo {
|
|
|
|
command: CliCommand::Feature(FeatureCliCommand::Status { features }),
|
|
|
|
signers: vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn process_feature_subcommand(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &CliConfig,
|
|
|
|
feature_subcommand: &FeatureCliCommand,
|
|
|
|
) -> ProcessResult {
|
|
|
|
match feature_subcommand {
|
2020-09-29 12:26:08 -07:00
|
|
|
FeatureCliCommand::Status { features } => process_status(rpc_client, config, features),
|
2021-01-22 11:56:51 -08:00
|
|
|
FeatureCliCommand::Activate { feature, force } => {
|
|
|
|
process_activate(rpc_client, config, *feature, *force)
|
|
|
|
}
|
2020-09-23 13:36:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-21 15:28:11 -07:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
struct WorkingFeatureSetStatsEntry {
|
|
|
|
stake: u64,
|
|
|
|
rpc_nodes_count: u32,
|
2021-10-21 23:51:24 -07:00
|
|
|
software_versions: HashSet<Option<semver::Version>>,
|
2021-10-21 15:28:11 -07:00
|
|
|
}
|
|
|
|
type WorkingFeatureSetStats = HashMap<u32, WorkingFeatureSetStatsEntry>;
|
|
|
|
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
struct FeatureSetStatsEntry {
|
|
|
|
stake_percent: f64,
|
|
|
|
rpc_nodes_percent: f32,
|
2021-10-21 23:51:24 -07:00
|
|
|
software_versions: Vec<Option<semver::Version>>,
|
2021-10-21 15:28:11 -07:00
|
|
|
}
|
|
|
|
type FeatureSetStats = HashMap<u32, FeatureSetStatsEntry>;
|
|
|
|
|
|
|
|
fn feature_set_stats(rpc_client: &RpcClient) -> Result<FeatureSetStats, ClientError> {
|
2020-09-28 20:20:11 -07:00
|
|
|
// Validator identity -> feature set
|
2021-10-15 19:16:12 -07:00
|
|
|
let feature_sets = rpc_client
|
2020-09-23 13:36:34 -07:00
|
|
|
.get_cluster_nodes()?
|
|
|
|
.into_iter()
|
2021-10-15 19:16:12 -07:00
|
|
|
.map(|contact_info| {
|
|
|
|
(
|
|
|
|
contact_info.pubkey,
|
|
|
|
contact_info.feature_set,
|
|
|
|
contact_info.rpc.is_some(),
|
2021-10-21 23:51:24 -07:00
|
|
|
contact_info
|
|
|
|
.version
|
|
|
|
.and_then(|v| semver::Version::parse(&v).ok()),
|
2021-10-15 19:16:12 -07:00
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
2020-09-23 13:36:34 -07:00
|
|
|
|
|
|
|
let vote_accounts = rpc_client.get_vote_accounts()?;
|
|
|
|
|
2021-10-15 19:16:12 -07:00
|
|
|
let mut total_active_stake: u64 = vote_accounts
|
|
|
|
.delinquent
|
2020-09-23 13:36:34 -07:00
|
|
|
.iter()
|
|
|
|
.map(|vote_account| vote_account.activated_stake)
|
|
|
|
.sum();
|
|
|
|
|
2021-10-15 19:16:12 -07:00
|
|
|
let vote_stakes = vote_accounts
|
|
|
|
.current
|
|
|
|
.into_iter()
|
|
|
|
.map(|vote_account| {
|
|
|
|
total_active_stake += vote_account.activated_stake;
|
|
|
|
(vote_account.node_pubkey, vote_account.activated_stake)
|
|
|
|
})
|
|
|
|
.collect::<HashMap<_, _>>();
|
|
|
|
|
2021-10-21 15:28:11 -07:00
|
|
|
let mut feature_set_stats: WorkingFeatureSetStats = HashMap::new();
|
2021-10-15 19:16:12 -07:00
|
|
|
let mut total_rpc_nodes = 0;
|
2021-10-21 23:51:24 -07:00
|
|
|
for (node_id, feature_set, is_rpc, version) in feature_sets {
|
2021-10-15 19:16:12 -07:00
|
|
|
let feature_set = feature_set.unwrap_or(0);
|
|
|
|
let feature_set_entry = feature_set_stats.entry(feature_set).or_default();
|
|
|
|
|
2021-10-21 23:51:24 -07:00
|
|
|
feature_set_entry.software_versions.insert(version);
|
|
|
|
|
2021-10-15 19:16:12 -07:00
|
|
|
if let Some(vote_stake) = vote_stakes.get(&node_id) {
|
2021-10-21 15:28:11 -07:00
|
|
|
feature_set_entry.stake += *vote_stake;
|
2021-10-15 19:16:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if is_rpc {
|
2021-10-21 15:28:11 -07:00
|
|
|
feature_set_entry.rpc_nodes_count += 1;
|
2021-10-15 19:16:12 -07:00
|
|
|
total_rpc_nodes += 1;
|
2020-09-28 20:20:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-15 19:16:12 -07:00
|
|
|
Ok(feature_set_stats
|
2021-02-22 09:31:13 -08:00
|
|
|
.into_iter()
|
2021-10-21 15:28:11 -07:00
|
|
|
.filter_map(
|
|
|
|
|(
|
|
|
|
feature_set,
|
|
|
|
WorkingFeatureSetStatsEntry {
|
|
|
|
stake,
|
|
|
|
rpc_nodes_count,
|
2021-10-21 23:51:24 -07:00
|
|
|
software_versions,
|
2021-10-21 15:28:11 -07:00
|
|
|
},
|
|
|
|
)| {
|
2021-10-22 18:16:42 -07:00
|
|
|
let stake_percent = (stake as f64 / total_active_stake as f64) * 100.;
|
|
|
|
let rpc_nodes_percent = (rpc_nodes_count as f32 / total_rpc_nodes as f32) * 100.;
|
2021-10-21 23:51:24 -07:00
|
|
|
let mut software_versions = software_versions.into_iter().collect::<Vec<_>>();
|
|
|
|
software_versions.sort();
|
2021-10-21 15:28:11 -07:00
|
|
|
if stake_percent >= 0.001 || rpc_nodes_percent >= 0.001 {
|
|
|
|
Some((
|
|
|
|
feature_set,
|
|
|
|
FeatureSetStatsEntry {
|
|
|
|
stake_percent,
|
|
|
|
rpc_nodes_percent,
|
2021-10-21 23:51:24 -07:00
|
|
|
software_versions,
|
2021-10-21 15:28:11 -07:00
|
|
|
},
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
2021-02-22 09:31:13 -08:00
|
|
|
.collect())
|
2020-09-28 20:20:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Feature activation is only allowed when 95% of the active stake is on the current feature set
|
2020-10-20 21:34:51 -07:00
|
|
|
fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<bool, ClientError> {
|
2020-09-28 20:20:11 -07:00
|
|
|
let my_feature_set = solana_version::Version::default().feature_set;
|
|
|
|
|
2021-10-15 19:16:12 -07:00
|
|
|
let feature_set_stats = feature_set_stats(rpc_client)?;
|
2020-09-28 20:20:11 -07:00
|
|
|
|
2021-10-15 19:16:12 -07:00
|
|
|
let (stake_allowed, rpc_allowed) = feature_set_stats
|
2020-09-28 20:20:11 -07:00
|
|
|
.get(&my_feature_set)
|
2021-10-21 15:28:11 -07:00
|
|
|
.map(
|
|
|
|
|FeatureSetStatsEntry {
|
|
|
|
stake_percent,
|
|
|
|
rpc_nodes_percent,
|
2021-10-21 23:51:24 -07:00
|
|
|
..
|
2021-10-21 15:28:11 -07:00
|
|
|
}| (*stake_percent >= 95., *rpc_nodes_percent >= 95.),
|
|
|
|
)
|
2021-10-15 19:16:12 -07:00
|
|
|
.unwrap_or((false, false));
|
2020-09-28 20:20:11 -07:00
|
|
|
|
2021-11-29 20:19:23 -08:00
|
|
|
if !quiet {
|
2021-10-15 19:16:12 -07:00
|
|
|
if feature_set_stats.get(&my_feature_set).is_none() {
|
2020-11-03 16:08:08 -08:00
|
|
|
println!(
|
|
|
|
"{}",
|
|
|
|
style("To activate features the tool and cluster feature sets must match, select a tool version that matches the cluster")
|
|
|
|
.bold());
|
|
|
|
} else {
|
2021-10-15 19:16:12 -07:00
|
|
|
if !stake_allowed {
|
|
|
|
print!(
|
|
|
|
"\n{}",
|
|
|
|
style("To activate features the stake must be >= 95%")
|
|
|
|
.bold()
|
|
|
|
.red()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if !rpc_allowed {
|
|
|
|
print!(
|
|
|
|
"\n{}",
|
|
|
|
style("To activate features the RPC nodes must be >= 95%")
|
|
|
|
.bold()
|
|
|
|
.red()
|
|
|
|
);
|
|
|
|
}
|
2020-11-03 16:08:08 -08:00
|
|
|
}
|
|
|
|
println!(
|
2021-10-15 19:16:12 -07:00
|
|
|
"\n\n{}",
|
2021-02-12 23:07:04 -08:00
|
|
|
style(format!("Tool Feature Set: {}", my_feature_set)).bold()
|
2020-11-03 16:08:08 -08:00
|
|
|
);
|
2021-10-22 00:50:25 -07:00
|
|
|
|
|
|
|
let mut feature_set_stats = feature_set_stats.into_iter().collect::<Vec<_>>();
|
|
|
|
feature_set_stats.sort_by(|l, r| {
|
|
|
|
match l.1.software_versions[0]
|
|
|
|
.cmp(&r.1.software_versions[0])
|
|
|
|
.reverse()
|
|
|
|
{
|
|
|
|
Ordering::Equal => {
|
|
|
|
match l
|
|
|
|
.1
|
|
|
|
.stake_percent
|
|
|
|
.partial_cmp(&r.1.stake_percent)
|
|
|
|
.unwrap()
|
|
|
|
.reverse()
|
|
|
|
{
|
|
|
|
Ordering::Equal => {
|
|
|
|
l.1.rpc_nodes_percent
|
|
|
|
.partial_cmp(&r.1.rpc_nodes_percent)
|
|
|
|
.unwrap()
|
|
|
|
.reverse()
|
|
|
|
}
|
|
|
|
o => o,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
o => o,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-10-21 23:51:24 -07:00
|
|
|
let software_versions_title = "Software Version";
|
2021-10-15 19:16:12 -07:00
|
|
|
let feature_set_title = "Feature Set";
|
|
|
|
let stake_percent_title = "Stake";
|
|
|
|
let rpc_percent_title = "RPC";
|
|
|
|
let mut stats_output = Vec::new();
|
2021-10-21 23:51:24 -07:00
|
|
|
let mut max_software_versions_len = software_versions_title.len();
|
2021-10-15 19:16:12 -07:00
|
|
|
let mut max_feature_set_len = feature_set_title.len();
|
|
|
|
let mut max_stake_percent_len = stake_percent_title.len();
|
|
|
|
let mut max_rpc_percent_len = rpc_percent_title.len();
|
2021-10-21 15:28:11 -07:00
|
|
|
for (
|
|
|
|
feature_set,
|
|
|
|
FeatureSetStatsEntry {
|
|
|
|
stake_percent,
|
|
|
|
rpc_nodes_percent,
|
2021-10-21 23:51:24 -07:00
|
|
|
software_versions,
|
2021-10-21 15:28:11 -07:00
|
|
|
},
|
|
|
|
) in feature_set_stats.into_iter()
|
|
|
|
{
|
|
|
|
let me = feature_set == my_feature_set;
|
|
|
|
let feature_set = if feature_set == 0 {
|
2021-10-15 19:16:12 -07:00
|
|
|
"unknown".to_string()
|
2020-09-23 13:36:34 -07:00
|
|
|
} else {
|
2021-10-15 19:16:12 -07:00
|
|
|
feature_set.to_string()
|
|
|
|
};
|
|
|
|
let stake_percent = format!("{:.2}%", stake_percent);
|
2021-10-21 15:28:11 -07:00
|
|
|
let rpc_percent = format!("{:.2}%", rpc_nodes_percent);
|
2021-10-15 19:16:12 -07:00
|
|
|
|
2021-10-21 23:51:24 -07:00
|
|
|
let mut has_unknown = false;
|
|
|
|
let mut software_versions = software_versions
|
|
|
|
.iter()
|
|
|
|
.filter_map(|v| {
|
|
|
|
if v.is_none() {
|
|
|
|
has_unknown = true;
|
|
|
|
}
|
|
|
|
v.as_ref()
|
|
|
|
})
|
|
|
|
.map(ToString::to_string)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
if has_unknown {
|
|
|
|
software_versions.push("unknown".to_string());
|
|
|
|
}
|
|
|
|
let software_versions = software_versions.join(", ");
|
|
|
|
max_software_versions_len = max_software_versions_len.max(software_versions.len());
|
|
|
|
|
2021-10-15 19:16:12 -07:00
|
|
|
max_feature_set_len = max_feature_set_len.max(feature_set.len());
|
|
|
|
max_stake_percent_len = max_stake_percent_len.max(stake_percent.len());
|
|
|
|
max_rpc_percent_len = max_rpc_percent_len.max(rpc_percent.len());
|
|
|
|
|
2021-10-21 23:51:24 -07:00
|
|
|
stats_output.push((
|
|
|
|
software_versions,
|
|
|
|
feature_set,
|
|
|
|
stake_percent,
|
|
|
|
rpc_percent,
|
|
|
|
me,
|
|
|
|
));
|
2021-10-15 19:16:12 -07:00
|
|
|
}
|
|
|
|
println!(
|
|
|
|
"{}",
|
|
|
|
style(format!(
|
2021-10-21 23:51:24 -07:00
|
|
|
"{1:<0$} {3:<2$} {5:<4$} {7:<6$}",
|
|
|
|
max_software_versions_len,
|
|
|
|
software_versions_title,
|
2021-10-15 19:16:12 -07:00
|
|
|
max_feature_set_len,
|
|
|
|
feature_set_title,
|
|
|
|
max_stake_percent_len,
|
|
|
|
stake_percent_title,
|
|
|
|
max_rpc_percent_len,
|
|
|
|
rpc_percent_title,
|
|
|
|
))
|
|
|
|
.bold(),
|
|
|
|
);
|
2021-10-21 23:51:24 -07:00
|
|
|
for (software_versions, feature_set, stake_percent, rpc_percent, me) in stats_output {
|
2021-10-15 19:16:12 -07:00
|
|
|
println!(
|
2021-10-21 23:51:24 -07:00
|
|
|
"{1:<0$} {3:>2$} {5:>4$} {7:>6$} {8}",
|
|
|
|
max_software_versions_len,
|
|
|
|
software_versions,
|
2021-10-15 19:16:12 -07:00
|
|
|
max_feature_set_len,
|
|
|
|
feature_set,
|
|
|
|
max_stake_percent_len,
|
|
|
|
stake_percent,
|
|
|
|
max_rpc_percent_len,
|
|
|
|
rpc_percent,
|
|
|
|
if me { "<-- me" } else { "" },
|
|
|
|
);
|
2020-09-28 20:20:11 -07:00
|
|
|
}
|
2020-10-20 21:34:51 -07:00
|
|
|
println!();
|
2020-09-28 20:20:11 -07:00
|
|
|
}
|
2020-09-23 13:36:34 -07:00
|
|
|
|
2021-10-15 19:16:12 -07:00
|
|
|
Ok(stake_allowed && rpc_allowed)
|
2020-09-23 13:36:34 -07:00
|
|
|
}
|
|
|
|
|
2021-06-01 16:25:53 -07:00
|
|
|
fn status_from_account(account: Account) -> Option<CliFeatureStatus> {
|
|
|
|
feature::from_account(&account).map(|feature| match feature.activated_at {
|
|
|
|
None => CliFeatureStatus::Pending,
|
|
|
|
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_feature_status(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
feature_id: &Pubkey,
|
|
|
|
) -> Result<Option<CliFeatureStatus>, Box<dyn std::error::Error>> {
|
|
|
|
rpc_client
|
|
|
|
.get_account(feature_id)
|
|
|
|
.map(status_from_account)
|
|
|
|
.map_err(|e| e.into())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_feature_is_active(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
feature_id: &Pubkey,
|
|
|
|
) -> Result<bool, Box<dyn std::error::Error>> {
|
|
|
|
get_feature_status(rpc_client, feature_id)
|
|
|
|
.map(|status| matches!(status, Some(CliFeatureStatus::Active(_))))
|
|
|
|
}
|
|
|
|
|
2020-09-29 12:26:08 -07:00
|
|
|
fn process_status(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &CliConfig,
|
|
|
|
feature_ids: &[Pubkey],
|
|
|
|
) -> ProcessResult {
|
|
|
|
let mut features: Vec<CliFeature> = vec![];
|
2020-09-23 13:36:34 -07:00
|
|
|
let mut inactive = false;
|
|
|
|
for (i, account) in rpc_client
|
|
|
|
.get_multiple_accounts(feature_ids)?
|
|
|
|
.into_iter()
|
|
|
|
.enumerate()
|
|
|
|
{
|
|
|
|
let feature_id = &feature_ids[i];
|
|
|
|
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
|
|
|
|
if let Some(account) = account {
|
2021-06-01 16:25:53 -07:00
|
|
|
if let Some(feature_status) = status_from_account(account) {
|
2020-09-29 12:26:08 -07:00
|
|
|
features.push(CliFeature {
|
|
|
|
id: feature_id.to_string(),
|
|
|
|
description: feature_name.to_string(),
|
|
|
|
status: feature_status,
|
|
|
|
});
|
2020-09-23 13:36:34 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
inactive = true;
|
2020-09-29 12:26:08 -07:00
|
|
|
features.push(CliFeature {
|
|
|
|
id: feature_id.to_string(),
|
|
|
|
description: feature_name.to_string(),
|
|
|
|
status: CliFeatureStatus::Inactive,
|
|
|
|
});
|
2020-09-23 13:36:34 -07:00
|
|
|
}
|
|
|
|
|
2021-12-13 04:42:57 -08:00
|
|
|
features.sort_unstable();
|
|
|
|
|
2020-10-20 21:34:51 -07:00
|
|
|
let feature_activation_allowed = feature_activation_allowed(rpc_client, features.len() <= 1)?;
|
2020-09-29 12:26:08 -07:00
|
|
|
let feature_set = CliFeatures {
|
|
|
|
features,
|
2020-10-20 21:34:51 -07:00
|
|
|
feature_activation_allowed,
|
2020-09-29 12:26:08 -07:00
|
|
|
inactive,
|
|
|
|
};
|
|
|
|
Ok(config.output_format.formatted_string(&feature_set))
|
2020-09-23 13:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn process_activate(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
config: &CliConfig,
|
|
|
|
feature_id: Pubkey,
|
2021-01-22 11:56:51 -08:00
|
|
|
force: ForceActivation,
|
2020-09-23 13:36:34 -07:00
|
|
|
) -> ProcessResult {
|
|
|
|
let account = rpc_client
|
|
|
|
.get_multiple_accounts(&[feature_id])?
|
|
|
|
.into_iter()
|
|
|
|
.next()
|
|
|
|
.unwrap();
|
2021-01-22 11:56:51 -08:00
|
|
|
|
2020-09-23 13:36:34 -07:00
|
|
|
if let Some(account) = account {
|
2020-10-30 13:40:55 -07:00
|
|
|
if feature::from_account(&account).is_some() {
|
2020-09-23 13:36:34 -07:00
|
|
|
return Err(format!("{} has already been activated", feature_id).into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-20 21:34:51 -07:00
|
|
|
if !feature_activation_allowed(rpc_client, false)? {
|
2021-01-22 11:56:51 -08:00
|
|
|
match force {
|
|
|
|
ForceActivation::Almost =>
|
|
|
|
return Err("Add force argument once more to override the sanity check to force feature activation ".into()),
|
|
|
|
ForceActivation::Yes => println!("FEATURE ACTIVATION FORCED"),
|
|
|
|
ForceActivation::No =>
|
|
|
|
return Err("Feature activation is not allowed at this time".into()),
|
|
|
|
}
|
2020-09-23 13:36:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let rent = rpc_client.get_minimum_balance_for_rent_exemption(Feature::size_of())?;
|
|
|
|
|
2021-08-13 09:08:20 -07:00
|
|
|
let blockhash = rpc_client.get_latest_blockhash()?;
|
2020-09-23 13:36:34 -07:00
|
|
|
let (message, _) = resolve_spend_tx_and_check_account_balance(
|
|
|
|
rpc_client,
|
|
|
|
false,
|
|
|
|
SpendAmount::Some(rent),
|
2021-08-13 09:08:20 -07:00
|
|
|
&blockhash,
|
2020-09-23 13:36:34 -07:00
|
|
|
&config.signers[0].pubkey(),
|
|
|
|
|lamports| {
|
|
|
|
Message::new(
|
2020-10-30 13:40:55 -07:00
|
|
|
&feature::activate_with_lamports(
|
|
|
|
&feature_id,
|
|
|
|
&config.signers[0].pubkey(),
|
|
|
|
lamports,
|
|
|
|
),
|
2020-09-23 13:36:34 -07:00
|
|
|
Some(&config.signers[0].pubkey()),
|
|
|
|
)
|
|
|
|
},
|
|
|
|
config.commitment,
|
|
|
|
)?;
|
|
|
|
let mut transaction = Transaction::new_unsigned(message);
|
|
|
|
transaction.try_sign(&config.signers, blockhash)?;
|
|
|
|
|
|
|
|
println!(
|
|
|
|
"Activating {} ({})",
|
|
|
|
FEATURE_NAMES.get(&feature_id).unwrap(),
|
|
|
|
feature_id
|
|
|
|
);
|
|
|
|
rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
|
|
|
|
Ok("".to_string())
|
|
|
|
}
|