From f9ee97d6f5800f8637bbb1d6719a07990996acd7 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Mon, 27 Apr 2020 14:34:24 -0600 Subject: [PATCH] CLI: Improve stake (de)activation display --- cli/src/cli_output.rs | 150 +++++++++++++++++++++++------- cli/src/cluster_query.rs | 13 ++- cli/src/stake.rs | 101 ++++++++++++++------ programs/stake/src/stake_state.rs | 2 +- 4 files changed, 200 insertions(+), 66 deletions(-) diff --git a/cli/src/cli_output.rs b/cli/src/cli_output.rs index 72e23b8af3..9372246dbf 100644 --- a/cli/src/cli_output.rs +++ b/cli/src/cli_output.rs @@ -482,7 +482,7 @@ impl fmt::Display for CliKeyedStakeState { #[serde(rename_all = "camelCase")] pub struct CliStakeState { pub stake_type: CliStakeType, - pub total_stake: u64, + pub account_balance: u64, #[serde(skip_serializing_if = "Option::is_none")] pub delegated_stake: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -497,6 +497,16 @@ pub struct CliStakeState { pub lockup: Option, #[serde(skip_serializing)] pub use_lamports_unit: bool, + #[serde(skip_serializing)] + pub current_epoch: Epoch, + #[serde(skip_serializing_if = "Option::is_none")] + pub rent_exempt_reserve: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub active_stake: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub activating_stake: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub deactivating_stake: Option, } impl fmt::Display for CliStakeState { @@ -522,52 +532,122 @@ impl fmt::Display for CliStakeState { Ok(()) } + 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) + )?; + } + 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 { + 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(); 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 + "Delegated Stake: {}", + build_balance_message(delegated_stake, self.use_lamports_unit, true) )?; + 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")?; } show_authorized(f, self.authorized.as_ref().unwrap())?; show_lockup(f, self.lockup.as_ref().unwrap())?; diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index aa7d8d6e58..c6545453e5 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -29,7 +29,11 @@ use solana_sdk::{ native_token::lamports_to_sol, pubkey::{self, Pubkey}, system_instruction, system_program, - sysvar::{self, Sysvar}, + sysvar::{ + self, + stake_history::{self, StakeHistory}, + Sysvar, + }, transaction::Transaction, }; use std::{ @@ -1184,8 +1188,13 @@ pub fn process_show_stakes( let progress_bar = new_spinner_progress_bar(); progress_bar.set_message("Fetching stake accounts..."); let all_stake_accounts = rpc_client.get_program_accounts(&solana_stake_program::id())?; + let stake_history_account = rpc_client.get_account(&stake_history::id())?; progress_bar.finish_and_clear(); + let stake_history = StakeHistory::from_account(&stake_history_account).ok_or_else(|| { + CliError::RpcRequestError("Failed to deserialize stake history".to_string()) + })?; + let mut stake_accounts: Vec = vec![]; for (stake_pubkey, stake_account) in all_stake_accounts { if let Ok(stake_state) = stake_account.state() { @@ -1198,6 +1207,7 @@ pub fn process_show_stakes( stake_account.lamports, &stake_state, use_lamports_unit, + &stake_history, ), }); } @@ -1214,6 +1224,7 @@ pub fn process_show_stakes( stake_account.lamports, &stake_state, use_lamports_unit, + &stake_history, ), }); } diff --git a/cli/src/stake.rs b/cli/src/stake.rs index c61c9ac46a..e048eea668 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -1256,53 +1256,85 @@ pub fn process_stake_set_lockup( } } +fn u64_some_if_not_zero(n: u64) -> Option { + if n > 0 { + Some(n) + } else { + None + } +} + pub fn build_stake_state( - stake_lamports: u64, + account_balance: u64, stake_state: &StakeState, use_lamports_unit: bool, + stake_history: &StakeHistory, ) -> CliStakeState { match stake_state { StakeState::Stake( Meta { - authorized, lockup, .. + rent_exempt_reserve, + authorized, + lockup, }, stake, - ) => CliStakeState { - stake_type: CliStakeType::Stake, - total_stake: stake_lamports, - delegated_stake: Some(stake.delegation.stake), - delegated_vote_account_address: if stake.delegation.voter_pubkey != Pubkey::default() { - Some(stake.delegation.voter_pubkey.to_string()) - } else { - None - }, - activation_epoch: Some(if stake.delegation.activation_epoch < std::u64::MAX { - stake.delegation.activation_epoch - } else { - 0 - }), - deactivation_epoch: if stake.delegation.deactivation_epoch < std::u64::MAX { - Some(stake.delegation.deactivation_epoch) - } else { - None - }, - authorized: Some(authorized.into()), - lockup: Some(lockup.into()), - use_lamports_unit, - }, + ) => { + // The first entry in stake history is the previous epoch, so +1 for current + let current_epoch = stake_history.iter().next().unwrap().0 + 1; + let (active_stake, activating_stake, deactivating_stake) = stake + .delegation + .stake_activating_and_deactivating(current_epoch, Some(stake_history)); + CliStakeState { + stake_type: CliStakeType::Stake, + account_balance, + delegated_stake: Some(stake.delegation.stake), + delegated_vote_account_address: if stake.delegation.voter_pubkey + != Pubkey::default() + { + Some(stake.delegation.voter_pubkey.to_string()) + } else { + None + }, + activation_epoch: Some(if stake.delegation.activation_epoch < std::u64::MAX { + stake.delegation.activation_epoch + } else { + 0 + }), + deactivation_epoch: if stake.delegation.deactivation_epoch < std::u64::MAX { + Some(stake.delegation.deactivation_epoch) + } else { + None + }, + authorized: Some(authorized.into()), + lockup: Some(lockup.into()), + use_lamports_unit, + current_epoch, + rent_exempt_reserve: Some(*rent_exempt_reserve), + active_stake: u64_some_if_not_zero(active_stake), + activating_stake: u64_some_if_not_zero(activating_stake), + deactivating_stake: u64_some_if_not_zero(deactivating_stake), + } + } StakeState::RewardsPool => CliStakeState { stake_type: CliStakeType::RewardsPool, + account_balance, + ..CliStakeState::default() + }, + StakeState::Uninitialized => CliStakeState { + account_balance, ..CliStakeState::default() }, - StakeState::Uninitialized => CliStakeState::default(), StakeState::Initialized(Meta { - authorized, lockup, .. + rent_exempt_reserve, + authorized, + lockup, }) => CliStakeState { stake_type: CliStakeType::Initialized, - total_stake: stake_lamports, + account_balance, authorized: Some(authorized.into()), lockup: Some(lockup.into()), use_lamports_unit, + rent_exempt_reserve: Some(*rent_exempt_reserve), ..CliStakeState::default() }, } @@ -1324,7 +1356,18 @@ pub fn process_show_stake_account( } match stake_account.state() { Ok(stake_state) => { - let state = build_stake_state(stake_account.lamports, &stake_state, use_lamports_unit); + let stake_history_account = rpc_client.get_account(&stake_history::id())?; + let stake_history = + StakeHistory::from_account(&stake_history_account).ok_or_else(|| { + CliError::RpcRequestError("Failed to deserialize stake history".to_string()) + })?; + + let state = build_stake_state( + stake_account.lamports, + &stake_state, + use_lamports_unit, + &stake_history, + ); Ok(config.output_format.formatted_string(&state)) } Err(err) => Err(CliError::RpcRequestError(format!( diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index 31b0f9cc5a..bd5e75e92d 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -202,7 +202,7 @@ impl Delegation { } #[allow(clippy::comparison_chain)] - fn stake_activating_and_deactivating( + pub fn stake_activating_and_deactivating( &self, epoch: Epoch, history: Option<&StakeHistory>,