parent
c77ed82caa
commit
5298e3872c
|
@ -1,5 +1,14 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.0.4"
|
||||
|
@ -3827,6 +3836,7 @@ dependencies = [
|
|||
name = "solana-cli"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"Inflector 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bs58 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -3862,7 +3872,6 @@ dependencies = [
|
|||
"solana-vote-signer 1.2.0",
|
||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"titlecase 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -5486,15 +5495,6 @@ dependencies = [
|
|||
"crunchy 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "titlecase"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "0.1.22"
|
||||
|
@ -6280,6 +6280,7 @@ dependencies = [
|
|||
]
|
||||
|
||||
[metadata]
|
||||
"checksum Inflector 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
|
||||
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
|
||||
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
|
||||
|
@ -6748,7 +6749,6 @@ dependencies = [
|
|||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
"checksum tiny-bip39 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1cd1fb03fe8e07d17cd851a624a9fff74642a997b67fbd1ccd77533241640d92"
|
||||
"checksum tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d8a021c69bb74a44ccedb824a046447e2c84a01df9e5c20779750acb38e11b2"
|
||||
"checksum titlecase 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f565e410cfc24c2f2a89960b023ca192689d7f77d3f8d4f4af50c2d8affe1117"
|
||||
"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
|
||||
"checksum tokio 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0e1bef565a52394086ecac0a6fa3b8ace4cb3a138ee1d96bd2b93283b56824e3"
|
||||
"checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46"
|
||||
|
|
|
@ -18,6 +18,7 @@ ctrlc = { version = "3.1.4", features = ["termination"] }
|
|||
console = "0.10.0"
|
||||
dirs = "2.0.2"
|
||||
log = "0.4.8"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.14.0"
|
||||
humantime = "2.0.0"
|
||||
num-traits = "0.2"
|
||||
|
@ -41,7 +42,6 @@ solana-stake-program = { path = "../programs/stake", version = "1.2.0" }
|
|||
solana-storage-program = { path = "../programs/storage", version = "1.2.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.2.0" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.0" }
|
||||
titlecase = "1.1.0"
|
||||
thiserror = "1.0.15"
|
||||
url = "2.1.1"
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{
|
||||
cli_output::{CliAccount, OutputFormat},
|
||||
cluster_query::*,
|
||||
display::{println_name_value, println_signers},
|
||||
nonce::{self, *},
|
||||
|
@ -21,6 +22,7 @@ use solana_clap_utils::{
|
|||
use solana_client::{
|
||||
client_error::{ClientErrorKind, Result as ClientResult},
|
||||
rpc_client::RpcClient,
|
||||
rpc_response::{RpcAccount, RpcKeyedAccount},
|
||||
};
|
||||
#[cfg(not(test))]
|
||||
use solana_faucet::faucet::request_airdrop_transaction;
|
||||
|
@ -472,6 +474,7 @@ pub struct CliConfig<'a> {
|
|||
pub keypair_path: String,
|
||||
pub rpc_client: Option<RpcClient>,
|
||||
pub verbose: bool,
|
||||
pub output_format: OutputFormat,
|
||||
}
|
||||
|
||||
impl CliConfig<'_> {
|
||||
|
@ -563,6 +566,7 @@ impl Default for CliConfig<'_> {
|
|||
keypair_path: Self::default_keypair_path(),
|
||||
rpc_client: None,
|
||||
verbose: false,
|
||||
output_format: OutputFormat::Display,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1179,31 +1183,33 @@ fn process_confirm(rpc_client: &RpcClient, signature: &Signature) -> ProcessResu
|
|||
|
||||
fn process_show_account(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
account_pubkey: &Pubkey,
|
||||
output_file: &Option<String>,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let account = rpc_client.get_account(account_pubkey)?;
|
||||
let data = account.data.clone();
|
||||
let cli_account = CliAccount {
|
||||
keyed_account: RpcKeyedAccount {
|
||||
pubkey: account_pubkey.to_string(),
|
||||
account: RpcAccount::encode(account),
|
||||
},
|
||||
use_lamports_unit,
|
||||
};
|
||||
|
||||
println!();
|
||||
println_name_value("Public Key:", &account_pubkey.to_string());
|
||||
println_name_value(
|
||||
"Balance:",
|
||||
&build_balance_message(account.lamports, use_lamports_unit, true),
|
||||
);
|
||||
println_name_value("Owner:", &account.owner.to_string());
|
||||
println_name_value("Executable:", &account.executable.to_string());
|
||||
println_name_value("Rent Epoch:", &account.rent_epoch.to_string());
|
||||
config.output_format.formatted_print(&cli_account);
|
||||
|
||||
if let Some(output_file) = output_file {
|
||||
let mut f = File::create(output_file)?;
|
||||
f.write_all(&account.data)?;
|
||||
println!();
|
||||
println!("Wrote account data to {}", output_file);
|
||||
} else if !account.data.is_empty() {
|
||||
use pretty_hex::*;
|
||||
println!("{:?}", account.data.hex_dump());
|
||||
if config.output_format == OutputFormat::Display {
|
||||
if let Some(output_file) = output_file {
|
||||
let mut f = File::create(output_file)?;
|
||||
f.write_all(&data)?;
|
||||
println!();
|
||||
println!("Wrote account data to {}", output_file);
|
||||
} else if !data.is_empty() {
|
||||
use pretty_hex::*;
|
||||
println!("{:?}", data.hex_dump());
|
||||
}
|
||||
}
|
||||
|
||||
Ok("".to_string())
|
||||
|
@ -1577,7 +1583,7 @@ fn process_witness(
|
|||
}
|
||||
|
||||
pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
if config.verbose {
|
||||
if config.verbose && config.output_format == OutputFormat::Display {
|
||||
println_name_value("RPC URL:", &config.json_rpc_url);
|
||||
println_name_value("Default Signer Path:", &config.keypair_path);
|
||||
if config.keypair_path.starts_with("usb://") {
|
||||
|
@ -1622,7 +1628,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||
CliCommand::GetBlockTime { slot } => process_get_block_time(&rpc_client, *slot),
|
||||
CliCommand::GetGenesisHash => process_get_genesis_hash(&rpc_client),
|
||||
CliCommand::GetEpochInfo { commitment_config } => {
|
||||
process_get_epoch_info(&rpc_client, *commitment_config)
|
||||
process_get_epoch_info(&rpc_client, config, *commitment_config)
|
||||
}
|
||||
CliCommand::GetEpoch { commitment_config } => {
|
||||
process_get_epoch(&rpc_client, *commitment_config)
|
||||
|
@ -1662,13 +1668,14 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||
vote_account_pubkeys,
|
||||
} => process_show_stakes(
|
||||
&rpc_client,
|
||||
config,
|
||||
*use_lamports_unit,
|
||||
vote_account_pubkeys.as_deref(),
|
||||
),
|
||||
CliCommand::ShowValidators {
|
||||
use_lamports_unit,
|
||||
commitment_config,
|
||||
} => process_show_validators(&rpc_client, *use_lamports_unit, *commitment_config),
|
||||
} => process_show_validators(&rpc_client, config, *use_lamports_unit, *commitment_config),
|
||||
|
||||
// Nonce Commands
|
||||
|
||||
|
@ -1711,7 +1718,12 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||
CliCommand::ShowNonceAccount {
|
||||
nonce_account_pubkey,
|
||||
use_lamports_unit,
|
||||
} => process_show_nonce_account(&rpc_client, &nonce_account_pubkey, *use_lamports_unit),
|
||||
} => process_show_nonce_account(
|
||||
&rpc_client,
|
||||
config,
|
||||
&nonce_account_pubkey,
|
||||
*use_lamports_unit,
|
||||
),
|
||||
// Withdraw lamports from a nonce account
|
||||
CliCommand::WithdrawFromNonceAccount {
|
||||
nonce_account,
|
||||
|
@ -1940,7 +1952,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||
|
||||
// Return all or single validator info
|
||||
CliCommand::GetValidatorInfo(info_pubkey) => {
|
||||
process_get_validator_info(&rpc_client, *info_pubkey)
|
||||
process_get_validator_info(&rpc_client, config, *info_pubkey)
|
||||
}
|
||||
// Publish validator info
|
||||
CliCommand::SetValidatorInfo {
|
||||
|
@ -2532,7 +2544,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
|||
)
|
||||
.arg(
|
||||
Arg::with_name("output_file")
|
||||
.long("output")
|
||||
.long("output-file")
|
||||
.short("o")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
|
|
|
@ -0,0 +1,841 @@
|
|||
use crate::{cli::build_balance_message, display::writeln_name_value};
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use console::{style, Emoji};
|
||||
use inflector::cases::titlecase::to_title_case;
|
||||
use serde::Serialize;
|
||||
use serde_json::{Map, Value};
|
||||
use solana_client::rpc_response::{RpcEpochInfo, RpcKeyedAccount, RpcVoteAccountInfo};
|
||||
use solana_sdk::{
|
||||
clock::{self, Epoch, Slot, UnixTimestamp},
|
||||
stake_history::StakeHistoryEntry,
|
||||
};
|
||||
use solana_stake_program::stake_state::{Authorized, Lockup};
|
||||
use solana_vote_program::{
|
||||
authorized_voters::AuthorizedVoters,
|
||||
vote_state::{BlockTimestamp, Lockout},
|
||||
};
|
||||
use std::{collections::BTreeMap, fmt, time::Duration};
|
||||
|
||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum OutputFormat {
|
||||
Display,
|
||||
Json,
|
||||
JsonCompact,
|
||||
}
|
||||
|
||||
impl OutputFormat {
|
||||
pub fn formatted_print<T>(&self, item: &T)
|
||||
where
|
||||
T: Serialize + fmt::Display,
|
||||
{
|
||||
match self {
|
||||
OutputFormat::Display => {
|
||||
println!("{}", item);
|
||||
}
|
||||
OutputFormat::Json => {
|
||||
println!("{}", serde_json::to_string_pretty(item).unwrap());
|
||||
}
|
||||
OutputFormat::JsonCompact => {
|
||||
println!("{}", serde_json::to_value(item).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliAccount {
|
||||
#[serde(flatten)]
|
||||
pub keyed_account: RpcKeyedAccount,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Public Key:", &self.keyed_account.pubkey)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Balance:",
|
||||
&build_balance_message(
|
||||
self.keyed_account.account.lamports,
|
||||
self.use_lamports_unit,
|
||||
true,
|
||||
),
|
||||
)?;
|
||||
writeln_name_value(f, "Owner:", &self.keyed_account.account.owner)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Executable:",
|
||||
&self.keyed_account.account.executable.to_string(),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Rent Epoch:",
|
||||
&self.keyed_account.account.rent_epoch.to_string(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct CliBlockProduction {
|
||||
pub epoch: Epoch,
|
||||
pub start_slot: Slot,
|
||||
pub end_slot: Slot,
|
||||
pub total_slots: usize,
|
||||
pub total_blocks_produced: usize,
|
||||
pub total_slots_skipped: usize,
|
||||
pub leaders: Vec<CliBlockProductionEntry>,
|
||||
pub individual_slot_status: Vec<CliSlotStatus>,
|
||||
#[serde(skip_serializing)]
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliBlockProduction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>23}",
|
||||
"Identity Pubkey",
|
||||
"Leader Slots",
|
||||
"Blocks Produced",
|
||||
"Skipped Slots",
|
||||
"Skipped Slot Percentage",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for leader in &self.leaders {
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
||||
leader.identity_pubkey,
|
||||
leader.leader_slots,
|
||||
leader.blocks_produced,
|
||||
leader.skipped_slots,
|
||||
leader.skipped_slots as f64 / leader.leader_slots as f64 * 100.
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
||||
format!("Epoch {} total:", self.epoch),
|
||||
self.total_slots,
|
||||
self.total_blocks_produced,
|
||||
self.total_slots_skipped,
|
||||
self.total_slots_skipped as f64 / self.total_slots as f64 * 100.
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" (using data from {} slots: {} to {})",
|
||||
self.total_slots, self.start_slot, self.end_slot
|
||||
)?;
|
||||
if self.verbose {
|
||||
writeln!(f)?;
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(" {:<15} {:<44}", "Slot", "Identity Pubkey")).bold(),
|
||||
)?;
|
||||
for status in &self.individual_slot_status {
|
||||
if status.skipped {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<15} {:<44} SKIPPED",
|
||||
status.slot, status.leader
|
||||
))
|
||||
.red()
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(" {:<15} {:<44}", status.slot, status.leader))
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliBlockProductionEntry {
|
||||
pub identity_pubkey: String,
|
||||
pub leader_slots: u64,
|
||||
pub blocks_produced: u64,
|
||||
pub skipped_slots: u64,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliSlotStatus {
|
||||
pub slot: Slot,
|
||||
pub leader: String,
|
||||
pub skipped: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliEpochInfo {
|
||||
#[serde(flatten)]
|
||||
pub epoch_info: RpcEpochInfo,
|
||||
}
|
||||
|
||||
impl From<RpcEpochInfo> for CliEpochInfo {
|
||||
fn from(epoch_info: RpcEpochInfo) -> Self {
|
||||
Self { epoch_info }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliEpochInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?;
|
||||
writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?;
|
||||
let start_slot = self.epoch_info.absolute_slot - self.epoch_info.slot_index;
|
||||
let end_slot = start_slot + self.epoch_info.slots_in_epoch;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Slot Range:",
|
||||
&format!("[{}..{})", start_slot, end_slot),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Percent:",
|
||||
&format!(
|
||||
"{:>3.3}%",
|
||||
self.epoch_info.slot_index as f64 / self.epoch_info.slots_in_epoch as f64 * 100_f64
|
||||
),
|
||||
)?;
|
||||
let remaining_slots_in_epoch = self.epoch_info.slots_in_epoch - self.epoch_info.slot_index;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Slots:",
|
||||
&format!(
|
||||
"{}/{} ({} remaining)",
|
||||
self.epoch_info.slot_index,
|
||||
self.epoch_info.slots_in_epoch,
|
||||
remaining_slots_in_epoch
|
||||
),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Time:",
|
||||
&format!(
|
||||
"{}/{} ({} remaining)",
|
||||
slot_to_human_time(self.epoch_info.slot_index),
|
||||
slot_to_human_time(self.epoch_info.slots_in_epoch),
|
||||
slot_to_human_time(remaining_slots_in_epoch)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_to_human_time(slot: Slot) -> String {
|
||||
humantime::format_duration(Duration::from_secs(
|
||||
slot * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND,
|
||||
))
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliValidators {
|
||||
pub total_active_stake: u64,
|
||||
pub total_current_stake: u64,
|
||||
pub total_deliquent_stake: u64,
|
||||
pub current_validators: Vec<CliValidator>,
|
||||
pub delinquent_validators: Vec<CliValidator>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliValidators {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn write_vote_account(
|
||||
f: &mut fmt::Formatter,
|
||||
validator: &CliValidator,
|
||||
total_active_stake: u64,
|
||||
use_lamports_unit: bool,
|
||||
delinquent: bool,
|
||||
) -> fmt::Result {
|
||||
fn non_zero_or_dash(v: u64) -> String {
|
||||
if v == 0 {
|
||||
"-".into()
|
||||
} else {
|
||||
format!("{}", v)
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{} {:<44} {:<44} {:>9}% {:>8} {:>10} {:>7} {}",
|
||||
if delinquent {
|
||||
WARNING.to_string()
|
||||
} else {
|
||||
" ".to_string()
|
||||
},
|
||||
validator.identity_pubkey,
|
||||
validator.vote_account_pubkey,
|
||||
validator.commission,
|
||||
non_zero_or_dash(validator.last_vote),
|
||||
non_zero_or_dash(validator.root_slot),
|
||||
validator.credits,
|
||||
if validator.activated_stake > 0 {
|
||||
format!(
|
||||
"{} ({:.2}%)",
|
||||
build_balance_message(validator.activated_stake, use_lamports_unit, true),
|
||||
100. * validator.activated_stake as f64 / total_active_stake as f64
|
||||
)
|
||||
} else {
|
||||
"-".into()
|
||||
},
|
||||
)
|
||||
}
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Active Stake:",
|
||||
&build_balance_message(self.total_active_stake, self.use_lamports_unit, true),
|
||||
)?;
|
||||
if self.total_deliquent_stake > 0 {
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Current Stake:",
|
||||
&format!(
|
||||
"{} ({:0.2}%)",
|
||||
&build_balance_message(self.total_current_stake, self.use_lamports_unit, true),
|
||||
100. * self.total_current_stake as f64 / self.total_active_stake as f64
|
||||
),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Delinquent Stake:",
|
||||
&format!(
|
||||
"{} ({:0.2}%)",
|
||||
&build_balance_message(
|
||||
self.total_deliquent_stake,
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
),
|
||||
100. * self.total_deliquent_stake as f64 / self.total_active_stake as f64
|
||||
),
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:<44} {} {} {} {:>7} {}",
|
||||
"Identity Pubkey",
|
||||
"Vote Account Pubkey",
|
||||
"Commission",
|
||||
"Last Vote",
|
||||
"Root Block",
|
||||
"Credits",
|
||||
"Active Stake",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for validator in &self.current_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
for validator in &self.delinquent_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliValidator {
|
||||
pub identity_pubkey: String,
|
||||
pub vote_account_pubkey: String,
|
||||
pub commission: u8,
|
||||
pub last_vote: u64,
|
||||
pub root_slot: u64,
|
||||
pub credits: u64,
|
||||
pub activated_stake: u64,
|
||||
}
|
||||
|
||||
impl CliValidator {
|
||||
pub fn new(vote_account: &RpcVoteAccountInfo, current_epoch: Epoch) -> Self {
|
||||
Self {
|
||||
identity_pubkey: vote_account.node_pubkey.to_string(),
|
||||
vote_account_pubkey: vote_account.vote_pubkey.to_string(),
|
||||
commission: vote_account.commission,
|
||||
last_vote: vote_account.last_vote,
|
||||
root_slot: vote_account.root_slot,
|
||||
credits: vote_account
|
||||
.epoch_credits
|
||||
.iter()
|
||||
.find_map(|(epoch, credits, _)| {
|
||||
if *epoch == current_epoch {
|
||||
Some(*credits)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0),
|
||||
activated_stake: vote_account.activated_stake,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliNonceAccount {
|
||||
pub balance: u64,
|
||||
pub minimum_balance_for_rent_exemption: u64,
|
||||
pub nonce: Option<String>,
|
||||
pub lamports_per_signature: Option<u64>,
|
||||
pub authority: Option<String>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliNonceAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"Balance: {}",
|
||||
build_balance_message(self.balance, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Minimum Balance Required: {}",
|
||||
build_balance_message(
|
||||
self.minimum_balance_for_rent_exemption,
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
)
|
||||
)?;
|
||||
let nonce = self.nonce.as_deref().unwrap_or("uninitialized");
|
||||
writeln!(f, "Nonce: {}", nonce)?;
|
||||
if let Some(fees) = self.lamports_per_signature {
|
||||
writeln!(f, "Fee: {} lamports per signature", fees)?;
|
||||
} else {
|
||||
writeln!(f, "Fees: uninitialized")?;
|
||||
}
|
||||
let authority = self.authority.as_deref().unwrap_or("uninitialized");
|
||||
writeln!(f, "Authority: {}", authority)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliStakeVec(Vec<CliKeyedStakeState>);
|
||||
|
||||
impl CliStakeVec {
|
||||
pub fn new(list: Vec<CliKeyedStakeState>) -> Self {
|
||||
Self(list)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeVec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for state in &self.0 {
|
||||
writeln!(f)?;
|
||||
write!(f, "{}", state)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliKeyedStakeState {
|
||||
pub stake_pubkey: String,
|
||||
#[serde(flatten)]
|
||||
pub stake_state: CliStakeState,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliKeyedStakeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?;
|
||||
write!(f, "{}", self.stake_state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeState {
|
||||
pub stake_type: CliStakeType,
|
||||
pub total_stake: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delegated_stake: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delegated_vote_account_address: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub activation_epoch: Option<Epoch>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deactivation_epoch: Option<Epoch>,
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub authorized: Option<CliAuthorized>,
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub lockup: Option<CliLockup>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn show_authorized(f: &mut fmt::Formatter, authorized: &CliAuthorized) -> fmt::Result {
|
||||
writeln!(f, "Stake Authority: {}", authorized.staker)?;
|
||||
writeln!(f, "Withdraw Authority: {}", authorized.withdrawer)?;
|
||||
Ok(())
|
||||
}
|
||||
fn show_lockup(f: &mut fmt::Formatter, lockup: &CliLockup) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"Lockup Timestamp: {} (UnixTimestamp: {})",
|
||||
DateTime::<Utc>::from_utc(
|
||||
NaiveDateTime::from_timestamp(lockup.unix_timestamp, 0),
|
||||
Utc
|
||||
)
|
||||
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
lockup.unix_timestamp
|
||||
)?;
|
||||
writeln!(f, "Lockup Epoch: {}", lockup.epoch)?;
|
||||
writeln!(f, "Lockup Custodian: {}", lockup.custodian)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
match self.stake_type {
|
||||
CliStakeType::RewardsPool => writeln!(f, "Stake account is a rewards pool")?,
|
||||
CliStakeType::Uninitialized => writeln!(f, "Stake account is uninitialized")?,
|
||||
CliStakeType::Initialized => {
|
||||
writeln!(
|
||||
f,
|
||||
"Total Stake: {}",
|
||||
build_balance_message(self.total_stake, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(f, "Stake account is undelegated")?;
|
||||
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
||||
show_lockup(f, self.lockup.as_ref().unwrap())?;
|
||||
}
|
||||
CliStakeType::Stake => {
|
||||
writeln!(
|
||||
f,
|
||||
"Total Stake: {}",
|
||||
build_balance_message(self.total_stake, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Delegated Stake: {}",
|
||||
build_balance_message(
|
||||
self.delegated_stake.unwrap(),
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
)
|
||||
)?;
|
||||
if let Some(delegated_vote_account_address) = &self.delegated_vote_account_address {
|
||||
writeln!(
|
||||
f,
|
||||
"Delegated Vote Account Address: {}",
|
||||
delegated_vote_account_address
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
f,
|
||||
"Stake activates starting from epoch: {}",
|
||||
self.activation_epoch.unwrap()
|
||||
)?;
|
||||
if let Some(deactivation_epoch) = self.deactivation_epoch {
|
||||
writeln!(
|
||||
f,
|
||||
"Stake deactivates starting from epoch: {}",
|
||||
deactivation_epoch
|
||||
)?;
|
||||
}
|
||||
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
||||
show_lockup(f, self.lockup.as_ref().unwrap())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum CliStakeType {
|
||||
Stake,
|
||||
RewardsPool,
|
||||
Uninitialized,
|
||||
Initialized,
|
||||
}
|
||||
|
||||
impl Default for CliStakeType {
|
||||
fn default() -> Self {
|
||||
Self::Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeHistory {
|
||||
pub entries: Vec<CliStakeHistoryEntry>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeHistory {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<5} {:>20} {:>20} {:>20}",
|
||||
"Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for entry in &self.entries {
|
||||
writeln!(
|
||||
f,
|
||||
" {:>5} {:>20} {:>20} {:>20} {}",
|
||||
entry.epoch,
|
||||
build_balance_message(entry.effective_stake, self.use_lamports_unit, false),
|
||||
build_balance_message(entry.activating_stake, self.use_lamports_unit, false),
|
||||
build_balance_message(entry.deactivating_stake, self.use_lamports_unit, false),
|
||||
if self.use_lamports_unit {
|
||||
"lamports"
|
||||
} else {
|
||||
"SOL"
|
||||
}
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&(Epoch, StakeHistoryEntry)> for CliStakeHistoryEntry {
|
||||
fn from((epoch, entry): &(Epoch, StakeHistoryEntry)) -> Self {
|
||||
Self {
|
||||
epoch: *epoch,
|
||||
effective_stake: entry.effective,
|
||||
activating_stake: entry.activating,
|
||||
deactivating_stake: entry.deactivating,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeHistoryEntry {
|
||||
pub epoch: Epoch,
|
||||
pub effective_stake: u64,
|
||||
pub activating_stake: u64,
|
||||
pub deactivating_stake: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAuthorized {
|
||||
pub staker: String,
|
||||
pub withdrawer: String,
|
||||
}
|
||||
|
||||
impl From<&Authorized> for CliAuthorized {
|
||||
fn from(authorized: &Authorized) -> Self {
|
||||
Self {
|
||||
staker: authorized.staker.to_string(),
|
||||
withdrawer: authorized.withdrawer.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliLockup {
|
||||
pub unix_timestamp: UnixTimestamp,
|
||||
pub epoch: Epoch,
|
||||
pub custodian: String,
|
||||
}
|
||||
|
||||
impl From<&Lockup> for CliLockup {
|
||||
fn from(lockup: &Lockup) -> Self {
|
||||
Self {
|
||||
unix_timestamp: lockup.unix_timestamp,
|
||||
epoch: lockup.epoch,
|
||||
custodian: lockup.custodian.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliValidatorInfoVec(Vec<CliValidatorInfo>);
|
||||
|
||||
impl CliValidatorInfoVec {
|
||||
pub fn new(list: Vec<CliValidatorInfo>) -> Self {
|
||||
Self(list)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliValidatorInfoVec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.0.is_empty() {
|
||||
writeln!(f, "No validator info accounts found")?;
|
||||
}
|
||||
for validator_info in &self.0 {
|
||||
writeln!(f)?;
|
||||
write!(f, "{}", validator_info)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliValidatorInfo {
|
||||
pub identity_pubkey: String,
|
||||
pub info_pubkey: String,
|
||||
pub info: Map<String, Value>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliValidatorInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Validator Identity Pubkey:", &self.identity_pubkey)?;
|
||||
writeln_name_value(f, " Info Pubkey:", &self.info_pubkey)?;
|
||||
for (key, value) in self.info.iter() {
|
||||
writeln_name_value(
|
||||
f,
|
||||
&format!(" {}:", to_title_case(key)),
|
||||
&value.as_str().unwrap_or("?"),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliVoteAccount {
|
||||
pub account_balance: u64,
|
||||
pub validator_identity: String,
|
||||
#[serde(flatten)]
|
||||
pub authorized_voters: CliAuthorizedVoters,
|
||||
pub authorized_withdrawer: String,
|
||||
pub credits: u64,
|
||||
pub commission: u8,
|
||||
pub root_slot: Option<Slot>,
|
||||
pub recent_timestamp: BlockTimestamp,
|
||||
pub votes: Vec<CliLockout>,
|
||||
pub epoch_voting_history: Vec<CliEpochVotingHistory>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliVoteAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"Account Balance: {}",
|
||||
build_balance_message(self.account_balance, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(f, "Validator Identity: {}", self.validator_identity)?;
|
||||
writeln!(f, "Authorized Voters: {}", self.authorized_voters)?;
|
||||
writeln!(f, "Authorized Withdrawer: {}", self.authorized_withdrawer)?;
|
||||
writeln!(f, "Credits: {}", self.credits)?;
|
||||
writeln!(f, "Commission: {}%", self.commission)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Root Slot: {}",
|
||||
match self.root_slot {
|
||||
Some(slot) => slot.to_string(),
|
||||
None => "~".to_string(),
|
||||
}
|
||||
)?;
|
||||
writeln!(f, "Recent Timestamp: {:?}", self.recent_timestamp)?;
|
||||
if !self.votes.is_empty() {
|
||||
writeln!(f, "Recent Votes:")?;
|
||||
for vote in &self.votes {
|
||||
writeln!(
|
||||
f,
|
||||
"- slot: {}\n confirmation count: {}",
|
||||
vote.slot, vote.confirmation_count
|
||||
)?;
|
||||
}
|
||||
writeln!(f, "Epoch Voting History:")?;
|
||||
for epoch_info in &self.epoch_voting_history {
|
||||
writeln!(
|
||||
f,
|
||||
"- epoch: {}\n slots in epoch: {}\n credits earned: {}",
|
||||
epoch_info.epoch, epoch_info.slots_in_epoch, epoch_info.credits_earned,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAuthorizedVoters {
|
||||
authorized_voters: BTreeMap<Epoch, String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliAuthorizedVoters {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.authorized_voters)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuthorizedVoters> for CliAuthorizedVoters {
|
||||
fn from(authorized_voters: &AuthorizedVoters) -> Self {
|
||||
let mut voter_map: BTreeMap<Epoch, String> = BTreeMap::new();
|
||||
for (epoch, voter) in authorized_voters.iter() {
|
||||
voter_map.insert(*epoch, voter.to_string());
|
||||
}
|
||||
Self {
|
||||
authorized_voters: voter_map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliEpochVotingHistory {
|
||||
pub epoch: Epoch,
|
||||
pub slots_in_epoch: u64,
|
||||
pub credits_earned: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliLockout {
|
||||
pub slot: Slot,
|
||||
pub confirmation_count: u32,
|
||||
}
|
||||
|
||||
impl From<&Lockout> for CliLockout {
|
||||
fn from(lockout: &Lockout) -> Self {
|
||||
Self {
|
||||
slot: lockout.slot,
|
||||
confirmation_count: lockout.confirmation_count,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
use crate::{
|
||||
cli::{
|
||||
build_balance_message, check_account_for_fee, CliCommand, CliCommandInfo, CliConfig,
|
||||
CliError, ProcessResult,
|
||||
cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||
cli_output::{
|
||||
CliBlockProduction, CliBlockProductionEntry, CliEpochInfo, CliKeyedStakeState,
|
||||
CliSlotStatus, CliStakeVec, CliValidator, CliValidators,
|
||||
},
|
||||
display::println_name_value,
|
||||
};
|
||||
|
@ -13,7 +14,6 @@ use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_f
|
|||
use solana_client::{
|
||||
pubsub_client::{PubsubClient, SlotInfoMessage},
|
||||
rpc_client::RpcClient,
|
||||
rpc_response::RpcVoteAccountInfo,
|
||||
};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
|
@ -42,7 +42,6 @@ use std::{
|
|||
|
||||
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
||||
static CROSS_MARK: Emoji = Emoji("❌ ", "");
|
||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
||||
|
||||
pub trait ClusterQuerySubCommands {
|
||||
fn cluster_query_subcommands(self) -> Self;
|
||||
|
@ -599,51 +598,15 @@ pub fn process_get_block_time(rpc_client: &RpcClient, slot: Slot) -> ProcessResu
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
fn slot_to_human_time(slot: Slot) -> String {
|
||||
humantime::format_duration(Duration::from_secs(
|
||||
slot * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND,
|
||||
))
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn process_get_epoch_info(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ProcessResult {
|
||||
let epoch_info = rpc_client.get_epoch_info_with_commitment(commitment_config.clone())?;
|
||||
println!();
|
||||
println_name_value("Slot:", &epoch_info.absolute_slot.to_string());
|
||||
println_name_value("Epoch:", &epoch_info.epoch.to_string());
|
||||
let start_slot = epoch_info.absolute_slot - epoch_info.slot_index;
|
||||
let end_slot = start_slot + epoch_info.slots_in_epoch;
|
||||
println_name_value(
|
||||
"Epoch Slot Range:",
|
||||
&format!("[{}..{})", start_slot, end_slot),
|
||||
);
|
||||
println_name_value(
|
||||
"Epoch Completed Percent:",
|
||||
&format!(
|
||||
"{:>3.3}%",
|
||||
epoch_info.slot_index as f64 / epoch_info.slots_in_epoch as f64 * 100_f64
|
||||
),
|
||||
);
|
||||
let remaining_slots_in_epoch = epoch_info.slots_in_epoch - epoch_info.slot_index;
|
||||
println_name_value(
|
||||
"Epoch Completed Slots:",
|
||||
&format!(
|
||||
"{}/{} ({} remaining)",
|
||||
epoch_info.slot_index, epoch_info.slots_in_epoch, remaining_slots_in_epoch
|
||||
),
|
||||
);
|
||||
println_name_value(
|
||||
"Epoch Completed Time:",
|
||||
&format!(
|
||||
"{}/{} ({} remaining)",
|
||||
slot_to_human_time(epoch_info.slot_index),
|
||||
slot_to_human_time(epoch_info.slots_in_epoch),
|
||||
slot_to_human_time(remaining_slots_in_epoch)
|
||||
),
|
||||
);
|
||||
let epoch_info: CliEpochInfo = rpc_client
|
||||
.get_epoch_info_with_commitment(commitment_config.clone())?
|
||||
.into();
|
||||
config.output_format.formatted_print(&epoch_info);
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
|
@ -736,9 +699,9 @@ pub fn process_show_block_production(
|
|||
let start_slot_index = (start_slot - first_slot_in_epoch) as usize;
|
||||
let end_slot_index = (end_slot - first_slot_in_epoch) as usize;
|
||||
let total_slots = end_slot_index - start_slot_index + 1;
|
||||
let total_blocks = confirmed_blocks.len();
|
||||
assert!(total_blocks <= total_slots);
|
||||
let total_slots_skipped = total_slots - total_blocks;
|
||||
let total_blocks_produced = confirmed_blocks.len();
|
||||
assert!(total_blocks_produced <= total_slots);
|
||||
let total_slots_skipped = total_slots - total_blocks_produced;
|
||||
let mut leader_slot_count = HashMap::new();
|
||||
let mut leader_skipped_slots = HashMap::new();
|
||||
|
||||
|
@ -762,7 +725,7 @@ pub fn process_show_block_production(
|
|||
|
||||
progress_bar.set_message(&format!(
|
||||
"Processing {} slots containing {} blocks and {} empty slots...",
|
||||
total_slots, total_blocks, total_slots_skipped
|
||||
total_slots, total_blocks_produced, total_slots_skipped
|
||||
));
|
||||
|
||||
let mut confirmed_blocks_index = 0;
|
||||
|
@ -781,71 +744,52 @@ pub fn process_show_block_production(
|
|||
continue;
|
||||
}
|
||||
if slot_of_next_confirmed_block == slot {
|
||||
individual_slot_status
|
||||
.push(style(format!(" {:<15} {:<44}", slot, leader)).to_string());
|
||||
individual_slot_status.push(CliSlotStatus {
|
||||
slot,
|
||||
leader: (*leader).to_string(),
|
||||
skipped: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
*skipped_slots += 1;
|
||||
individual_slot_status.push(
|
||||
style(format!(" {:<15} {:<44} SKIPPED", slot, leader))
|
||||
.red()
|
||||
.to_string(),
|
||||
);
|
||||
individual_slot_status.push(CliSlotStatus {
|
||||
slot,
|
||||
leader: (*leader).to_string(),
|
||||
skipped: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
progress_bar.finish_and_clear();
|
||||
println!(
|
||||
"\n{}",
|
||||
style(format!(
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>23}",
|
||||
"Identity Pubkey",
|
||||
"Leader Slots",
|
||||
"Blocks Produced",
|
||||
"Skipped Slots",
|
||||
"Skipped Slot Percentage",
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
|
||||
let mut table = vec![];
|
||||
for (leader, leader_slots) in leader_slot_count.iter() {
|
||||
let skipped_slots = leader_skipped_slots.get(leader).unwrap();
|
||||
let blocks_produced = leader_slots - skipped_slots;
|
||||
table.push(format!(
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
||||
leader,
|
||||
leader_slots,
|
||||
blocks_produced,
|
||||
skipped_slots,
|
||||
*skipped_slots as f64 / *leader_slots as f64 * 100.
|
||||
));
|
||||
}
|
||||
table.sort();
|
||||
|
||||
println!(
|
||||
"{}\n\n {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
||||
table.join("\n"),
|
||||
format!("Epoch {} total:", epoch),
|
||||
let mut leaders: Vec<CliBlockProductionEntry> = leader_slot_count
|
||||
.iter()
|
||||
.map(|(leader, leader_slots)| {
|
||||
let skipped_slots = leader_skipped_slots.get(leader).unwrap();
|
||||
let blocks_produced = leader_slots - skipped_slots;
|
||||
CliBlockProductionEntry {
|
||||
identity_pubkey: (**leader).to_string(),
|
||||
leader_slots: *leader_slots,
|
||||
blocks_produced,
|
||||
skipped_slots: *skipped_slots,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
leaders.sort_by(|a, b| a.identity_pubkey.partial_cmp(&b.identity_pubkey).unwrap());
|
||||
let block_production = CliBlockProduction {
|
||||
epoch,
|
||||
start_slot,
|
||||
end_slot,
|
||||
total_slots,
|
||||
total_blocks,
|
||||
total_blocks_produced,
|
||||
total_slots_skipped,
|
||||
total_slots_skipped as f64 / total_slots as f64 * 100.
|
||||
);
|
||||
println!(
|
||||
" (using data from {} slots: {} to {})",
|
||||
total_slots, start_slot, end_slot
|
||||
);
|
||||
|
||||
if config.verbose {
|
||||
println!(
|
||||
"\n\n{}\n{}",
|
||||
style(format!(" {:<15} {:<44}", "Slot", "Identity Pubkey")).bold(),
|
||||
individual_slot_status.join("\n")
|
||||
);
|
||||
}
|
||||
leaders,
|
||||
individual_slot_status,
|
||||
verbose: config.verbose,
|
||||
};
|
||||
config.output_format.formatted_print(&block_production);
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
|
@ -1127,10 +1071,11 @@ pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult {
|
|||
|
||||
pub fn process_show_stakes(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
use_lamports_unit: bool,
|
||||
vote_account_pubkeys: Option<&[Pubkey]>,
|
||||
) -> ProcessResult {
|
||||
use crate::stake::print_stake_state;
|
||||
use crate::stake::build_stake_state;
|
||||
use solana_stake_program::stake_state::StakeState;
|
||||
|
||||
let progress_bar = new_spinner_progress_bar();
|
||||
|
@ -1138,13 +1083,20 @@ pub fn process_show_stakes(
|
|||
let all_stake_accounts = rpc_client.get_program_accounts(&solana_stake_program::id())?;
|
||||
progress_bar.finish_and_clear();
|
||||
|
||||
let mut stake_accounts: Vec<CliKeyedStakeState> = vec![];
|
||||
for (stake_pubkey, stake_account) in all_stake_accounts {
|
||||
if let Ok(stake_state) = stake_account.state() {
|
||||
match stake_state {
|
||||
StakeState::Initialized(_) => {
|
||||
if vote_account_pubkeys.is_none() {
|
||||
println!("\nstake pubkey: {}", stake_pubkey);
|
||||
print_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
||||
stake_accounts.push(CliKeyedStakeState {
|
||||
stake_pubkey: stake_pubkey.to_string(),
|
||||
stake_state: build_stake_state(
|
||||
stake_account.lamports,
|
||||
&stake_state,
|
||||
use_lamports_unit,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
StakeState::Stake(_, stake) => {
|
||||
|
@ -1153,19 +1105,29 @@ pub fn process_show_stakes(
|
|||
.unwrap()
|
||||
.contains(&stake.delegation.voter_pubkey)
|
||||
{
|
||||
println!("\nstake pubkey: {}", stake_pubkey);
|
||||
print_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
||||
stake_accounts.push(CliKeyedStakeState {
|
||||
stake_pubkey: stake_pubkey.to_string(),
|
||||
stake_state: build_stake_state(
|
||||
stake_account.lamports,
|
||||
&stake_state,
|
||||
use_lamports_unit,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
config
|
||||
.output_format
|
||||
.formatted_print(&CliStakeVec::new(stake_accounts));
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
pub fn process_show_validators(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
use_lamports_unit: bool,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ProcessResult {
|
||||
|
@ -1175,126 +1137,36 @@ pub fn process_show_validators(
|
|||
.current
|
||||
.iter()
|
||||
.chain(vote_accounts.delinquent.iter())
|
||||
.fold(0, |acc, vote_account| acc + vote_account.activated_stake)
|
||||
as f64;
|
||||
.fold(0, |acc, vote_account| acc + vote_account.activated_stake);
|
||||
|
||||
let total_deliquent_stake = vote_accounts
|
||||
.delinquent
|
||||
.iter()
|
||||
.fold(0, |acc, vote_account| acc + vote_account.activated_stake)
|
||||
as f64;
|
||||
.fold(0, |acc, vote_account| acc + vote_account.activated_stake);
|
||||
let total_current_stake = total_active_stake - total_deliquent_stake;
|
||||
|
||||
println_name_value(
|
||||
"Active Stake:",
|
||||
&build_balance_message(total_active_stake as u64, use_lamports_unit, true),
|
||||
);
|
||||
if total_deliquent_stake > 0. {
|
||||
println_name_value(
|
||||
"Current Stake:",
|
||||
&format!(
|
||||
"{} ({:0.2}%)",
|
||||
&build_balance_message(total_current_stake as u64, use_lamports_unit, true),
|
||||
100. * total_current_stake / total_active_stake
|
||||
),
|
||||
);
|
||||
println_name_value(
|
||||
"Delinquent Stake:",
|
||||
&format!(
|
||||
"{} ({:0.2}%)",
|
||||
&build_balance_message(total_deliquent_stake as u64, use_lamports_unit, true),
|
||||
100. * total_deliquent_stake / total_active_stake
|
||||
),
|
||||
);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:<44} {} {} {} {:>7} {}",
|
||||
"Identity Pubkey",
|
||||
"Vote Account Pubkey",
|
||||
"Commission",
|
||||
"Last Vote",
|
||||
"Root Block",
|
||||
"Credits",
|
||||
"Active Stake",
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
|
||||
fn print_vote_account(
|
||||
vote_account: RpcVoteAccountInfo,
|
||||
current_epoch: Epoch,
|
||||
total_active_stake: f64,
|
||||
use_lamports_unit: bool,
|
||||
delinquent: bool,
|
||||
) {
|
||||
fn non_zero_or_dash(v: u64) -> String {
|
||||
if v == 0 {
|
||||
"-".into()
|
||||
} else {
|
||||
format!("{}", v)
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
"{} {:<44} {:<44} {:>9}% {:>8} {:>10} {:>7} {}",
|
||||
if delinquent {
|
||||
WARNING.to_string()
|
||||
} else {
|
||||
" ".to_string()
|
||||
},
|
||||
vote_account.node_pubkey,
|
||||
vote_account.vote_pubkey,
|
||||
vote_account.commission,
|
||||
non_zero_or_dash(vote_account.last_vote),
|
||||
non_zero_or_dash(vote_account.root_slot),
|
||||
vote_account
|
||||
.epoch_credits
|
||||
.iter()
|
||||
.find_map(|(epoch, credits, _)| if *epoch == current_epoch {
|
||||
Some(*credits)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.unwrap_or(0),
|
||||
if vote_account.activated_stake > 0 {
|
||||
format!(
|
||||
"{} ({:.2}%)",
|
||||
build_balance_message(vote_account.activated_stake, use_lamports_unit, true),
|
||||
100. * vote_account.activated_stake as f64 / total_active_stake
|
||||
)
|
||||
} else {
|
||||
"-".into()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let mut current = vote_accounts.current;
|
||||
current.sort_by(|a, b| b.activated_stake.cmp(&a.activated_stake));
|
||||
for vote_account in current.into_iter() {
|
||||
print_vote_account(
|
||||
vote_account,
|
||||
epoch_info.epoch,
|
||||
total_active_stake,
|
||||
use_lamports_unit,
|
||||
false,
|
||||
);
|
||||
}
|
||||
let current_validators: Vec<CliValidator> = current
|
||||
.iter()
|
||||
.map(|vote_account| CliValidator::new(vote_account, epoch_info.epoch))
|
||||
.collect();
|
||||
let mut delinquent = vote_accounts.delinquent;
|
||||
delinquent.sort_by(|a, b| b.activated_stake.cmp(&a.activated_stake));
|
||||
for vote_account in delinquent.into_iter() {
|
||||
print_vote_account(
|
||||
vote_account,
|
||||
epoch_info.epoch,
|
||||
total_active_stake,
|
||||
use_lamports_unit,
|
||||
true,
|
||||
);
|
||||
}
|
||||
let delinquent_validators: Vec<CliValidator> = delinquent
|
||||
.iter()
|
||||
.map(|vote_account| CliValidator::new(vote_account, epoch_info.epoch))
|
||||
.collect();
|
||||
|
||||
let cli_validators = CliValidators {
|
||||
total_active_stake,
|
||||
total_current_stake,
|
||||
total_deliquent_stake,
|
||||
current_validators,
|
||||
delinquent_validators,
|
||||
use_lamports_unit,
|
||||
};
|
||||
config.output_format.formatted_print(&cli_validators);
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::cli::SettingType;
|
||||
use console::style;
|
||||
use solana_sdk::hash::Hash;
|
||||
use std::fmt;
|
||||
|
||||
// Pretty print a "name value"
|
||||
pub fn println_name_value(name: &str, value: &str) {
|
||||
|
@ -12,6 +13,15 @@ pub fn println_name_value(name: &str, value: &str) {
|
|||
println!("{} {}", style(name).bold(), styled_value);
|
||||
}
|
||||
|
||||
pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fmt::Result {
|
||||
let styled_value = if value == "" {
|
||||
style("(not set)").italic()
|
||||
} else {
|
||||
style(value)
|
||||
};
|
||||
writeln!(f, "{} {}", style(name).bold(), styled_value)
|
||||
}
|
||||
|
||||
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
|
||||
let description = match setting_type {
|
||||
SettingType::Explicit => "",
|
||||
|
|
|
@ -18,7 +18,11 @@ macro_rules! pubkey {
|
|||
};
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod cli;
|
||||
pub mod cli_output;
|
||||
pub mod cluster_query;
|
||||
pub mod display;
|
||||
pub mod nonce;
|
||||
|
|
|
@ -9,6 +9,7 @@ use solana_clap_utils::{
|
|||
};
|
||||
use solana_cli::{
|
||||
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
|
||||
cli_output::OutputFormat,
|
||||
display::{println_name_value, println_name_value_or},
|
||||
};
|
||||
use solana_cli_config::{Config, CONFIG_FILE};
|
||||
|
@ -129,6 +130,15 @@ pub fn parse_args<'a>(
|
|||
let CliCommandInfo { command, signers } =
|
||||
parse_command(&matches, &default_signer_path, wallet_manager.as_ref())?;
|
||||
|
||||
let output_format = matches
|
||||
.value_of("output_format")
|
||||
.map(|value| match value {
|
||||
"json" => OutputFormat::Json,
|
||||
"json-compact" => OutputFormat::JsonCompact,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.unwrap_or(OutputFormat::Display);
|
||||
|
||||
Ok((
|
||||
CliConfig {
|
||||
command,
|
||||
|
@ -138,6 +148,7 @@ pub fn parse_args<'a>(
|
|||
keypair_path: default_signer_path,
|
||||
rpc_client: None,
|
||||
verbose: matches.is_present("verbose"),
|
||||
output_format,
|
||||
},
|
||||
signers,
|
||||
))
|
||||
|
@ -199,6 +210,14 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||
.global(true)
|
||||
.help("Show additional information"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_format")
|
||||
.long("output")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&["json", "json-compact"])
|
||||
.help("Return information in specified output format. Supports: json, json-compact"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use crate::cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
||||
SignerIndex,
|
||||
use crate::{
|
||||
cli::{
|
||||
check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||
ProcessResult, SignerIndex,
|
||||
},
|
||||
cli_output::CliNonceAccount,
|
||||
};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{
|
||||
|
@ -568,38 +571,26 @@ pub fn process_new_nonce(
|
|||
|
||||
pub fn process_show_nonce_account(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
nonce_account_pubkey: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let nonce_account = get_account(rpc_client, nonce_account_pubkey)?;
|
||||
let print_account = |data: Option<&nonce::state::Data>| {
|
||||
println!(
|
||||
"Balance: {}",
|
||||
build_balance_message(nonce_account.lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!(
|
||||
"Minimum Balance Required: {}",
|
||||
build_balance_message(
|
||||
rpc_client.get_minimum_balance_for_rent_exemption(State::size())?,
|
||||
use_lamports_unit,
|
||||
true
|
||||
)
|
||||
);
|
||||
match data {
|
||||
Some(ref data) => {
|
||||
println!("Nonce: {}", data.blockhash);
|
||||
println!(
|
||||
"Fee: {} lamports per signature",
|
||||
data.fee_calculator.lamports_per_signature
|
||||
);
|
||||
println!("Authority: {}", data.authority);
|
||||
}
|
||||
None => {
|
||||
println!("Nonce: uninitialized");
|
||||
println!("Fees: uninitialized");
|
||||
println!("Authority: uninitialized");
|
||||
}
|
||||
let mut nonce_account = CliNonceAccount {
|
||||
balance: nonce_account.lamports,
|
||||
minimum_balance_for_rent_exemption: rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(State::size())?,
|
||||
use_lamports_unit,
|
||||
..CliNonceAccount::default()
|
||||
};
|
||||
if let Some(ref data) = data {
|
||||
nonce_account.nonce = Some(data.blockhash.to_string());
|
||||
nonce_account.lamports_per_signature = Some(data.fee_calculator.lamports_per_signature);
|
||||
nonce_account.authority = Some(data.authority.to_string());
|
||||
}
|
||||
|
||||
config.output_format.formatted_print(&nonce_account);
|
||||
Ok("".to_string())
|
||||
};
|
||||
match state_from_account(&nonce_account)? {
|
||||
|
|
142
cli/src/stake.rs
142
cli/src/stake.rs
|
@ -1,15 +1,14 @@
|
|||
use crate::{
|
||||
cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, fee_payer_arg,
|
||||
generate_unique_signers, log_instruction_custom_error, nonce_authority_arg, return_signers,
|
||||
CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, FEE_PAYER_ARG,
|
||||
check_account_for_fee, check_unique_pubkeys, fee_payer_arg, generate_unique_signers,
|
||||
log_instruction_custom_error, nonce_authority_arg, return_signers, CliCommand,
|
||||
CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, FEE_PAYER_ARG,
|
||||
},
|
||||
cli_output::{CliStakeHistory, CliStakeHistoryEntry, CliStakeState, CliStakeType},
|
||||
nonce::{check_nonce_account, nonce_arg, NONCE_ARG, NONCE_AUTHORITY_ARG},
|
||||
offline::{blockhash_query::BlockhashQuery, *},
|
||||
};
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};
|
||||
use console::style;
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*, offline::*, ArgConstant};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
|
@ -1231,78 +1230,61 @@ pub fn process_stake_set_lockup(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamports_unit: bool) {
|
||||
fn show_authorized(authorized: &Authorized) {
|
||||
println!("Stake Authority: {}", authorized.staker);
|
||||
println!("Withdraw Authority: {}", authorized.withdrawer);
|
||||
}
|
||||
fn show_lockup(lockup: &Lockup) {
|
||||
println!(
|
||||
"Lockup Timestamp: {} (UnixTimestamp: {})",
|
||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(lockup.unix_timestamp, 0), Utc)
|
||||
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
lockup.unix_timestamp
|
||||
);
|
||||
println!("Lockup Epoch: {}", lockup.epoch);
|
||||
println!("Lockup Custodian: {}", lockup.custodian);
|
||||
}
|
||||
pub fn build_stake_state(
|
||||
stake_lamports: u64,
|
||||
stake_state: &StakeState,
|
||||
use_lamports_unit: bool,
|
||||
) -> CliStakeState {
|
||||
match stake_state {
|
||||
StakeState::Stake(
|
||||
Meta {
|
||||
authorized, lockup, ..
|
||||
},
|
||||
stake,
|
||||
) => {
|
||||
println!(
|
||||
"Total Stake: {}",
|
||||
build_balance_message(stake_lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!(
|
||||
"Delegated Stake: {}",
|
||||
build_balance_message(stake.delegation.stake, use_lamports_unit, true)
|
||||
);
|
||||
if stake.delegation.voter_pubkey != Pubkey::default() {
|
||||
println!(
|
||||
"Delegated Vote Account Address: {}",
|
||||
stake.delegation.voter_pubkey
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"Stake activates starting from epoch: {}",
|
||||
if stake.delegation.activation_epoch < std::u64::MAX {
|
||||
stake.delegation.activation_epoch
|
||||
} else {
|
||||
0
|
||||
}
|
||||
);
|
||||
if stake.delegation.deactivation_epoch < std::u64::MAX {
|
||||
println!(
|
||||
"Stake deactivates starting from epoch: {}",
|
||||
stake.delegation.deactivation_epoch
|
||||
);
|
||||
}
|
||||
show_authorized(&authorized);
|
||||
show_lockup(&lockup);
|
||||
}
|
||||
StakeState::RewardsPool => println!("Stake account is a rewards pool"),
|
||||
StakeState::Uninitialized => println!("Stake account is uninitialized"),
|
||||
) => CliStakeState {
|
||||
stake_type: CliStakeType::Stake,
|
||||
total_stake: stake_lamports,
|
||||
delegated_stake: Some(stake.delegation.stake),
|
||||
delegated_vote_account_address: if stake.delegation.voter_pubkey != Pubkey::default() {
|
||||
Some(stake.delegation.voter_pubkey.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
activation_epoch: Some(if stake.delegation.activation_epoch < std::u64::MAX {
|
||||
stake.delegation.activation_epoch
|
||||
} else {
|
||||
0
|
||||
}),
|
||||
deactivation_epoch: if stake.delegation.deactivation_epoch < std::u64::MAX {
|
||||
Some(stake.delegation.deactivation_epoch)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
authorized: Some(authorized.into()),
|
||||
lockup: Some(lockup.into()),
|
||||
use_lamports_unit,
|
||||
},
|
||||
StakeState::RewardsPool => CliStakeState {
|
||||
stake_type: CliStakeType::RewardsPool,
|
||||
..CliStakeState::default()
|
||||
},
|
||||
StakeState::Uninitialized => CliStakeState::default(),
|
||||
StakeState::Initialized(Meta {
|
||||
authorized, lockup, ..
|
||||
}) => {
|
||||
println!(
|
||||
"Total Stake: {}",
|
||||
build_balance_message(stake_lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!("Stake account is undelegated");
|
||||
show_authorized(&authorized);
|
||||
show_lockup(&lockup);
|
||||
}
|
||||
}) => CliStakeState {
|
||||
stake_type: CliStakeType::Initialized,
|
||||
total_stake: stake_lamports,
|
||||
authorized: Some(authorized.into()),
|
||||
lockup: Some(lockup.into()),
|
||||
use_lamports_unit,
|
||||
..CliStakeState::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_show_stake_account(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
stake_account_pubkey: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
|
@ -1316,7 +1298,8 @@ pub fn process_show_stake_account(
|
|||
}
|
||||
match stake_account.state() {
|
||||
Ok(stake_state) => {
|
||||
print_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
||||
let state = build_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
||||
config.output_format.formatted_print(&state);
|
||||
Ok("".to_string())
|
||||
}
|
||||
Err(err) => Err(CliError::RpcRequestError(format!(
|
||||
|
@ -1329,7 +1312,7 @@ pub fn process_show_stake_account(
|
|||
|
||||
pub fn process_show_stake_history(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let stake_history_account = rpc_client.get_account(&stake_history::id())?;
|
||||
|
@ -1337,26 +1320,15 @@ pub fn process_show_stake_history(
|
|||
CliError::RpcRequestError("Failed to deserialize stake history".to_string())
|
||||
})?;
|
||||
|
||||
println!();
|
||||
println!(
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<5} {:>20} {:>20} {:>20}",
|
||||
"Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
|
||||
for (epoch, entry) in stake_history.deref() {
|
||||
println!(
|
||||
" {:>5} {:>20} {:>20} {:>20} {}",
|
||||
epoch,
|
||||
build_balance_message(entry.effective, use_lamports_unit, false),
|
||||
build_balance_message(entry.activating, use_lamports_unit, false),
|
||||
build_balance_message(entry.deactivating, use_lamports_unit, false),
|
||||
if use_lamports_unit { "lamports" } else { "SOL" }
|
||||
);
|
||||
let mut entries: Vec<CliStakeHistoryEntry> = vec![];
|
||||
for entry in stake_history.deref() {
|
||||
entries.push(entry.into());
|
||||
}
|
||||
let stake_history_output = CliStakeHistory {
|
||||
entries,
|
||||
use_lamports_unit,
|
||||
};
|
||||
config.output_format.formatted_print(&stake_history_output);
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||
display::println_name_value,
|
||||
cli_output::{CliValidatorInfo, CliValidatorInfoVec},
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
|
@ -25,7 +25,6 @@ use solana_sdk::{
|
|||
transaction::Transaction,
|
||||
};
|
||||
use std::{error, sync::Arc};
|
||||
use titlecase::titlecase;
|
||||
|
||||
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
|
||||
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
|
||||
|
@ -375,7 +374,11 @@ pub fn process_set_validator_info(
|
|||
Ok("".to_string())
|
||||
}
|
||||
|
||||
pub fn process_get_validator_info(rpc_client: &RpcClient, pubkey: Option<Pubkey>) -> ProcessResult {
|
||||
pub fn process_get_validator_info(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
pubkey: Option<Pubkey>,
|
||||
) -> ProcessResult {
|
||||
let validator_info: Vec<(Pubkey, Account)> = if let Some(validator_info_pubkey) = pubkey {
|
||||
vec![(
|
||||
validator_info_pubkey,
|
||||
|
@ -394,23 +397,22 @@ pub fn process_get_validator_info(rpc_client: &RpcClient, pubkey: Option<Pubkey>
|
|||
.collect()
|
||||
};
|
||||
|
||||
let mut validator_info_list: Vec<CliValidatorInfo> = vec![];
|
||||
if validator_info.is_empty() {
|
||||
println!("No validator info accounts found");
|
||||
}
|
||||
for (validator_info_pubkey, validator_info_account) in validator_info.iter() {
|
||||
let (validator_pubkey, validator_info) =
|
||||
parse_validator_info(&validator_info_pubkey, &validator_info_account)?;
|
||||
println!();
|
||||
println_name_value("Validator Identity Pubkey:", &validator_pubkey.to_string());
|
||||
println_name_value(" Info Pubkey:", &validator_info_pubkey.to_string());
|
||||
for (key, value) in validator_info.iter() {
|
||||
println_name_value(
|
||||
&format!(" {}:", titlecase(key)),
|
||||
&value.as_str().unwrap_or("?"),
|
||||
);
|
||||
}
|
||||
validator_info_list.push(CliValidatorInfo {
|
||||
identity_pubkey: validator_pubkey.to_string(),
|
||||
info_pubkey: validator_info_pubkey.to_string(),
|
||||
info: validator_info,
|
||||
});
|
||||
}
|
||||
|
||||
config
|
||||
.output_format
|
||||
.formatted_print(&CliValidatorInfoVec::new(validator_info_list));
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use crate::cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
||||
SignerIndex,
|
||||
use crate::{
|
||||
cli::{
|
||||
check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||
ProcessResult, SignerIndex,
|
||||
},
|
||||
cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount},
|
||||
};
|
||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
||||
|
@ -549,7 +552,7 @@ fn get_vote_account(
|
|||
|
||||
pub fn process_show_vote_account(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
commitment_config: CommitmentConfig,
|
||||
|
@ -559,45 +562,38 @@ pub fn process_show_vote_account(
|
|||
|
||||
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
||||
|
||||
println!(
|
||||
"Account Balance: {}",
|
||||
build_balance_message(vote_account.lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!("Validator Identity: {}", vote_state.node_pubkey);
|
||||
println!("Authorized Voter: {:?}", vote_state.authorized_voters());
|
||||
println!(
|
||||
"Authorized Withdrawer: {}",
|
||||
vote_state.authorized_withdrawer
|
||||
);
|
||||
println!("Credits: {}", vote_state.credits());
|
||||
println!("Commission: {}%", vote_state.commission);
|
||||
println!(
|
||||
"Root Slot: {}",
|
||||
match vote_state.root_slot {
|
||||
Some(slot) => slot.to_string(),
|
||||
None => "~".to_string(),
|
||||
}
|
||||
);
|
||||
println!("Recent Timestamp: {:?}", vote_state.last_timestamp);
|
||||
let mut votes: Vec<CliLockout> = vec![];
|
||||
let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
|
||||
if !vote_state.votes.is_empty() {
|
||||
println!("recent votes:");
|
||||
for vote in &vote_state.votes {
|
||||
println!(
|
||||
"- slot: {}\n confirmation count: {}",
|
||||
vote.slot, vote.confirmation_count
|
||||
);
|
||||
votes.push(vote.into());
|
||||
}
|
||||
|
||||
println!("Epoch Voting History:");
|
||||
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
|
||||
let credits_earned = credits - prev_credits;
|
||||
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
|
||||
println!(
|
||||
"- epoch: {}\n slots in epoch: {}\n credits earned: {}",
|
||||
epoch, slots_in_epoch, credits_earned,
|
||||
);
|
||||
epoch_voting_history.push(CliEpochVotingHistory {
|
||||
epoch: *epoch,
|
||||
slots_in_epoch,
|
||||
credits_earned,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let vote_account_data = CliVoteAccount {
|
||||
account_balance: vote_account.lamports,
|
||||
validator_identity: vote_state.node_pubkey.to_string(),
|
||||
authorized_voters: vote_state.authorized_voters().into(),
|
||||
authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
|
||||
credits: vote_state.credits(),
|
||||
commission: vote_state.commission,
|
||||
root_slot: vote_state.root_slot,
|
||||
recent_timestamp: vote_state.last_timestamp.clone(),
|
||||
votes,
|
||||
epoch_voting_history,
|
||||
use_lamports_unit,
|
||||
};
|
||||
|
||||
config.output_format.formatted_print(&vote_account_data);
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue