Add json support for feature sets; also print output after feature list (#21905)
* Add json support for feature sets; also print output after feature list * Move stringifying into Display implementation
This commit is contained in:
@ -5,7 +5,7 @@ use {
|
|||||||
},
|
},
|
||||||
clap::{App, AppSettings, Arg, ArgMatches, SubCommand},
|
clap::{App, AppSettings, Arg, ArgMatches, SubCommand},
|
||||||
console::style,
|
console::style,
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Deserializer, Serialize, Serializer},
|
||||||
solana_clap_utils::{input_parsers::*, input_validators::*, keypair::*},
|
solana_clap_utils::{input_parsers::*, input_validators::*, keypair::*},
|
||||||
solana_cli_output::{QuietDisplay, VerboseDisplay},
|
solana_cli_output::{QuietDisplay, VerboseDisplay},
|
||||||
solana_client::{client_error::ClientError, rpc_client::RpcClient},
|
solana_client::{client_error::ClientError, rpc_client::RpcClient},
|
||||||
@ -23,6 +23,7 @@ use {
|
|||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
fmt,
|
fmt,
|
||||||
|
str::FromStr,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -104,6 +105,8 @@ impl Ord for CliFeature {
|
|||||||
pub struct CliFeatures {
|
pub struct CliFeatures {
|
||||||
pub features: Vec<CliFeature>,
|
pub features: Vec<CliFeature>,
|
||||||
pub feature_activation_allowed: bool,
|
pub feature_activation_allowed: bool,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub cluster_feature_sets: Option<CliClusterFeatureSets>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub inactive: bool,
|
pub inactive: bool,
|
||||||
}
|
}
|
||||||
@ -135,6 +138,11 @@ impl fmt::Display for CliFeatures {
|
|||||||
feature.description,
|
feature.description,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(feature_sets) = &self.cluster_feature_sets {
|
||||||
|
write!(f, "{}", feature_sets)?;
|
||||||
|
}
|
||||||
|
|
||||||
if self.inactive && !self.feature_activation_allowed {
|
if self.inactive && !self.feature_activation_allowed {
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
@ -151,6 +159,191 @@ impl fmt::Display for CliFeatures {
|
|||||||
impl QuietDisplay for CliFeatures {}
|
impl QuietDisplay for CliFeatures {}
|
||||||
impl VerboseDisplay for CliFeatures {}
|
impl VerboseDisplay for CliFeatures {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CliClusterFeatureSets {
|
||||||
|
pub tool_feature_set: u32,
|
||||||
|
pub feature_sets: Vec<CliFeatureSet>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub stake_allowed: bool,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub rpc_allowed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CliClusterFeatureSets {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let mut tool_feature_set_matches_cluster = false;
|
||||||
|
|
||||||
|
let software_versions_title = "Software Version";
|
||||||
|
let feature_set_title = "Feature Set";
|
||||||
|
let stake_percent_title = "Stake";
|
||||||
|
let rpc_percent_title = "RPC";
|
||||||
|
let mut max_software_versions_len = software_versions_title.len();
|
||||||
|
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();
|
||||||
|
|
||||||
|
let feature_sets: Vec<_> = self
|
||||||
|
.feature_sets
|
||||||
|
.iter()
|
||||||
|
.map(|feature_set_info| {
|
||||||
|
let me = if self.tool_feature_set == feature_set_info.feature_set {
|
||||||
|
tool_feature_set_matches_cluster = true;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let software_versions: Vec<_> = feature_set_info
|
||||||
|
.software_versions
|
||||||
|
.iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect();
|
||||||
|
let software_versions = software_versions.join(", ");
|
||||||
|
let feature_set = if feature_set_info.feature_set == 0 {
|
||||||
|
"unknown".to_string()
|
||||||
|
} else {
|
||||||
|
feature_set_info.feature_set.to_string()
|
||||||
|
};
|
||||||
|
let stake_percent = format!("{:.2}%", feature_set_info.stake_percent);
|
||||||
|
let rpc_percent = format!("{:.2}%", feature_set_info.rpc_percent);
|
||||||
|
|
||||||
|
max_software_versions_len = max_software_versions_len.max(software_versions.len());
|
||||||
|
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());
|
||||||
|
|
||||||
|
(
|
||||||
|
software_versions,
|
||||||
|
feature_set,
|
||||||
|
stake_percent,
|
||||||
|
rpc_percent,
|
||||||
|
me,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !tool_feature_set_matches_cluster {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
style("To activate features the tool and cluster feature sets must match, select a tool version that matches the cluster")
|
||||||
|
.bold())?;
|
||||||
|
} else {
|
||||||
|
if !self.stake_allowed {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
style("To activate features the stake must be >= 95%")
|
||||||
|
.bold()
|
||||||
|
.red()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
if !self.rpc_allowed {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\n{}",
|
||||||
|
style("To activate features the RPC nodes must be >= 95%")
|
||||||
|
.bold()
|
||||||
|
.red()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"\n\n{}",
|
||||||
|
style(format!("Tool Feature Set: {}", self.tool_feature_set)).bold()
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
style(format!(
|
||||||
|
"{1:<0$} {3:<2$} {5:<4$} {7:<6$}",
|
||||||
|
max_software_versions_len,
|
||||||
|
software_versions_title,
|
||||||
|
max_feature_set_len,
|
||||||
|
feature_set_title,
|
||||||
|
max_stake_percent_len,
|
||||||
|
stake_percent_title,
|
||||||
|
max_rpc_percent_len,
|
||||||
|
rpc_percent_title,
|
||||||
|
))
|
||||||
|
.bold(),
|
||||||
|
)?;
|
||||||
|
for (software_versions, feature_set, stake_percent, rpc_percent, me) in feature_sets {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"{1:<0$} {3:>2$} {5:>4$} {7:>6$} {8}",
|
||||||
|
max_software_versions_len,
|
||||||
|
software_versions,
|
||||||
|
max_feature_set_len,
|
||||||
|
feature_set,
|
||||||
|
max_stake_percent_len,
|
||||||
|
stake_percent,
|
||||||
|
max_rpc_percent_len,
|
||||||
|
rpc_percent,
|
||||||
|
if me { "<-- me" } else { "" },
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
writeln!(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuietDisplay for CliClusterFeatureSets {}
|
||||||
|
impl VerboseDisplay for CliClusterFeatureSets {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CliFeatureSet {
|
||||||
|
software_versions: Vec<CliVersion>,
|
||||||
|
feature_set: u32,
|
||||||
|
stake_percent: f64,
|
||||||
|
rpc_percent: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
struct CliVersion(Option<semver::Version>);
|
||||||
|
|
||||||
|
impl fmt::Display for CliVersion {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let s = match &self.0 {
|
||||||
|
None => "unknown".to_string(),
|
||||||
|
Some(version) => version.to_string(),
|
||||||
|
};
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for CliVersion {
|
||||||
|
type Err = semver::Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let version_option = if s == "unknown" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(semver::Version::from_str(s)?)
|
||||||
|
};
|
||||||
|
Ok(CliVersion(version_option))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for CliVersion {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for CliVersion {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s: &str = Deserialize::deserialize(deserializer)?;
|
||||||
|
CliVersion::from_str(s).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait FeatureSubCommands {
|
pub trait FeatureSubCommands {
|
||||||
fn feature_subcommands(self) -> Self;
|
fn feature_subcommands(self) -> Self;
|
||||||
}
|
}
|
||||||
@ -367,7 +560,10 @@ fn feature_set_stats(rpc_client: &RpcClient) -> Result<FeatureSetStats, ClientEr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Feature activation is only allowed when 95% of the active stake is on the current feature set
|
// Feature activation is only allowed when 95% of the active stake is on the current feature set
|
||||||
fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<bool, ClientError> {
|
fn feature_activation_allowed(
|
||||||
|
rpc_client: &RpcClient,
|
||||||
|
quiet: bool,
|
||||||
|
) -> Result<(bool, Option<CliClusterFeatureSets>), ClientError> {
|
||||||
let my_feature_set = solana_version::Version::default().feature_set;
|
let my_feature_set = solana_version::Version::default().feature_set;
|
||||||
|
|
||||||
let feature_set_stats = feature_set_stats(rpc_client)?;
|
let feature_set_stats = feature_set_stats(rpc_client)?;
|
||||||
@ -383,54 +579,43 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
|
|||||||
)
|
)
|
||||||
.unwrap_or((false, false));
|
.unwrap_or((false, false));
|
||||||
|
|
||||||
if !quiet {
|
let cluster_feature_sets = if quiet {
|
||||||
if feature_set_stats.get(&my_feature_set).is_none() {
|
None
|
||||||
println!(
|
} else {
|
||||||
"{}",
|
let mut feature_sets = feature_set_stats
|
||||||
style("To activate features the tool and cluster feature sets must match, select a tool version that matches the cluster")
|
.into_iter()
|
||||||
.bold());
|
.map(
|
||||||
} else {
|
|(
|
||||||
if !stake_allowed {
|
feature_set,
|
||||||
print!(
|
FeatureSetStatsEntry {
|
||||||
"\n{}",
|
stake_percent,
|
||||||
style("To activate features the stake must be >= 95%")
|
rpc_nodes_percent: rpc_percent,
|
||||||
.bold()
|
software_versions,
|
||||||
.red()
|
},
|
||||||
);
|
)| {
|
||||||
}
|
CliFeatureSet {
|
||||||
if !rpc_allowed {
|
software_versions: software_versions.into_iter().map(CliVersion).collect(),
|
||||||
print!(
|
feature_set,
|
||||||
"\n{}",
|
stake_percent,
|
||||||
style("To activate features the RPC nodes must be >= 95%")
|
rpc_percent,
|
||||||
.bold()
|
}
|
||||||
.red()
|
},
|
||||||
);
|
)
|
||||||
}
|
.collect::<Vec<_>>();
|
||||||
}
|
feature_sets.sort_by(|l, r| {
|
||||||
println!(
|
match l.software_versions[0]
|
||||||
"\n\n{}",
|
.cmp(&r.software_versions[0])
|
||||||
style(format!("Tool Feature Set: {}", my_feature_set)).bold()
|
|
||||||
);
|
|
||||||
|
|
||||||
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()
|
.reverse()
|
||||||
{
|
{
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
match l
|
match l
|
||||||
.1
|
|
||||||
.stake_percent
|
.stake_percent
|
||||||
.partial_cmp(&r.1.stake_percent)
|
.partial_cmp(&r.stake_percent)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.reverse()
|
.reverse()
|
||||||
{
|
{
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
l.1.rpc_nodes_percent
|
l.rpc_percent.partial_cmp(&r.rpc_percent).unwrap().reverse()
|
||||||
.partial_cmp(&r.1.rpc_nodes_percent)
|
|
||||||
.unwrap()
|
|
||||||
.reverse()
|
|
||||||
}
|
}
|
||||||
o => o,
|
o => o,
|
||||||
}
|
}
|
||||||
@ -438,96 +623,15 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
|
|||||||
o => o,
|
o => o,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Some(CliClusterFeatureSets {
|
||||||
|
tool_feature_set: my_feature_set,
|
||||||
|
feature_sets,
|
||||||
|
stake_allowed,
|
||||||
|
rpc_allowed,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
let software_versions_title = "Software Version";
|
Ok((stake_allowed && rpc_allowed, cluster_feature_sets))
|
||||||
let feature_set_title = "Feature Set";
|
|
||||||
let stake_percent_title = "Stake";
|
|
||||||
let rpc_percent_title = "RPC";
|
|
||||||
let mut stats_output = Vec::new();
|
|
||||||
let mut max_software_versions_len = software_versions_title.len();
|
|
||||||
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();
|
|
||||||
for (
|
|
||||||
feature_set,
|
|
||||||
FeatureSetStatsEntry {
|
|
||||||
stake_percent,
|
|
||||||
rpc_nodes_percent,
|
|
||||||
software_versions,
|
|
||||||
},
|
|
||||||
) in feature_set_stats.into_iter()
|
|
||||||
{
|
|
||||||
let me = feature_set == my_feature_set;
|
|
||||||
let feature_set = if feature_set == 0 {
|
|
||||||
"unknown".to_string()
|
|
||||||
} else {
|
|
||||||
feature_set.to_string()
|
|
||||||
};
|
|
||||||
let stake_percent = format!("{:.2}%", stake_percent);
|
|
||||||
let rpc_percent = format!("{:.2}%", rpc_nodes_percent);
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
stats_output.push((
|
|
||||||
software_versions,
|
|
||||||
feature_set,
|
|
||||||
stake_percent,
|
|
||||||
rpc_percent,
|
|
||||||
me,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
style(format!(
|
|
||||||
"{1:<0$} {3:<2$} {5:<4$} {7:<6$}",
|
|
||||||
max_software_versions_len,
|
|
||||||
software_versions_title,
|
|
||||||
max_feature_set_len,
|
|
||||||
feature_set_title,
|
|
||||||
max_stake_percent_len,
|
|
||||||
stake_percent_title,
|
|
||||||
max_rpc_percent_len,
|
|
||||||
rpc_percent_title,
|
|
||||||
))
|
|
||||||
.bold(),
|
|
||||||
);
|
|
||||||
for (software_versions, feature_set, stake_percent, rpc_percent, me) in stats_output {
|
|
||||||
println!(
|
|
||||||
"{1:<0$} {3:>2$} {5:>4$} {7:>6$} {8}",
|
|
||||||
max_software_versions_len,
|
|
||||||
software_versions,
|
|
||||||
max_feature_set_len,
|
|
||||||
feature_set,
|
|
||||||
max_stake_percent_len,
|
|
||||||
stake_percent,
|
|
||||||
max_rpc_percent_len,
|
|
||||||
rpc_percent,
|
|
||||||
if me { "<-- me" } else { "" },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(stake_allowed && rpc_allowed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn status_from_account(account: Account) -> Option<CliFeatureStatus> {
|
fn status_from_account(account: Account) -> Option<CliFeatureStatus> {
|
||||||
@ -589,10 +693,12 @@ fn process_status(
|
|||||||
|
|
||||||
features.sort_unstable();
|
features.sort_unstable();
|
||||||
|
|
||||||
let feature_activation_allowed = feature_activation_allowed(rpc_client, features.len() <= 1)?;
|
let (feature_activation_allowed, cluster_feature_sets) =
|
||||||
|
feature_activation_allowed(rpc_client, features.len() <= 1)?;
|
||||||
let feature_set = CliFeatures {
|
let feature_set = CliFeatures {
|
||||||
features,
|
features,
|
||||||
feature_activation_allowed,
|
feature_activation_allowed,
|
||||||
|
cluster_feature_sets,
|
||||||
inactive,
|
inactive,
|
||||||
};
|
};
|
||||||
Ok(config.output_format.formatted_string(&feature_set))
|
Ok(config.output_format.formatted_string(&feature_set))
|
||||||
@ -616,7 +722,7 @@ fn process_activate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !feature_activation_allowed(rpc_client, false)? {
|
if !feature_activation_allowed(rpc_client, false)?.0 {
|
||||||
match force {
|
match force {
|
||||||
ForceActivation::Almost =>
|
ForceActivation::Almost =>
|
||||||
return Err("Add force argument once more to override the sanity check to force feature activation ".into()),
|
return Err("Add force argument once more to override the sanity check to force feature activation ".into()),
|
||||||
|
Reference in New Issue
Block a user