2021-02-01 18:03:07 -08:00
|
|
|
use {
|
|
|
|
crate::{
|
|
|
|
display::{
|
|
|
|
build_balance_message, build_balance_message_with_config, format_labeled_address,
|
|
|
|
unix_timestamp_to_string, writeln_name_value, BuildBalanceMessageConfig,
|
|
|
|
},
|
|
|
|
QuietDisplay, VerboseDisplay,
|
|
|
|
},
|
|
|
|
console::{style, Emoji},
|
|
|
|
inflector::cases::titlecase::to_title_case,
|
|
|
|
serde::{Deserialize, Serialize},
|
|
|
|
serde_json::{Map, Value},
|
|
|
|
solana_account_decoder::parse_token::UiTokenAccount,
|
|
|
|
solana_clap_utils::keypair::SignOnly,
|
|
|
|
solana_client::rpc_response::{
|
|
|
|
RpcAccountBalance, RpcInflationGovernor, RpcInflationRate, RpcKeyedAccount, RpcSupply,
|
|
|
|
RpcVoteAccountInfo,
|
|
|
|
},
|
|
|
|
solana_sdk::{
|
|
|
|
clock::{self, Epoch, Slot, UnixTimestamp},
|
|
|
|
epoch_info::EpochInfo,
|
|
|
|
hash::Hash,
|
|
|
|
native_token::lamports_to_sol,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
signature::Signature,
|
|
|
|
stake_history::StakeHistoryEntry,
|
|
|
|
transaction::Transaction,
|
|
|
|
},
|
|
|
|
solana_stake_program::stake_state::{Authorized, Lockup},
|
|
|
|
solana_vote_program::{
|
|
|
|
authorized_voters::AuthorizedVoters,
|
|
|
|
vote_state::{BlockTimestamp, Lockout},
|
|
|
|
},
|
|
|
|
std::{
|
|
|
|
collections::{BTreeMap, HashMap},
|
|
|
|
fmt,
|
|
|
|
str::FromStr,
|
|
|
|
time::Duration,
|
2021-01-31 22:57:22 -07:00
|
|
|
},
|
2020-06-17 23:09:33 -07:00
|
|
|
};
|
2020-04-14 13:10:25 -06:00
|
|
|
|
|
|
|
static WARNING: Emoji = Emoji("⚠️", "!");
|
|
|
|
|
|
|
|
#[derive(PartialEq)]
|
|
|
|
pub enum OutputFormat {
|
|
|
|
Display,
|
|
|
|
Json,
|
|
|
|
JsonCompact,
|
2020-09-28 14:18:31 -06:00
|
|
|
DisplayQuiet,
|
|
|
|
DisplayVerbose,
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
impl OutputFormat {
|
2020-05-06 20:27:15 -06:00
|
|
|
pub fn formatted_string<T>(&self, item: &T) -> String
|
2020-04-14 13:10:25 -06:00
|
|
|
where
|
2020-09-28 14:18:31 -06:00
|
|
|
T: Serialize + fmt::Display + QuietDisplay + VerboseDisplay,
|
2020-04-14 13:10:25 -06:00
|
|
|
{
|
|
|
|
match self {
|
2020-05-06 20:27:15 -06:00
|
|
|
OutputFormat::Display => format!("{}", item),
|
2020-09-28 14:18:31 -06:00
|
|
|
OutputFormat::DisplayQuiet => {
|
|
|
|
let mut s = String::new();
|
|
|
|
QuietDisplay::write_str(item, &mut s).unwrap();
|
|
|
|
s
|
|
|
|
}
|
|
|
|
OutputFormat::DisplayVerbose => {
|
|
|
|
let mut s = String::new();
|
|
|
|
VerboseDisplay::write_str(item, &mut s).unwrap();
|
|
|
|
s
|
|
|
|
}
|
2020-05-06 20:27:15 -06:00
|
|
|
OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(),
|
|
|
|
OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct CliAccount {
|
|
|
|
#[serde(flatten)]
|
|
|
|
pub keyed_account: RpcKeyedAccount,
|
|
|
|
#[serde(skip_serializing)]
|
|
|
|
pub use_lamports_unit: bool,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliAccount {}
|
|
|
|
impl VerboseDisplay for CliAccount {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliBlockProduction {}
|
|
|
|
impl VerboseDisplay for CliBlockProduction {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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}",
|
2020-10-15 09:16:36 -07:00
|
|
|
"Identity",
|
2020-04-14 13:10:25 -06:00
|
|
|
"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)]
|
2020-05-20 16:42:46 -07:00
|
|
|
pub epoch_info: EpochInfo,
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
|
2020-05-20 16:42:46 -07:00
|
|
|
impl From<EpochInfo> for CliEpochInfo {
|
|
|
|
fn from(epoch_info: EpochInfo) -> Self {
|
2020-04-14 13:10:25 -06:00
|
|
|
Self { epoch_info }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliEpochInfo {}
|
|
|
|
impl VerboseDisplay for CliEpochInfo {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
impl fmt::Display for CliEpochInfo {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(f)?;
|
2020-07-22 08:36:12 -07:00
|
|
|
writeln_name_value(
|
|
|
|
f,
|
|
|
|
"Block height:",
|
|
|
|
&self.epoch_info.block_height.to_string(),
|
|
|
|
)?;
|
2020-04-14 13:10:25 -06:00
|
|
|
writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?;
|
|
|
|
writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?;
|
2020-12-17 10:04:53 -08:00
|
|
|
if let Some(transaction_count) = &self.epoch_info.transaction_count {
|
|
|
|
writeln_name_value(f, "Transaction Count:", &transaction_count.to_string())?;
|
|
|
|
}
|
2020-04-14 13:10:25 -06:00
|
|
|
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 {
|
2021-01-07 09:49:24 -06:00
|
|
|
humantime::format_duration(Duration::from_millis(slot * clock::DEFAULT_MS_PER_SLOT)).to_string()
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
|
2020-06-18 16:19:32 -07:00
|
|
|
#[derive(Serialize, Deserialize, Default)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliValidatorsStakeByVersion {
|
|
|
|
pub current_validators: usize,
|
|
|
|
pub delinquent_validators: usize,
|
|
|
|
pub current_active_stake: u64,
|
|
|
|
pub delinquent_active_stake: u64,
|
|
|
|
}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliValidators {
|
|
|
|
pub total_active_stake: u64,
|
|
|
|
pub total_current_stake: u64,
|
2020-10-09 18:35:52 -06:00
|
|
|
pub total_delinquent_stake: u64,
|
2020-04-14 13:10:25 -06:00
|
|
|
pub current_validators: Vec<CliValidator>,
|
|
|
|
pub delinquent_validators: Vec<CliValidator>,
|
2020-06-18 16:19:32 -07:00
|
|
|
pub stake_by_version: BTreeMap<String, CliValidatorsStakeByVersion>,
|
2020-04-14 13:10:25 -06:00
|
|
|
#[serde(skip_serializing)]
|
|
|
|
pub use_lamports_unit: bool,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliValidators {}
|
|
|
|
impl VerboseDisplay for CliValidators {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
2020-09-21 20:20:51 -07:00
|
|
|
"{} {:<44} {:<44} {:>3}% {:>8} {:>10} {:>10} {:>8} {}",
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
2020-06-17 16:04:13 -07:00
|
|
|
validator.version,
|
2020-04-14 13:10:25 -06:00
|
|
|
if validator.activated_stake > 0 {
|
|
|
|
format!(
|
|
|
|
"{} ({:.2}%)",
|
|
|
|
build_balance_message(validator.activated_stake, use_lamports_unit, true),
|
2020-06-02 11:58:01 -07:00
|
|
|
100. * validator.activated_stake as f64 / total_active_stake as f64,
|
2020-04-14 13:10:25 -06:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
"-".into()
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2021-02-02 20:54:04 -07:00
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"{}",
|
|
|
|
style(format!(
|
|
|
|
" {:<44} {:<38} {} {} {} {:>10} {:^8} {}",
|
|
|
|
"Identity",
|
|
|
|
"Vote Account",
|
|
|
|
"Commission",
|
|
|
|
"Last Vote",
|
|
|
|
"Root Block",
|
|
|
|
"Credits",
|
|
|
|
"Version",
|
|
|
|
"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,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
writeln!(f)?;
|
2020-04-14 13:10:25 -06:00
|
|
|
writeln_name_value(
|
|
|
|
f,
|
|
|
|
"Active Stake:",
|
|
|
|
&build_balance_message(self.total_active_stake, self.use_lamports_unit, true),
|
|
|
|
)?;
|
2020-10-09 18:35:52 -06:00
|
|
|
if self.total_delinquent_stake > 0 {
|
2020-04-14 13:10:25 -06:00
|
|
|
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(
|
2020-10-09 18:35:52 -06:00
|
|
|
self.total_delinquent_stake,
|
2020-04-14 13:10:25 -06:00
|
|
|
self.use_lamports_unit,
|
|
|
|
true
|
|
|
|
),
|
2020-10-09 18:35:52 -06:00
|
|
|
100. * self.total_delinquent_stake as f64 / self.total_active_stake as f64
|
2020-04-14 13:10:25 -06:00
|
|
|
),
|
|
|
|
)?;
|
|
|
|
}
|
2020-06-17 16:04:13 -07:00
|
|
|
|
|
|
|
writeln!(f)?;
|
2020-06-18 16:19:32 -07:00
|
|
|
writeln!(f, "{}", style("Stake By Version:").bold())?;
|
|
|
|
for (version, info) in self.stake_by_version.iter() {
|
2020-06-17 16:04:13 -07:00
|
|
|
writeln!(
|
|
|
|
f,
|
2020-09-21 20:20:51 -07:00
|
|
|
"{:<8} - {:3} current validators ({:>5.2}%){}",
|
2020-06-17 16:04:13 -07:00
|
|
|
version,
|
2020-06-18 16:19:32 -07:00
|
|
|
info.current_validators,
|
|
|
|
100. * info.current_active_stake as f64 / self.total_active_stake as f64,
|
|
|
|
if info.delinquent_validators > 0 {
|
|
|
|
format!(
|
|
|
|
", {:3} delinquent validators ({:>5.2}%)",
|
|
|
|
info.delinquent_validators,
|
|
|
|
100. * info.delinquent_active_stake as f64 / self.total_active_stake as f64
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
"".to_string()
|
|
|
|
},
|
2020-06-17 16:04:13 -07:00
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
2020-06-17 16:04:13 -07:00
|
|
|
pub version: String,
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
impl CliValidator {
|
2020-06-17 23:09:33 -07:00
|
|
|
pub fn new(
|
|
|
|
vote_account: &RpcVoteAccountInfo,
|
|
|
|
current_epoch: Epoch,
|
|
|
|
version: String,
|
|
|
|
address_labels: &HashMap<String, String>,
|
|
|
|
) -> Self {
|
2020-04-14 13:10:25 -06:00
|
|
|
Self {
|
2020-06-17 23:09:33 -07:00
|
|
|
identity_pubkey: format_labeled_address(&vote_account.node_pubkey, address_labels),
|
|
|
|
vote_account_pubkey: format_labeled_address(&vote_account.vote_pubkey, address_labels),
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
2020-06-17 16:04:13 -07:00
|
|
|
version,
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliNonceAccount {}
|
|
|
|
impl VerboseDisplay for CliNonceAccount {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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");
|
2020-11-06 10:33:11 -07:00
|
|
|
writeln!(f, "Nonce blockhash: {}", nonce)?;
|
2020-04-14 13:10:25 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliStakeVec {}
|
2020-12-02 15:02:52 -07:00
|
|
|
impl VerboseDisplay for CliStakeVec {
|
|
|
|
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
|
|
|
for state in &self.0 {
|
|
|
|
writeln!(w)?;
|
|
|
|
VerboseDisplay::write_str(state, w)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2020-09-28 14:18:31 -06:00
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliKeyedStakeState {}
|
2020-12-02 15:02:52 -07:00
|
|
|
impl VerboseDisplay for CliKeyedStakeState {
|
|
|
|
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
|
|
|
writeln!(w, "Stake Pubkey: {}", self.stake_pubkey)?;
|
|
|
|
VerboseDisplay::write_str(&self.stake_state, w)
|
|
|
|
}
|
|
|
|
}
|
2020-09-28 14:18:31 -06:00
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-07 14:38:17 -07:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliEpochReward {
|
|
|
|
pub epoch: Epoch,
|
|
|
|
pub effective_slot: Slot,
|
|
|
|
pub amount: u64, // lamports
|
|
|
|
pub post_balance: u64, // lamports
|
|
|
|
pub percent_change: f64,
|
2020-12-11 10:47:32 -08:00
|
|
|
pub apr: Option<f64>,
|
2020-10-07 14:38:17 -07:00
|
|
|
}
|
|
|
|
|
2020-11-07 00:07:40 +09:00
|
|
|
fn show_votes_and_credits(
|
|
|
|
f: &mut fmt::Formatter,
|
|
|
|
votes: &[CliLockout],
|
|
|
|
epoch_voting_history: &[CliEpochVotingHistory],
|
|
|
|
) -> fmt::Result {
|
|
|
|
if votes.is_empty() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
writeln!(f, "Recent Votes:")?;
|
|
|
|
for vote in votes {
|
|
|
|
writeln!(f, "- slot: {}", vote.slot)?;
|
|
|
|
writeln!(f, " confirmation count: {}", vote.confirmation_count)?;
|
|
|
|
}
|
|
|
|
writeln!(f, "Epoch Voting History:")?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"* missed credits include slots unavailable to vote on due to delinquent leaders",
|
|
|
|
)?;
|
|
|
|
for entry in epoch_voting_history {
|
|
|
|
writeln!(
|
|
|
|
f, // tame fmt so that this will be folded like following
|
|
|
|
"- epoch: {}",
|
|
|
|
entry.epoch
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
" credits range: [{}..{})",
|
|
|
|
entry.prev_credits, entry.credits
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
" credits/slots: {}/{}",
|
|
|
|
entry.credits_earned, entry.slots_in_epoch
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-07 14:38:17 -07:00
|
|
|
fn show_epoch_rewards(
|
|
|
|
f: &mut fmt::Formatter,
|
|
|
|
epoch_rewards: &Option<Vec<CliEpochReward>>,
|
|
|
|
) -> fmt::Result {
|
|
|
|
if let Some(epoch_rewards) = epoch_rewards {
|
|
|
|
if epoch_rewards.is_empty() {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
writeln!(f, "Epoch Rewards:")?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
2021-01-02 08:04:38 -08:00
|
|
|
" {:<6} {:<11} {:<16} {:<16} {:>14} {:>14}",
|
2020-10-07 14:38:17 -07:00
|
|
|
"Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR"
|
|
|
|
)?;
|
|
|
|
for reward in epoch_rewards {
|
|
|
|
writeln!(
|
|
|
|
f,
|
2021-01-02 08:04:38 -08:00
|
|
|
" {:<6} {:<11} ◎{:<16.9} ◎{:<14.9} {:>13.2}% {}",
|
2020-10-07 14:38:17 -07:00
|
|
|
reward.epoch,
|
|
|
|
reward.effective_slot,
|
|
|
|
lamports_to_sol(reward.amount),
|
|
|
|
lamports_to_sol(reward.post_balance),
|
|
|
|
reward.percent_change,
|
2020-12-11 10:47:32 -08:00
|
|
|
reward
|
|
|
|
.apr
|
2021-01-02 08:04:38 -08:00
|
|
|
.map(|apr| format!("{:>13.2}%", apr))
|
2020-12-11 10:47:32 -08:00
|
|
|
.unwrap_or_default(),
|
2020-10-07 14:38:17 -07:00
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
#[derive(Default, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliStakeState {
|
|
|
|
pub stake_type: CliStakeType,
|
2020-04-27 14:34:24 -06:00
|
|
|
pub account_balance: u64,
|
2020-04-14 13:10:25 -06:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2020-12-02 15:02:52 -07:00
|
|
|
pub credits_observed: Option<u64>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
2020-04-27 14:34:24 -06:00
|
|
|
#[serde(skip_serializing)]
|
|
|
|
pub current_epoch: Epoch,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub rent_exempt_reserve: Option<u64>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub active_stake: Option<u64>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub activating_stake: Option<u64>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub deactivating_stake: Option<u64>,
|
2020-10-07 14:38:17 -07:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub epoch_rewards: Option<Vec<CliEpochReward>>,
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliStakeState {}
|
2020-12-02 15:02:52 -07:00
|
|
|
impl VerboseDisplay for CliStakeState {
|
|
|
|
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
|
|
|
write!(w, "{}", self)?;
|
|
|
|
if let Some(credits) = self.credits_observed {
|
|
|
|
writeln!(w, "Credits Observed: {}", credits)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2020-09-28 14:18:31 -06:00
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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(())
|
|
|
|
}
|
2020-05-27 17:03:49 -06:00
|
|
|
fn show_lockup(f: &mut fmt::Formatter, lockup: Option<&CliLockup>) -> fmt::Result {
|
|
|
|
if let Some(lockup) = lockup {
|
|
|
|
if lockup.unix_timestamp != UnixTimestamp::default() {
|
|
|
|
writeln!(
|
|
|
|
f,
|
2020-10-18 21:59:29 -07:00
|
|
|
"Lockup Timestamp: {}",
|
|
|
|
unix_timestamp_to_string(lockup.unix_timestamp)
|
2020-05-27 17:03:49 -06:00
|
|
|
)?;
|
|
|
|
}
|
|
|
|
if lockup.epoch != Epoch::default() {
|
|
|
|
writeln!(f, "Lockup Epoch: {}", lockup.epoch)?;
|
|
|
|
}
|
|
|
|
writeln!(f, "Lockup Custodian: {}", lockup.custodian)?;
|
|
|
|
}
|
2020-04-14 13:10:25 -06:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-04-27 14:34:24 -06:00
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Balance: {}",
|
|
|
|
build_balance_message(self.account_balance, self.use_lamports_unit, true)
|
|
|
|
)?;
|
|
|
|
|
|
|
|
if let Some(rent_exempt_reserve) = self.rent_exempt_reserve {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Rent Exempt Reserve: {}",
|
|
|
|
build_balance_message(rent_exempt_reserve, self.use_lamports_unit, true)
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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, "Stake account is undelegated")?;
|
|
|
|
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
2020-05-27 17:03:49 -06:00
|
|
|
show_lockup(f, self.lockup.as_ref())?;
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
CliStakeType::Stake => {
|
2020-04-27 14:34:24 -06:00
|
|
|
let show_delegation = {
|
|
|
|
self.active_stake.is_some()
|
|
|
|
|| self.activating_stake.is_some()
|
|
|
|
|| self.deactivating_stake.is_some()
|
|
|
|
|| self
|
|
|
|
.deactivation_epoch
|
|
|
|
.map(|de| de > self.current_epoch)
|
|
|
|
.unwrap_or(true)
|
|
|
|
};
|
|
|
|
if show_delegation {
|
|
|
|
let delegated_stake = self.delegated_stake.unwrap();
|
2020-04-14 13:10:25 -06:00
|
|
|
writeln!(
|
|
|
|
f,
|
2020-04-27 14:34:24 -06:00
|
|
|
"Delegated Stake: {}",
|
|
|
|
build_balance_message(delegated_stake, self.use_lamports_unit, true)
|
2020-04-14 13:10:25 -06:00
|
|
|
)?;
|
2020-04-27 14:34:24 -06:00
|
|
|
if self
|
|
|
|
.deactivation_epoch
|
|
|
|
.map(|d| self.current_epoch <= d)
|
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
|
|
|
let active_stake = self.active_stake.unwrap_or(0);
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Active Stake: {}",
|
|
|
|
build_balance_message(active_stake, self.use_lamports_unit, true),
|
|
|
|
)?;
|
|
|
|
let activating_stake = self.activating_stake.or_else(|| {
|
|
|
|
if self.active_stake.is_none() {
|
|
|
|
Some(delegated_stake)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if let Some(activating_stake) = activating_stake {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Activating Stake: {}",
|
|
|
|
build_balance_message(
|
|
|
|
activating_stake,
|
|
|
|
self.use_lamports_unit,
|
|
|
|
true
|
|
|
|
),
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Stake activates starting from epoch: {}",
|
|
|
|
self.activation_epoch.unwrap()
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(deactivation_epoch) = self.deactivation_epoch {
|
|
|
|
if self.current_epoch > deactivation_epoch {
|
|
|
|
let deactivating_stake = self.deactivating_stake.or(self.active_stake);
|
|
|
|
if let Some(deactivating_stake) = deactivating_stake {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Inactive Stake: {}",
|
|
|
|
build_balance_message(
|
|
|
|
delegated_stake - deactivating_stake,
|
|
|
|
self.use_lamports_unit,
|
|
|
|
true
|
|
|
|
),
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Deactivating Stake: {}",
|
|
|
|
build_balance_message(
|
|
|
|
deactivating_stake,
|
|
|
|
self.use_lamports_unit,
|
|
|
|
true
|
|
|
|
),
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Stake deactivates starting from epoch: {}",
|
|
|
|
deactivation_epoch
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
if let Some(delegated_vote_account_address) =
|
|
|
|
&self.delegated_vote_account_address
|
|
|
|
{
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Delegated Vote Account Address: {}",
|
|
|
|
delegated_vote_account_address
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
writeln!(f, "Stake account is undelegated")?;
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
2020-05-27 17:03:49 -06:00
|
|
|
show_lockup(f, self.lockup.as_ref())?;
|
2020-10-07 14:38:17 -07:00
|
|
|
show_epoch_rewards(f, &self.epoch_rewards)?
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-07 14:38:17 -07:00
|
|
|
#[derive(Serialize, Deserialize, PartialEq)]
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliStakeHistory {}
|
|
|
|
impl VerboseDisplay for CliStakeHistory {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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()
|
|
|
|
)?;
|
2021-01-31 22:57:22 -07:00
|
|
|
let config = BuildBalanceMessageConfig {
|
|
|
|
use_lamports_unit: self.use_lamports_unit,
|
|
|
|
show_unit: false,
|
|
|
|
trim_trailing_zeros: false,
|
|
|
|
};
|
2020-04-14 13:10:25 -06:00
|
|
|
for entry in &self.entries {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
" {:>5} {:>20} {:>20} {:>20} {}",
|
|
|
|
entry.epoch,
|
2021-01-31 22:57:22 -07:00
|
|
|
build_balance_message_with_config(entry.effective_stake, &config),
|
|
|
|
build_balance_message_with_config(entry.activating_stake, &config),
|
|
|
|
build_balance_message_with_config(entry.deactivating_stake, &config),
|
2020-04-14 13:10:25 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliValidatorInfoVec {}
|
|
|
|
impl VerboseDisplay for CliValidatorInfoVec {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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>,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliValidatorInfo {}
|
|
|
|
impl VerboseDisplay for CliValidatorInfo {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
impl fmt::Display for CliValidatorInfo {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2020-10-15 09:16:36 -07:00
|
|
|
writeln_name_value(f, "Validator Identity:", &self.identity_pubkey)?;
|
|
|
|
writeln_name_value(f, " Info Address:", &self.info_pubkey)?;
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
2020-10-07 14:38:17 -07:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub epoch_rewards: Option<Vec<CliEpochReward>>,
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliVoteAccount {}
|
|
|
|
impl VerboseDisplay for CliVoteAccount {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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(),
|
|
|
|
}
|
|
|
|
)?;
|
2020-10-18 21:59:29 -07:00
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Recent Timestamp: {} from slot {}",
|
|
|
|
unix_timestamp_to_string(self.recent_timestamp.timestamp),
|
|
|
|
self.recent_timestamp.slot
|
|
|
|
)?;
|
2020-11-07 00:07:40 +09:00
|
|
|
show_votes_and_credits(f, &self.votes, &self.epoch_voting_history)?;
|
2020-10-07 14:38:17 -07:00
|
|
|
show_epoch_rewards(f, &self.epoch_rewards)?;
|
2020-04-14 13:10:25 -06:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliAuthorizedVoters {
|
|
|
|
authorized_voters: BTreeMap<Epoch, String>,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliAuthorizedVoters {}
|
|
|
|
impl VerboseDisplay for CliAuthorizedVoters {}
|
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
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,
|
2020-11-07 00:07:40 +09:00
|
|
|
pub credits: u64,
|
|
|
|
pub prev_credits: u64,
|
2020-04-14 13:10:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-05 09:42:03 -06:00
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliBlockTime {
|
|
|
|
pub slot: Slot,
|
|
|
|
pub timestamp: UnixTimestamp,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliBlockTime {}
|
|
|
|
impl VerboseDisplay for CliBlockTime {}
|
|
|
|
|
2020-05-05 09:42:03 -06:00
|
|
|
impl fmt::Display for CliBlockTime {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln_name_value(f, "Block:", &self.slot.to_string())?;
|
2020-10-18 21:59:29 -07:00
|
|
|
writeln_name_value(f, "Date:", &unix_timestamp_to_string(self.timestamp))
|
2020-05-05 09:42:03 -06:00
|
|
|
}
|
|
|
|
}
|
2020-05-07 07:21:48 +03:00
|
|
|
|
2021-01-09 17:02:22 -07:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliLeaderSchedule {
|
|
|
|
pub epoch: Epoch,
|
|
|
|
pub leader_schedule_entries: Vec<CliLeaderScheduleEntry>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QuietDisplay for CliLeaderSchedule {}
|
|
|
|
impl VerboseDisplay for CliLeaderSchedule {}
|
|
|
|
|
|
|
|
impl fmt::Display for CliLeaderSchedule {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
for entry in &self.leader_schedule_entries {
|
|
|
|
writeln!(f, " {:<15} {:<44}", entry.slot, entry.leader)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliLeaderScheduleEntry {
|
|
|
|
pub slot: Slot,
|
|
|
|
pub leader: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliInflation {
|
|
|
|
pub governor: RpcInflationGovernor,
|
|
|
|
pub current_rate: RpcInflationRate,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QuietDisplay for CliInflation {}
|
|
|
|
impl VerboseDisplay for CliInflation {}
|
|
|
|
|
|
|
|
impl fmt::Display for CliInflation {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(f, "{}", style("Inflation Governor:").bold())?;
|
|
|
|
if (self.governor.initial - self.governor.terminal).abs() < f64::EPSILON {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Fixed APR: {:>5.2}%",
|
|
|
|
self.governor.terminal * 100.
|
|
|
|
)?;
|
|
|
|
} else {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Initial APR: {:>5.2}%",
|
|
|
|
self.governor.initial * 100.
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Terminal APR: {:>5.2}%",
|
|
|
|
self.governor.terminal * 100.
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Rate reduction per year: {:>5.2}%",
|
|
|
|
self.governor.taper * 100.
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
if self.governor.foundation_term > 0. {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Foundation percentage: {:>5.2}%",
|
|
|
|
self.governor.foundation
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Foundation term: {:.1} years",
|
|
|
|
self.governor.foundation_term
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"\n{}",
|
|
|
|
style(format!("Inflation for Epoch {}:", self.current_rate.epoch)).bold()
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Total APR: {:>5.2}%",
|
|
|
|
self.current_rate.total * 100.
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Staking APR: {:>5.2}%",
|
|
|
|
self.current_rate.validator * 100.
|
|
|
|
)?;
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"Foundation APR: {:>5.2}%",
|
|
|
|
self.current_rate.foundation * 100.
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-07 07:21:48 +03:00
|
|
|
#[derive(Serialize, Deserialize, Default)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliSignOnlyData {
|
|
|
|
pub blockhash: String,
|
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
|
|
|
pub signers: Vec<String>,
|
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
|
|
|
pub absent: Vec<String>,
|
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
|
|
|
pub bad_sig: Vec<String>,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliSignOnlyData {}
|
|
|
|
impl VerboseDisplay for CliSignOnlyData {}
|
|
|
|
|
2020-05-07 07:21:48 +03:00
|
|
|
impl fmt::Display for CliSignOnlyData {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(f)?;
|
|
|
|
writeln_name_value(f, "Blockhash:", &self.blockhash)?;
|
|
|
|
if !self.signers.is_empty() {
|
|
|
|
writeln!(f, "{}", style("Signers (Pubkey=Signature):").bold())?;
|
|
|
|
for signer in self.signers.iter() {
|
|
|
|
writeln!(f, " {}", signer)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !self.absent.is_empty() {
|
|
|
|
writeln!(f, "{}", style("Absent Signers (Pubkey):").bold())?;
|
|
|
|
for pubkey in self.absent.iter() {
|
|
|
|
writeln!(f, " {}", pubkey)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !self.bad_sig.is_empty() {
|
|
|
|
writeln!(f, "{}", style("Bad Signatures (Pubkey):").bold())?;
|
|
|
|
for pubkey in self.bad_sig.iter() {
|
|
|
|
writeln!(f, " {}", pubkey)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
2020-05-26 15:08:07 -06:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-05-07 07:21:48 +03:00
|
|
|
pub struct CliSignature {
|
|
|
|
pub signature: String,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliSignature {}
|
|
|
|
impl VerboseDisplay for CliSignature {}
|
|
|
|
|
2020-05-07 07:21:48 +03:00
|
|
|
impl fmt::Display for CliSignature {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(f)?;
|
|
|
|
writeln_name_value(f, "Signature:", &self.signature)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2020-05-10 12:05:14 -06:00
|
|
|
|
2020-05-12 21:05:05 -06:00
|
|
|
#[derive(Serialize, Deserialize)]
|
2020-05-26 15:08:07 -06:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-05-12 21:05:05 -06:00
|
|
|
pub struct CliAccountBalances {
|
|
|
|
pub accounts: Vec<RpcAccountBalance>,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliAccountBalances {}
|
|
|
|
impl VerboseDisplay for CliAccountBalances {}
|
|
|
|
|
2020-05-12 21:05:05 -06:00
|
|
|
impl fmt::Display for CliAccountBalances {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"{}",
|
|
|
|
style(format!("{:<44} {}", "Address", "Balance",)).bold()
|
|
|
|
)?;
|
|
|
|
for account in &self.accounts {
|
|
|
|
writeln!(
|
|
|
|
f,
|
|
|
|
"{:<44} {}",
|
|
|
|
account.address,
|
|
|
|
&format!("{} SOL", lamports_to_sol(account.lamports))
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 12:05:14 -06:00
|
|
|
#[derive(Serialize, Deserialize)]
|
2020-05-26 15:08:07 -06:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-05-10 12:05:14 -06:00
|
|
|
pub struct CliSupply {
|
|
|
|
pub total: u64,
|
|
|
|
pub circulating: u64,
|
|
|
|
pub non_circulating: u64,
|
|
|
|
pub non_circulating_accounts: Vec<String>,
|
|
|
|
#[serde(skip_serializing)]
|
|
|
|
pub print_accounts: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<RpcSupply> for CliSupply {
|
|
|
|
fn from(rpc_supply: RpcSupply) -> Self {
|
|
|
|
Self {
|
|
|
|
total: rpc_supply.total,
|
|
|
|
circulating: rpc_supply.circulating,
|
|
|
|
non_circulating: rpc_supply.non_circulating,
|
|
|
|
non_circulating_accounts: rpc_supply.non_circulating_accounts,
|
|
|
|
print_accounts: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliSupply {}
|
|
|
|
impl VerboseDisplay for CliSupply {}
|
|
|
|
|
2020-05-10 12:05:14 -06:00
|
|
|
impl fmt::Display for CliSupply {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln_name_value(f, "Total:", &format!("{} SOL", lamports_to_sol(self.total)))?;
|
|
|
|
writeln_name_value(
|
|
|
|
f,
|
|
|
|
"Circulating:",
|
|
|
|
&format!("{} SOL", lamports_to_sol(self.circulating)),
|
|
|
|
)?;
|
|
|
|
writeln_name_value(
|
|
|
|
f,
|
|
|
|
"Non-Circulating:",
|
|
|
|
&format!("{} SOL", lamports_to_sol(self.non_circulating)),
|
|
|
|
)?;
|
|
|
|
if self.print_accounts {
|
|
|
|
writeln!(f)?;
|
|
|
|
writeln_name_value(f, "Non-Circulating Accounts:", " ")?;
|
|
|
|
for account in &self.non_circulating_accounts {
|
|
|
|
writeln!(f, " {}", account)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2020-05-26 15:08:07 -06:00
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliFees {
|
|
|
|
pub slot: Slot,
|
|
|
|
pub blockhash: String,
|
|
|
|
pub lamports_per_signature: u64,
|
|
|
|
pub last_valid_slot: Slot,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliFees {}
|
|
|
|
impl VerboseDisplay for CliFees {}
|
|
|
|
|
2020-05-26 15:08:07 -06:00
|
|
|
impl fmt::Display for CliFees {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln_name_value(f, "Blockhash:", &self.blockhash)?;
|
|
|
|
writeln_name_value(
|
|
|
|
f,
|
|
|
|
"Lamports per signature:",
|
|
|
|
&self.lamports_per_signature.to_string(),
|
|
|
|
)?;
|
|
|
|
writeln_name_value(f, "Last valid slot:", &self.last_valid_slot.to_string())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2020-09-22 20:29:32 -06:00
|
|
|
|
2020-09-24 18:33:08 -06:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliTokenAccount {
|
|
|
|
pub address: String,
|
|
|
|
#[serde(flatten)]
|
|
|
|
pub token_account: UiTokenAccount,
|
|
|
|
}
|
|
|
|
|
2020-09-28 14:18:31 -06:00
|
|
|
impl QuietDisplay for CliTokenAccount {}
|
|
|
|
impl VerboseDisplay for CliTokenAccount {}
|
|
|
|
|
2020-09-24 18:33:08 -06:00
|
|
|
impl fmt::Display for CliTokenAccount {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(f)?;
|
|
|
|
writeln_name_value(f, "Address:", &self.address)?;
|
|
|
|
let account = &self.token_account;
|
|
|
|
writeln_name_value(
|
|
|
|
f,
|
|
|
|
"Balance:",
|
|
|
|
&account.token_amount.real_number_string_trimmed(),
|
|
|
|
)?;
|
|
|
|
let mint = format!(
|
|
|
|
"{}{}",
|
|
|
|
account.mint,
|
|
|
|
if account.is_native { " (native)" } else { "" }
|
|
|
|
);
|
|
|
|
writeln_name_value(f, "Mint:", &mint)?;
|
|
|
|
writeln_name_value(f, "Owner:", &account.owner)?;
|
|
|
|
writeln_name_value(f, "State:", &format!("{:?}", account.state))?;
|
|
|
|
if let Some(delegate) = &account.delegate {
|
|
|
|
writeln!(f, "Delegation:")?;
|
|
|
|
writeln_name_value(f, " Delegate:", delegate)?;
|
|
|
|
let allowance = account.delegated_amount.as_ref().unwrap();
|
|
|
|
writeln_name_value(f, " Allowance:", &allowance.real_number_string_trimmed())?;
|
|
|
|
}
|
|
|
|
writeln_name_value(
|
|
|
|
f,
|
|
|
|
"Close authority:",
|
|
|
|
&account.close_authority.as_ref().unwrap_or(&String::new()),
|
|
|
|
)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-01 10:39:37 -07:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliProgramId {
|
|
|
|
pub program_id: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QuietDisplay for CliProgramId {}
|
|
|
|
impl VerboseDisplay for CliProgramId {}
|
|
|
|
|
|
|
|
impl fmt::Display for CliProgramId {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln_name_value(f, "Program Id:", &self.program_id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliProgramBuffer {
|
|
|
|
pub buffer: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QuietDisplay for CliProgramBuffer {}
|
|
|
|
impl VerboseDisplay for CliProgramBuffer {}
|
|
|
|
|
|
|
|
impl fmt::Display for CliProgramBuffer {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln_name_value(f, "Buffer:", &self.buffer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub enum CliProgramAccountType {
|
|
|
|
Buffer,
|
|
|
|
Program,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliProgramAuthority {
|
|
|
|
pub authority: String,
|
|
|
|
pub account_type: CliProgramAccountType,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl QuietDisplay for CliProgramAuthority {}
|
|
|
|
impl VerboseDisplay for CliProgramAuthority {}
|
|
|
|
|
|
|
|
impl fmt::Display for CliProgramAuthority {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln_name_value(f, "Account Type:", &format!("{:?}", self.account_type))?;
|
|
|
|
writeln_name_value(f, "Authority:", &self.authority)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-02 15:36:02 -08:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliUpgradeableProgram {
|
|
|
|
pub program_id: String,
|
|
|
|
pub programdata_address: String,
|
2021-02-02 22:33:03 -08:00
|
|
|
pub authority: String,
|
2021-02-04 01:30:50 -08:00
|
|
|
pub last_deploy_slot: u64,
|
|
|
|
pub data_len: usize,
|
2021-02-02 15:36:02 -08:00
|
|
|
}
|
|
|
|
impl QuietDisplay for CliUpgradeableProgram {}
|
|
|
|
impl VerboseDisplay for CliUpgradeableProgram {}
|
|
|
|
impl fmt::Display for CliUpgradeableProgram {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(f)?;
|
2021-02-02 22:33:03 -08:00
|
|
|
writeln_name_value(f, "Program Id:", &self.program_id)?;
|
|
|
|
writeln_name_value(f, "ProgramData Address:", &self.programdata_address)?;
|
|
|
|
writeln_name_value(f, "Authority:", &self.authority)?;
|
2021-02-02 15:36:02 -08:00
|
|
|
writeln_name_value(
|
|
|
|
f,
|
2021-02-04 01:30:50 -08:00
|
|
|
"Last Deployed In Slot:",
|
|
|
|
&self.last_deploy_slot.to_string(),
|
2021-02-02 15:36:02 -08:00
|
|
|
)?;
|
|
|
|
writeln_name_value(
|
|
|
|
f,
|
2021-02-04 01:30:50 -08:00
|
|
|
"Data Length:",
|
|
|
|
&format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
|
2021-02-02 15:36:02 -08:00
|
|
|
)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct CliUpgradeableBuffer {
|
|
|
|
pub address: String,
|
|
|
|
pub authority: String,
|
|
|
|
pub program_len: usize,
|
|
|
|
}
|
|
|
|
impl QuietDisplay for CliUpgradeableBuffer {}
|
|
|
|
impl VerboseDisplay for CliUpgradeableBuffer {}
|
|
|
|
impl fmt::Display for CliUpgradeableBuffer {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
writeln!(f)?;
|
|
|
|
writeln_name_value(f, "Buffer Address:", &self.address)?;
|
2021-02-02 22:33:03 -08:00
|
|
|
writeln_name_value(f, "Authority:", &self.authority)?;
|
2021-02-02 15:36:02 -08:00
|
|
|
writeln_name_value(
|
|
|
|
f,
|
2021-02-02 22:33:03 -08:00
|
|
|
"Program Length:",
|
2021-02-02 15:36:02 -08:00
|
|
|
&format!("{:?} ({:#x?}) bytes", self.program_len, self.program_len),
|
|
|
|
)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-22 20:29:32 -06:00
|
|
|
pub fn return_signers(
|
|
|
|
tx: &Transaction,
|
|
|
|
output_format: &OutputFormat,
|
|
|
|
) -> Result<String, Box<dyn std::error::Error>> {
|
|
|
|
let verify_results = tx.verify_with_results();
|
|
|
|
let mut signers = Vec::new();
|
|
|
|
let mut absent = Vec::new();
|
|
|
|
let mut bad_sig = Vec::new();
|
|
|
|
tx.signatures
|
|
|
|
.iter()
|
|
|
|
.zip(tx.message.account_keys.iter())
|
|
|
|
.zip(verify_results.into_iter())
|
|
|
|
.for_each(|((sig, key), res)| {
|
|
|
|
if res {
|
|
|
|
signers.push(format!("{}={}", key, sig))
|
|
|
|
} else if *sig == Signature::default() {
|
|
|
|
absent.push(key.to_string());
|
|
|
|
} else {
|
|
|
|
bad_sig.push(key.to_string());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let cli_command = CliSignOnlyData {
|
|
|
|
blockhash: tx.message.recent_blockhash.to_string(),
|
|
|
|
signers,
|
|
|
|
absent,
|
|
|
|
bad_sig,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(output_format.formatted_string(&cli_command))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
|
|
|
|
let object: Value = serde_json::from_str(&reply).unwrap();
|
|
|
|
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
|
|
|
|
let blockhash = blockhash_str.parse::<Hash>().unwrap();
|
|
|
|
let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
|
|
|
|
let signer_strings = object.get("signers");
|
|
|
|
if let Some(sig_strings) = signer_strings {
|
|
|
|
present_signers = sig_strings
|
|
|
|
.as_array()
|
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.map(|signer_string| {
|
|
|
|
let mut signer = signer_string.as_str().unwrap().split('=');
|
|
|
|
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
|
|
|
|
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
|
|
|
|
(key, sig)
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
}
|
|
|
|
let mut absent_signers: Vec<Pubkey> = Vec::new();
|
|
|
|
let signer_strings = object.get("absent");
|
|
|
|
if let Some(sig_strings) = signer_strings {
|
|
|
|
absent_signers = sig_strings
|
|
|
|
.as_array()
|
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.map(|val| {
|
|
|
|
let s = val.as_str().unwrap();
|
|
|
|
Pubkey::from_str(s).unwrap()
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
}
|
|
|
|
let mut bad_signers: Vec<Pubkey> = Vec::new();
|
|
|
|
let signer_strings = object.get("badSig");
|
|
|
|
if let Some(sig_strings) = signer_strings {
|
|
|
|
bad_signers = sig_strings
|
|
|
|
.as_array()
|
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.map(|val| {
|
|
|
|
let s = val.as_str().unwrap();
|
|
|
|
Pubkey::from_str(s).unwrap()
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
}
|
|
|
|
|
|
|
|
SignOnly {
|
|
|
|
blockhash,
|
|
|
|
present_signers,
|
|
|
|
absent_signers,
|
|
|
|
bad_signers,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 23:39:26 -07:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum CliSignatureVerificationStatus {
|
|
|
|
None,
|
|
|
|
Pass,
|
|
|
|
Fail,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CliSignatureVerificationStatus {
|
2021-01-31 23:42:35 -07:00
|
|
|
pub fn verify_transaction(tx: &Transaction) -> Vec<Self> {
|
2021-01-31 23:39:26 -07:00
|
|
|
tx.verify_with_results()
|
|
|
|
.iter()
|
|
|
|
.zip(&tx.signatures)
|
|
|
|
.map(|(stat, sig)| match stat {
|
|
|
|
true => CliSignatureVerificationStatus::Pass,
|
|
|
|
false if sig == &Signature::default() => CliSignatureVerificationStatus::None,
|
|
|
|
false => CliSignatureVerificationStatus::Fail,
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for CliSignatureVerificationStatus {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Self::None => write!(f, "none"),
|
|
|
|
Self::Pass => write!(f, "pass"),
|
|
|
|
Self::Fail => write!(f, "fail"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-22 20:29:32 -06:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use solana_sdk::{
|
|
|
|
message::Message,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
signature::{keypair_from_seed, NullSigner, Signature, Signer, SignerError},
|
|
|
|
system_instruction,
|
|
|
|
transaction::Transaction,
|
|
|
|
};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_return_signers() {
|
|
|
|
struct BadSigner {
|
|
|
|
pubkey: Pubkey,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BadSigner {
|
|
|
|
pub fn new(pubkey: Pubkey) -> Self {
|
|
|
|
Self { pubkey }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Signer for BadSigner {
|
|
|
|
fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
|
|
|
|
Ok(self.pubkey)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
|
|
|
|
Ok(Signature::new(&[1u8; 64]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
|
|
|
|
let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::new(&[3u8; 32])));
|
|
|
|
let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::new(&[4u8; 32])));
|
|
|
|
let to = Pubkey::new(&[5u8; 32]);
|
|
|
|
let nonce = Pubkey::new(&[6u8; 32]);
|
|
|
|
let from = present.pubkey();
|
|
|
|
let fee_payer = absent.pubkey();
|
|
|
|
let nonce_auth = bad.pubkey();
|
|
|
|
let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
|
|
|
|
vec![system_instruction::transfer(&from, &to, 42)],
|
|
|
|
Some(&fee_payer),
|
|
|
|
&nonce,
|
|
|
|
&nonce_auth,
|
|
|
|
));
|
|
|
|
|
|
|
|
let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
|
|
|
|
let blockhash = Hash::new(&[7u8; 32]);
|
|
|
|
tx.try_partial_sign(&signers, blockhash).unwrap();
|
|
|
|
let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap();
|
|
|
|
let sign_only = parse_sign_only_reply_string(&res);
|
|
|
|
assert_eq!(sign_only.blockhash, blockhash);
|
|
|
|
assert_eq!(sign_only.present_signers[0].0, present.pubkey());
|
|
|
|
assert_eq!(sign_only.absent_signers[0], absent.pubkey());
|
|
|
|
assert_eq!(sign_only.bad_signers[0], bad.pubkey());
|
|
|
|
}
|
2020-09-28 14:18:31 -06:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verbose_quiet_output_formats() {
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
|
|
struct FallbackToDisplay {}
|
|
|
|
impl std::fmt::Display for FallbackToDisplay {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
write!(f, "display")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl QuietDisplay for FallbackToDisplay {}
|
|
|
|
impl VerboseDisplay for FallbackToDisplay {}
|
|
|
|
|
|
|
|
let f = FallbackToDisplay {};
|
|
|
|
assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
|
|
|
|
assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "display");
|
|
|
|
assert_eq!(
|
|
|
|
&OutputFormat::DisplayVerbose.formatted_string(&f),
|
|
|
|
"display"
|
|
|
|
);
|
|
|
|
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
|
|
struct DiscreteVerbosityDisplay {}
|
|
|
|
impl std::fmt::Display for DiscreteVerbosityDisplay {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
write!(f, "display")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl QuietDisplay for DiscreteVerbosityDisplay {
|
|
|
|
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
|
|
|
write!(w, "quiet")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl VerboseDisplay for DiscreteVerbosityDisplay {
|
|
|
|
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
|
|
|
write!(w, "verbose")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let f = DiscreteVerbosityDisplay {};
|
|
|
|
assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
|
|
|
|
assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "quiet");
|
|
|
|
assert_eq!(
|
|
|
|
&OutputFormat::DisplayVerbose.formatted_string(&f),
|
|
|
|
"verbose"
|
|
|
|
);
|
|
|
|
}
|
2020-09-22 20:29:32 -06:00
|
|
|
}
|