parent
c77ed82caa
commit
5298e3872c
|
@ -1,5 +1,14 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# 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]]
|
[[package]]
|
||||||
name = "adler32"
|
name = "adler32"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -3827,6 +3836,7 @@ dependencies = [
|
||||||
name = "solana-cli"
|
name = "solana-cli"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
dependencies = [
|
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)",
|
"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)",
|
"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)",
|
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -3862,7 +3872,6 @@ dependencies = [
|
||||||
"solana-vote-signer 1.2.0",
|
"solana-vote-signer 1.2.0",
|
||||||
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "0.1.22"
|
version = "0.1.22"
|
||||||
|
@ -6280,6 +6280,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[metadata]
|
[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 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.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"
|
"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 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-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 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.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 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"
|
"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"
|
console = "0.10.0"
|
||||||
dirs = "2.0.2"
|
dirs = "2.0.2"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
|
Inflector = "0.11.4"
|
||||||
indicatif = "0.14.0"
|
indicatif = "0.14.0"
|
||||||
humantime = "2.0.0"
|
humantime = "2.0.0"
|
||||||
num-traits = "0.2"
|
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-storage-program = { path = "../programs/storage", version = "1.2.0" }
|
||||||
solana-vote-program = { path = "../programs/vote", version = "1.2.0" }
|
solana-vote-program = { path = "../programs/vote", version = "1.2.0" }
|
||||||
solana-vote-signer = { path = "../vote-signer", version = "1.2.0" }
|
solana-vote-signer = { path = "../vote-signer", version = "1.2.0" }
|
||||||
titlecase = "1.1.0"
|
|
||||||
thiserror = "1.0.15"
|
thiserror = "1.0.15"
|
||||||
url = "2.1.1"
|
url = "2.1.1"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cli_output::{CliAccount, OutputFormat},
|
||||||
cluster_query::*,
|
cluster_query::*,
|
||||||
display::{println_name_value, println_signers},
|
display::{println_name_value, println_signers},
|
||||||
nonce::{self, *},
|
nonce::{self, *},
|
||||||
|
@ -21,6 +22,7 @@ use solana_clap_utils::{
|
||||||
use solana_client::{
|
use solana_client::{
|
||||||
client_error::{ClientErrorKind, Result as ClientResult},
|
client_error::{ClientErrorKind, Result as ClientResult},
|
||||||
rpc_client::RpcClient,
|
rpc_client::RpcClient,
|
||||||
|
rpc_response::{RpcAccount, RpcKeyedAccount},
|
||||||
};
|
};
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use solana_faucet::faucet::request_airdrop_transaction;
|
use solana_faucet::faucet::request_airdrop_transaction;
|
||||||
|
@ -472,6 +474,7 @@ pub struct CliConfig<'a> {
|
||||||
pub keypair_path: String,
|
pub keypair_path: String,
|
||||||
pub rpc_client: Option<RpcClient>,
|
pub rpc_client: Option<RpcClient>,
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
|
pub output_format: OutputFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliConfig<'_> {
|
impl CliConfig<'_> {
|
||||||
|
@ -563,6 +566,7 @@ impl Default for CliConfig<'_> {
|
||||||
keypair_path: Self::default_keypair_path(),
|
keypair_path: Self::default_keypair_path(),
|
||||||
rpc_client: None,
|
rpc_client: None,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
|
output_format: OutputFormat::Display,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1179,31 +1183,33 @@ fn process_confirm(rpc_client: &RpcClient, signature: &Signature) -> ProcessResu
|
||||||
|
|
||||||
fn process_show_account(
|
fn process_show_account(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
_config: &CliConfig,
|
config: &CliConfig,
|
||||||
account_pubkey: &Pubkey,
|
account_pubkey: &Pubkey,
|
||||||
output_file: &Option<String>,
|
output_file: &Option<String>,
|
||||||
use_lamports_unit: bool,
|
use_lamports_unit: bool,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let account = rpc_client.get_account(account_pubkey)?;
|
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!();
|
config.output_format.formatted_print(&cli_account);
|
||||||
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());
|
|
||||||
|
|
||||||
if let Some(output_file) = output_file {
|
if config.output_format == OutputFormat::Display {
|
||||||
let mut f = File::create(output_file)?;
|
if let Some(output_file) = output_file {
|
||||||
f.write_all(&account.data)?;
|
let mut f = File::create(output_file)?;
|
||||||
println!();
|
f.write_all(&data)?;
|
||||||
println!("Wrote account data to {}", output_file);
|
println!();
|
||||||
} else if !account.data.is_empty() {
|
println!("Wrote account data to {}", output_file);
|
||||||
use pretty_hex::*;
|
} else if !data.is_empty() {
|
||||||
println!("{:?}", account.data.hex_dump());
|
use pretty_hex::*;
|
||||||
|
println!("{:?}", data.hex_dump());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok("".to_string())
|
Ok("".to_string())
|
||||||
|
@ -1577,7 +1583,7 @@ fn process_witness(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_command(config: &CliConfig) -> ProcessResult {
|
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("RPC URL:", &config.json_rpc_url);
|
||||||
println_name_value("Default Signer Path:", &config.keypair_path);
|
println_name_value("Default Signer Path:", &config.keypair_path);
|
||||||
if config.keypair_path.starts_with("usb://") {
|
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::GetBlockTime { slot } => process_get_block_time(&rpc_client, *slot),
|
||||||
CliCommand::GetGenesisHash => process_get_genesis_hash(&rpc_client),
|
CliCommand::GetGenesisHash => process_get_genesis_hash(&rpc_client),
|
||||||
CliCommand::GetEpochInfo { commitment_config } => {
|
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 } => {
|
CliCommand::GetEpoch { commitment_config } => {
|
||||||
process_get_epoch(&rpc_client, *commitment_config)
|
process_get_epoch(&rpc_client, *commitment_config)
|
||||||
|
@ -1662,13 +1668,14 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||||
vote_account_pubkeys,
|
vote_account_pubkeys,
|
||||||
} => process_show_stakes(
|
} => process_show_stakes(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
|
config,
|
||||||
*use_lamports_unit,
|
*use_lamports_unit,
|
||||||
vote_account_pubkeys.as_deref(),
|
vote_account_pubkeys.as_deref(),
|
||||||
),
|
),
|
||||||
CliCommand::ShowValidators {
|
CliCommand::ShowValidators {
|
||||||
use_lamports_unit,
|
use_lamports_unit,
|
||||||
commitment_config,
|
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
|
// Nonce Commands
|
||||||
|
|
||||||
|
@ -1711,7 +1718,12 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||||
CliCommand::ShowNonceAccount {
|
CliCommand::ShowNonceAccount {
|
||||||
nonce_account_pubkey,
|
nonce_account_pubkey,
|
||||||
use_lamports_unit,
|
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
|
// Withdraw lamports from a nonce account
|
||||||
CliCommand::WithdrawFromNonceAccount {
|
CliCommand::WithdrawFromNonceAccount {
|
||||||
nonce_account,
|
nonce_account,
|
||||||
|
@ -1940,7 +1952,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||||
|
|
||||||
// Return all or single validator info
|
// Return all or single validator info
|
||||||
CliCommand::GetValidatorInfo(info_pubkey) => {
|
CliCommand::GetValidatorInfo(info_pubkey) => {
|
||||||
process_get_validator_info(&rpc_client, *info_pubkey)
|
process_get_validator_info(&rpc_client, config, *info_pubkey)
|
||||||
}
|
}
|
||||||
// Publish validator info
|
// Publish validator info
|
||||||
CliCommand::SetValidatorInfo {
|
CliCommand::SetValidatorInfo {
|
||||||
|
@ -2532,7 +2544,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("output_file")
|
Arg::with_name("output_file")
|
||||||
.long("output")
|
.long("output-file")
|
||||||
.short("o")
|
.short("o")
|
||||||
.value_name("FILEPATH")
|
.value_name("FILEPATH")
|
||||||
.takes_value(true)
|
.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::{
|
use crate::{
|
||||||
cli::{
|
cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||||
build_balance_message, check_account_for_fee, CliCommand, CliCommandInfo, CliConfig,
|
cli_output::{
|
||||||
CliError, ProcessResult,
|
CliBlockProduction, CliBlockProductionEntry, CliEpochInfo, CliKeyedStakeState,
|
||||||
|
CliSlotStatus, CliStakeVec, CliValidator, CliValidators,
|
||||||
},
|
},
|
||||||
display::println_name_value,
|
display::println_name_value,
|
||||||
};
|
};
|
||||||
|
@ -13,7 +14,6 @@ use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_f
|
||||||
use solana_client::{
|
use solana_client::{
|
||||||
pubsub_client::{PubsubClient, SlotInfoMessage},
|
pubsub_client::{PubsubClient, SlotInfoMessage},
|
||||||
rpc_client::RpcClient,
|
rpc_client::RpcClient,
|
||||||
rpc_response::RpcVoteAccountInfo,
|
|
||||||
};
|
};
|
||||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
|
@ -42,7 +42,6 @@ use std::{
|
||||||
|
|
||||||
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
||||||
static CROSS_MARK: Emoji = Emoji("❌ ", "");
|
static CROSS_MARK: Emoji = Emoji("❌ ", "");
|
||||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
|
||||||
|
|
||||||
pub trait ClusterQuerySubCommands {
|
pub trait ClusterQuerySubCommands {
|
||||||
fn cluster_query_subcommands(self) -> Self;
|
fn cluster_query_subcommands(self) -> Self;
|
||||||
|
@ -599,51 +598,15 @@ pub fn process_get_block_time(rpc_client: &RpcClient, slot: Slot) -> ProcessResu
|
||||||
Ok(result)
|
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(
|
pub fn process_get_epoch_info(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
|
config: &CliConfig,
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let epoch_info = rpc_client.get_epoch_info_with_commitment(commitment_config.clone())?;
|
let epoch_info: CliEpochInfo = rpc_client
|
||||||
println!();
|
.get_epoch_info_with_commitment(commitment_config.clone())?
|
||||||
println_name_value("Slot:", &epoch_info.absolute_slot.to_string());
|
.into();
|
||||||
println_name_value("Epoch:", &epoch_info.epoch.to_string());
|
config.output_format.formatted_print(&epoch_info);
|
||||||
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)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Ok("".to_string())
|
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 start_slot_index = (start_slot - first_slot_in_epoch) as usize;
|
||||||
let end_slot_index = (end_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_slots = end_slot_index - start_slot_index + 1;
|
||||||
let total_blocks = confirmed_blocks.len();
|
let total_blocks_produced = confirmed_blocks.len();
|
||||||
assert!(total_blocks <= total_slots);
|
assert!(total_blocks_produced <= total_slots);
|
||||||
let total_slots_skipped = total_slots - total_blocks;
|
let total_slots_skipped = total_slots - total_blocks_produced;
|
||||||
let mut leader_slot_count = HashMap::new();
|
let mut leader_slot_count = HashMap::new();
|
||||||
let mut leader_skipped_slots = HashMap::new();
|
let mut leader_skipped_slots = HashMap::new();
|
||||||
|
|
||||||
|
@ -762,7 +725,7 @@ pub fn process_show_block_production(
|
||||||
|
|
||||||
progress_bar.set_message(&format!(
|
progress_bar.set_message(&format!(
|
||||||
"Processing {} slots containing {} blocks and {} empty slots...",
|
"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;
|
let mut confirmed_blocks_index = 0;
|
||||||
|
@ -781,71 +744,52 @@ pub fn process_show_block_production(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if slot_of_next_confirmed_block == slot {
|
if slot_of_next_confirmed_block == slot {
|
||||||
individual_slot_status
|
individual_slot_status.push(CliSlotStatus {
|
||||||
.push(style(format!(" {:<15} {:<44}", slot, leader)).to_string());
|
slot,
|
||||||
|
leader: (*leader).to_string(),
|
||||||
|
skipped: false,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*skipped_slots += 1;
|
*skipped_slots += 1;
|
||||||
individual_slot_status.push(
|
individual_slot_status.push(CliSlotStatus {
|
||||||
style(format!(" {:<15} {:<44} SKIPPED", slot, leader))
|
slot,
|
||||||
.red()
|
leader: (*leader).to_string(),
|
||||||
.to_string(),
|
skipped: true,
|
||||||
);
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
progress_bar.finish_and_clear();
|
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![];
|
let mut leaders: Vec<CliBlockProductionEntry> = leader_slot_count
|
||||||
for (leader, leader_slots) in leader_slot_count.iter() {
|
.iter()
|
||||||
let skipped_slots = leader_skipped_slots.get(leader).unwrap();
|
.map(|(leader, leader_slots)| {
|
||||||
let blocks_produced = leader_slots - skipped_slots;
|
let skipped_slots = leader_skipped_slots.get(leader).unwrap();
|
||||||
table.push(format!(
|
let blocks_produced = leader_slots - skipped_slots;
|
||||||
" {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
CliBlockProductionEntry {
|
||||||
leader,
|
identity_pubkey: (**leader).to_string(),
|
||||||
leader_slots,
|
leader_slots: *leader_slots,
|
||||||
blocks_produced,
|
blocks_produced,
|
||||||
skipped_slots,
|
skipped_slots: *skipped_slots,
|
||||||
*skipped_slots as f64 / *leader_slots as f64 * 100.
|
}
|
||||||
));
|
})
|
||||||
}
|
.collect();
|
||||||
table.sort();
|
leaders.sort_by(|a, b| a.identity_pubkey.partial_cmp(&b.identity_pubkey).unwrap());
|
||||||
|
let block_production = CliBlockProduction {
|
||||||
println!(
|
epoch,
|
||||||
"{}\n\n {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
start_slot,
|
||||||
table.join("\n"),
|
end_slot,
|
||||||
format!("Epoch {} total:", epoch),
|
|
||||||
total_slots,
|
total_slots,
|
||||||
total_blocks,
|
total_blocks_produced,
|
||||||
total_slots_skipped,
|
total_slots_skipped,
|
||||||
total_slots_skipped as f64 / total_slots as f64 * 100.
|
leaders,
|
||||||
);
|
individual_slot_status,
|
||||||
println!(
|
verbose: config.verbose,
|
||||||
" (using data from {} slots: {} to {})",
|
};
|
||||||
total_slots, start_slot, end_slot
|
config.output_format.formatted_print(&block_production);
|
||||||
);
|
|
||||||
|
|
||||||
if config.verbose {
|
|
||||||
println!(
|
|
||||||
"\n\n{}\n{}",
|
|
||||||
style(format!(" {:<15} {:<44}", "Slot", "Identity Pubkey")).bold(),
|
|
||||||
individual_slot_status.join("\n")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok("".to_string())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1127,10 +1071,11 @@ pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult {
|
||||||
|
|
||||||
pub fn process_show_stakes(
|
pub fn process_show_stakes(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
|
config: &CliConfig,
|
||||||
use_lamports_unit: bool,
|
use_lamports_unit: bool,
|
||||||
vote_account_pubkeys: Option<&[Pubkey]>,
|
vote_account_pubkeys: Option<&[Pubkey]>,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
use crate::stake::print_stake_state;
|
use crate::stake::build_stake_state;
|
||||||
use solana_stake_program::stake_state::StakeState;
|
use solana_stake_program::stake_state::StakeState;
|
||||||
|
|
||||||
let progress_bar = new_spinner_progress_bar();
|
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())?;
|
let all_stake_accounts = rpc_client.get_program_accounts(&solana_stake_program::id())?;
|
||||||
progress_bar.finish_and_clear();
|
progress_bar.finish_and_clear();
|
||||||
|
|
||||||
|
let mut stake_accounts: Vec<CliKeyedStakeState> = vec![];
|
||||||
for (stake_pubkey, stake_account) in all_stake_accounts {
|
for (stake_pubkey, stake_account) in all_stake_accounts {
|
||||||
if let Ok(stake_state) = stake_account.state() {
|
if let Ok(stake_state) = stake_account.state() {
|
||||||
match stake_state {
|
match stake_state {
|
||||||
StakeState::Initialized(_) => {
|
StakeState::Initialized(_) => {
|
||||||
if vote_account_pubkeys.is_none() {
|
if vote_account_pubkeys.is_none() {
|
||||||
println!("\nstake pubkey: {}", stake_pubkey);
|
stake_accounts.push(CliKeyedStakeState {
|
||||||
print_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
stake_pubkey: stake_pubkey.to_string(),
|
||||||
|
stake_state: build_stake_state(
|
||||||
|
stake_account.lamports,
|
||||||
|
&stake_state,
|
||||||
|
use_lamports_unit,
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StakeState::Stake(_, stake) => {
|
StakeState::Stake(_, stake) => {
|
||||||
|
@ -1153,19 +1105,29 @@ pub fn process_show_stakes(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.contains(&stake.delegation.voter_pubkey)
|
.contains(&stake.delegation.voter_pubkey)
|
||||||
{
|
{
|
||||||
println!("\nstake pubkey: {}", stake_pubkey);
|
stake_accounts.push(CliKeyedStakeState {
|
||||||
print_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
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())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_show_validators(
|
pub fn process_show_validators(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
|
config: &CliConfig,
|
||||||
use_lamports_unit: bool,
|
use_lamports_unit: bool,
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
|
@ -1175,126 +1137,36 @@ pub fn process_show_validators(
|
||||||
.current
|
.current
|
||||||
.iter()
|
.iter()
|
||||||
.chain(vote_accounts.delinquent.iter())
|
.chain(vote_accounts.delinquent.iter())
|
||||||
.fold(0, |acc, vote_account| acc + vote_account.activated_stake)
|
.fold(0, |acc, vote_account| acc + vote_account.activated_stake);
|
||||||
as f64;
|
|
||||||
|
|
||||||
let total_deliquent_stake = vote_accounts
|
let total_deliquent_stake = vote_accounts
|
||||||
.delinquent
|
.delinquent
|
||||||
.iter()
|
.iter()
|
||||||
.fold(0, |acc, vote_account| acc + vote_account.activated_stake)
|
.fold(0, |acc, vote_account| acc + vote_account.activated_stake);
|
||||||
as f64;
|
|
||||||
let total_current_stake = total_active_stake - total_deliquent_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;
|
let mut current = vote_accounts.current;
|
||||||
current.sort_by(|a, b| b.activated_stake.cmp(&a.activated_stake));
|
current.sort_by(|a, b| b.activated_stake.cmp(&a.activated_stake));
|
||||||
for vote_account in current.into_iter() {
|
let current_validators: Vec<CliValidator> = current
|
||||||
print_vote_account(
|
.iter()
|
||||||
vote_account,
|
.map(|vote_account| CliValidator::new(vote_account, epoch_info.epoch))
|
||||||
epoch_info.epoch,
|
.collect();
|
||||||
total_active_stake,
|
|
||||||
use_lamports_unit,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let mut delinquent = vote_accounts.delinquent;
|
let mut delinquent = vote_accounts.delinquent;
|
||||||
delinquent.sort_by(|a, b| b.activated_stake.cmp(&a.activated_stake));
|
delinquent.sort_by(|a, b| b.activated_stake.cmp(&a.activated_stake));
|
||||||
for vote_account in delinquent.into_iter() {
|
let delinquent_validators: Vec<CliValidator> = delinquent
|
||||||
print_vote_account(
|
.iter()
|
||||||
vote_account,
|
.map(|vote_account| CliValidator::new(vote_account, epoch_info.epoch))
|
||||||
epoch_info.epoch,
|
.collect();
|
||||||
total_active_stake,
|
|
||||||
use_lamports_unit,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::cli::SettingType;
|
use crate::cli::SettingType;
|
||||||
use console::style;
|
use console::style;
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::hash::Hash;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
// Pretty print a "name value"
|
// Pretty print a "name value"
|
||||||
pub fn println_name_value(name: &str, value: &str) {
|
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);
|
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) {
|
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
|
||||||
let description = match setting_type {
|
let description = match setting_type {
|
||||||
SettingType::Explicit => "",
|
SettingType::Explicit => "",
|
||||||
|
|
|
@ -18,7 +18,11 @@ macro_rules! pubkey {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
|
pub mod cli_output;
|
||||||
pub mod cluster_query;
|
pub mod cluster_query;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod nonce;
|
pub mod nonce;
|
||||||
|
|
|
@ -9,6 +9,7 @@ use solana_clap_utils::{
|
||||||
};
|
};
|
||||||
use solana_cli::{
|
use solana_cli::{
|
||||||
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
|
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
|
||||||
|
cli_output::OutputFormat,
|
||||||
display::{println_name_value, println_name_value_or},
|
display::{println_name_value, println_name_value_or},
|
||||||
};
|
};
|
||||||
use solana_cli_config::{Config, CONFIG_FILE};
|
use solana_cli_config::{Config, CONFIG_FILE};
|
||||||
|
@ -129,6 +130,15 @@ pub fn parse_args<'a>(
|
||||||
let CliCommandInfo { command, signers } =
|
let CliCommandInfo { command, signers } =
|
||||||
parse_command(&matches, &default_signer_path, wallet_manager.as_ref())?;
|
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((
|
Ok((
|
||||||
CliConfig {
|
CliConfig {
|
||||||
command,
|
command,
|
||||||
|
@ -138,6 +148,7 @@ pub fn parse_args<'a>(
|
||||||
keypair_path: default_signer_path,
|
keypair_path: default_signer_path,
|
||||||
rpc_client: None,
|
rpc_client: None,
|
||||||
verbose: matches.is_present("verbose"),
|
verbose: matches.is_present("verbose"),
|
||||||
|
output_format,
|
||||||
},
|
},
|
||||||
signers,
|
signers,
|
||||||
))
|
))
|
||||||
|
@ -199,6 +210,14 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
.global(true)
|
.global(true)
|
||||||
.help("Show additional information"),
|
.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(
|
||||||
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||||
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use crate::cli::{
|
use crate::{
|
||||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
cli::{
|
||||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||||
SignerIndex,
|
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||||
|
ProcessResult, SignerIndex,
|
||||||
|
},
|
||||||
|
cli_output::CliNonceAccount,
|
||||||
};
|
};
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use solana_clap_utils::{
|
use solana_clap_utils::{
|
||||||
|
@ -568,38 +571,26 @@ pub fn process_new_nonce(
|
||||||
|
|
||||||
pub fn process_show_nonce_account(
|
pub fn process_show_nonce_account(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
|
config: &CliConfig,
|
||||||
nonce_account_pubkey: &Pubkey,
|
nonce_account_pubkey: &Pubkey,
|
||||||
use_lamports_unit: bool,
|
use_lamports_unit: bool,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let nonce_account = get_account(rpc_client, nonce_account_pubkey)?;
|
let nonce_account = get_account(rpc_client, nonce_account_pubkey)?;
|
||||||
let print_account = |data: Option<&nonce::state::Data>| {
|
let print_account = |data: Option<&nonce::state::Data>| {
|
||||||
println!(
|
let mut nonce_account = CliNonceAccount {
|
||||||
"Balance: {}",
|
balance: nonce_account.lamports,
|
||||||
build_balance_message(nonce_account.lamports, use_lamports_unit, true)
|
minimum_balance_for_rent_exemption: rpc_client
|
||||||
);
|
.get_minimum_balance_for_rent_exemption(State::size())?,
|
||||||
println!(
|
use_lamports_unit,
|
||||||
"Minimum Balance Required: {}",
|
..CliNonceAccount::default()
|
||||||
build_balance_message(
|
};
|
||||||
rpc_client.get_minimum_balance_for_rent_exemption(State::size())?,
|
if let Some(ref data) = data {
|
||||||
use_lamports_unit,
|
nonce_account.nonce = Some(data.blockhash.to_string());
|
||||||
true
|
nonce_account.lamports_per_signature = Some(data.fee_calculator.lamports_per_signature);
|
||||||
)
|
nonce_account.authority = Some(data.authority.to_string());
|
||||||
);
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.output_format.formatted_print(&nonce_account);
|
||||||
Ok("".to_string())
|
Ok("".to_string())
|
||||||
};
|
};
|
||||||
match state_from_account(&nonce_account)? {
|
match state_from_account(&nonce_account)? {
|
||||||
|
|
142
cli/src/stake.rs
142
cli/src/stake.rs
|
@ -1,15 +1,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{
|
cli::{
|
||||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, fee_payer_arg,
|
check_account_for_fee, check_unique_pubkeys, fee_payer_arg, generate_unique_signers,
|
||||||
generate_unique_signers, log_instruction_custom_error, nonce_authority_arg, return_signers,
|
log_instruction_custom_error, nonce_authority_arg, return_signers, CliCommand,
|
||||||
CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, FEE_PAYER_ARG,
|
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},
|
nonce::{check_nonce_account, nonce_arg, NONCE_ARG, NONCE_AUTHORITY_ARG},
|
||||||
offline::{blockhash_query::BlockhashQuery, *},
|
offline::{blockhash_query::BlockhashQuery, *},
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
|
||||||
use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};
|
||||||
use console::style;
|
|
||||||
use solana_clap_utils::{input_parsers::*, input_validators::*, offline::*, ArgConstant};
|
use solana_clap_utils::{input_parsers::*, input_validators::*, offline::*, ArgConstant};
|
||||||
use solana_client::rpc_client::RpcClient;
|
use solana_client::rpc_client::RpcClient;
|
||||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
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) {
|
pub fn build_stake_state(
|
||||||
fn show_authorized(authorized: &Authorized) {
|
stake_lamports: u64,
|
||||||
println!("Stake Authority: {}", authorized.staker);
|
stake_state: &StakeState,
|
||||||
println!("Withdraw Authority: {}", authorized.withdrawer);
|
use_lamports_unit: bool,
|
||||||
}
|
) -> CliStakeState {
|
||||||
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);
|
|
||||||
}
|
|
||||||
match stake_state {
|
match stake_state {
|
||||||
StakeState::Stake(
|
StakeState::Stake(
|
||||||
Meta {
|
Meta {
|
||||||
authorized, lockup, ..
|
authorized, lockup, ..
|
||||||
},
|
},
|
||||||
stake,
|
stake,
|
||||||
) => {
|
) => CliStakeState {
|
||||||
println!(
|
stake_type: CliStakeType::Stake,
|
||||||
"Total Stake: {}",
|
total_stake: stake_lamports,
|
||||||
build_balance_message(stake_lamports, use_lamports_unit, true)
|
delegated_stake: Some(stake.delegation.stake),
|
||||||
);
|
delegated_vote_account_address: if stake.delegation.voter_pubkey != Pubkey::default() {
|
||||||
println!(
|
Some(stake.delegation.voter_pubkey.to_string())
|
||||||
"Delegated Stake: {}",
|
} else {
|
||||||
build_balance_message(stake.delegation.stake, use_lamports_unit, true)
|
None
|
||||||
);
|
},
|
||||||
if stake.delegation.voter_pubkey != Pubkey::default() {
|
activation_epoch: Some(if stake.delegation.activation_epoch < std::u64::MAX {
|
||||||
println!(
|
stake.delegation.activation_epoch
|
||||||
"Delegated Vote Account Address: {}",
|
} else {
|
||||||
stake.delegation.voter_pubkey
|
0
|
||||||
);
|
}),
|
||||||
}
|
deactivation_epoch: if stake.delegation.deactivation_epoch < std::u64::MAX {
|
||||||
println!(
|
Some(stake.delegation.deactivation_epoch)
|
||||||
"Stake activates starting from epoch: {}",
|
} else {
|
||||||
if stake.delegation.activation_epoch < std::u64::MAX {
|
None
|
||||||
stake.delegation.activation_epoch
|
},
|
||||||
} else {
|
authorized: Some(authorized.into()),
|
||||||
0
|
lockup: Some(lockup.into()),
|
||||||
}
|
use_lamports_unit,
|
||||||
);
|
},
|
||||||
if stake.delegation.deactivation_epoch < std::u64::MAX {
|
StakeState::RewardsPool => CliStakeState {
|
||||||
println!(
|
stake_type: CliStakeType::RewardsPool,
|
||||||
"Stake deactivates starting from epoch: {}",
|
..CliStakeState::default()
|
||||||
stake.delegation.deactivation_epoch
|
},
|
||||||
);
|
StakeState::Uninitialized => CliStakeState::default(),
|
||||||
}
|
|
||||||
show_authorized(&authorized);
|
|
||||||
show_lockup(&lockup);
|
|
||||||
}
|
|
||||||
StakeState::RewardsPool => println!("Stake account is a rewards pool"),
|
|
||||||
StakeState::Uninitialized => println!("Stake account is uninitialized"),
|
|
||||||
StakeState::Initialized(Meta {
|
StakeState::Initialized(Meta {
|
||||||
authorized, lockup, ..
|
authorized, lockup, ..
|
||||||
}) => {
|
}) => CliStakeState {
|
||||||
println!(
|
stake_type: CliStakeType::Initialized,
|
||||||
"Total Stake: {}",
|
total_stake: stake_lamports,
|
||||||
build_balance_message(stake_lamports, use_lamports_unit, true)
|
authorized: Some(authorized.into()),
|
||||||
);
|
lockup: Some(lockup.into()),
|
||||||
println!("Stake account is undelegated");
|
use_lamports_unit,
|
||||||
show_authorized(&authorized);
|
..CliStakeState::default()
|
||||||
show_lockup(&lockup);
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_show_stake_account(
|
pub fn process_show_stake_account(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
_config: &CliConfig,
|
config: &CliConfig,
|
||||||
stake_account_pubkey: &Pubkey,
|
stake_account_pubkey: &Pubkey,
|
||||||
use_lamports_unit: bool,
|
use_lamports_unit: bool,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
|
@ -1316,7 +1298,8 @@ pub fn process_show_stake_account(
|
||||||
}
|
}
|
||||||
match stake_account.state() {
|
match stake_account.state() {
|
||||||
Ok(stake_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())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
Err(err) => Err(CliError::RpcRequestError(format!(
|
Err(err) => Err(CliError::RpcRequestError(format!(
|
||||||
|
@ -1329,7 +1312,7 @@ pub fn process_show_stake_account(
|
||||||
|
|
||||||
pub fn process_show_stake_history(
|
pub fn process_show_stake_history(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
_config: &CliConfig,
|
config: &CliConfig,
|
||||||
use_lamports_unit: bool,
|
use_lamports_unit: bool,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let stake_history_account = rpc_client.get_account(&stake_history::id())?;
|
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())
|
CliError::RpcRequestError("Failed to deserialize stake history".to_string())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
println!();
|
let mut entries: Vec<CliStakeHistoryEntry> = vec![];
|
||||||
println!(
|
for entry in stake_history.deref() {
|
||||||
"{}",
|
entries.push(entry.into());
|
||||||
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 stake_history_output = CliStakeHistory {
|
||||||
|
entries,
|
||||||
|
use_lamports_unit,
|
||||||
|
};
|
||||||
|
config.output_format.formatted_print(&stake_history_output);
|
||||||
Ok("".to_string())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||||
display::println_name_value,
|
cli_output::{CliValidatorInfo, CliValidatorInfoVec},
|
||||||
};
|
};
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||||
|
@ -25,7 +25,6 @@ use solana_sdk::{
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
use std::{error, sync::Arc};
|
use std::{error, sync::Arc};
|
||||||
use titlecase::titlecase;
|
|
||||||
|
|
||||||
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
|
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
|
||||||
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
|
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
|
||||||
|
@ -375,7 +374,11 @@ pub fn process_set_validator_info(
|
||||||
Ok("".to_string())
|
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 {
|
let validator_info: Vec<(Pubkey, Account)> = if let Some(validator_info_pubkey) = pubkey {
|
||||||
vec![(
|
vec![(
|
||||||
validator_info_pubkey,
|
validator_info_pubkey,
|
||||||
|
@ -394,23 +397,22 @@ pub fn process_get_validator_info(rpc_client: &RpcClient, pubkey: Option<Pubkey>
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut validator_info_list: Vec<CliValidatorInfo> = vec![];
|
||||||
if validator_info.is_empty() {
|
if validator_info.is_empty() {
|
||||||
println!("No validator info accounts found");
|
println!("No validator info accounts found");
|
||||||
}
|
}
|
||||||
for (validator_info_pubkey, validator_info_account) in validator_info.iter() {
|
for (validator_info_pubkey, validator_info_account) in validator_info.iter() {
|
||||||
let (validator_pubkey, validator_info) =
|
let (validator_pubkey, validator_info) =
|
||||||
parse_validator_info(&validator_info_pubkey, &validator_info_account)?;
|
parse_validator_info(&validator_info_pubkey, &validator_info_account)?;
|
||||||
println!();
|
validator_info_list.push(CliValidatorInfo {
|
||||||
println_name_value("Validator Identity Pubkey:", &validator_pubkey.to_string());
|
identity_pubkey: validator_pubkey.to_string(),
|
||||||
println_name_value(" Info Pubkey:", &validator_info_pubkey.to_string());
|
info_pubkey: validator_info_pubkey.to_string(),
|
||||||
for (key, value) in validator_info.iter() {
|
info: validator_info,
|
||||||
println_name_value(
|
});
|
||||||
&format!(" {}:", titlecase(key)),
|
|
||||||
&value.as_str().unwrap_or("?"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
config
|
||||||
|
.output_format
|
||||||
|
.formatted_print(&CliValidatorInfoVec::new(validator_info_list));
|
||||||
Ok("".to_string())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use crate::cli::{
|
use crate::{
|
||||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
cli::{
|
||||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||||
SignerIndex,
|
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 clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||||
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
||||||
|
@ -549,7 +552,7 @@ fn get_vote_account(
|
||||||
|
|
||||||
pub fn process_show_vote_account(
|
pub fn process_show_vote_account(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
_config: &CliConfig,
|
config: &CliConfig,
|
||||||
vote_account_pubkey: &Pubkey,
|
vote_account_pubkey: &Pubkey,
|
||||||
use_lamports_unit: bool,
|
use_lamports_unit: bool,
|
||||||
commitment_config: CommitmentConfig,
|
commitment_config: CommitmentConfig,
|
||||||
|
@ -559,45 +562,38 @@ pub fn process_show_vote_account(
|
||||||
|
|
||||||
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
||||||
|
|
||||||
println!(
|
let mut votes: Vec<CliLockout> = vec![];
|
||||||
"Account Balance: {}",
|
let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
|
||||||
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);
|
|
||||||
if !vote_state.votes.is_empty() {
|
if !vote_state.votes.is_empty() {
|
||||||
println!("recent votes:");
|
|
||||||
for vote in &vote_state.votes {
|
for vote in &vote_state.votes {
|
||||||
println!(
|
votes.push(vote.into());
|
||||||
"- slot: {}\n confirmation count: {}",
|
|
||||||
vote.slot, vote.confirmation_count
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Epoch Voting History:");
|
|
||||||
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
|
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
|
||||||
let credits_earned = credits - prev_credits;
|
let credits_earned = credits - prev_credits;
|
||||||
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
|
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
|
||||||
println!(
|
epoch_voting_history.push(CliEpochVotingHistory {
|
||||||
"- epoch: {}\n slots in epoch: {}\n credits earned: {}",
|
epoch: *epoch,
|
||||||
epoch, slots_in_epoch, credits_earned,
|
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())
|
Ok("".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue