From f10f57e89fb7e7fc842971d599f83d16b0c65531 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 8 Apr 2021 19:05:59 +0000 Subject: [PATCH] Cli: use get_inflation_rewards and limit epochs queried (#16408) (#16443) * Fix block-with-limit when not finalized blocks found * Enable confirmed commitment in getInflationReward * Use get_inflation_rewards in cli * Line up rewards output * Add range validator * Change cli epoch arg -> num epochs * Add solana inflation rewards subcommand * Consolidate epoch rewards meta (cherry picked from commit bb9d2fd07abdd90b2cde376d4d2753301eb6a3e3) Co-authored-by: Tyera Eulberg --- clap-utils/src/input_validators.rs | 23 +++ cli-output/src/cli_output.rs | 74 +++++++++- cli/src/cli.rs | 6 + cli/src/inflation.rs | 105 +++++++++++++- cli/src/stake.rs | 215 +++++++++++++++-------------- cli/src/vote.rs | 41 ++++-- client/src/rpc_config.rs | 8 ++ core/src/rpc.rs | 7 +- 8 files changed, 353 insertions(+), 126 deletions(-) diff --git a/clap-utils/src/input_validators.rs b/clap-utils/src/input_validators.rs index dceaccacb4..bd577d5023 100644 --- a/clap-utils/src/input_validators.rs +++ b/clap-utils/src/input_validators.rs @@ -32,6 +32,29 @@ where is_parsable_generic::(string) } +// Return an error if string cannot be parsed as numeric type T, and value not within specified +// range +pub fn is_within_range(string: String, range_min: T, range_max: T) -> Result<(), String> +where + T: FromStr + Copy + std::fmt::Debug + PartialOrd + std::ops::Add + From, + T::Err: Display, +{ + match string.parse::() { + Ok(input) => { + let range = range_min..range_max + 1.into(); + if !range.contains(&input) { + Err(format!( + "input '{:?}' out of range ({:?}..{:?}]", + input, range_min, range_max + )) + } else { + Ok(()) + } + } + Err(err) => Err(format!("error parsing '{}': {}", string, err)), + } +} + // Return an error if a pubkey cannot be parsed. pub fn is_pubkey(string: T) -> Result<(), String> where diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index dff3c1c719..2147a44c41 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -603,6 +603,76 @@ pub struct CliEpochReward { pub apr: Option, } +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CliKeyedEpochReward { + pub address: String, + pub reward: Option, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CliEpochRewardshMetadata { + pub epoch: Epoch, + pub effective_slot: Slot, + pub block_time: UnixTimestamp, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CliKeyedEpochRewards { + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub epoch_metadata: Option, + pub rewards: Vec, +} + +impl QuietDisplay for CliKeyedEpochRewards {} +impl VerboseDisplay for CliKeyedEpochRewards {} + +impl fmt::Display for CliKeyedEpochRewards { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.rewards.is_empty() { + writeln!(f, "No rewards found in epoch")?; + return Ok(()); + } + + if let Some(metadata) = &self.epoch_metadata { + writeln!(f, "Epoch: {}", metadata.epoch)?; + writeln!(f, "Reward Slot: {}", metadata.effective_slot)?; + let timestamp = metadata.block_time; + writeln!(f, "Block Time: {}", unix_timestamp_to_string(timestamp))?; + } + writeln!(f, "Epoch Rewards:")?; + writeln!( + f, + " {:<44} {:<18} {:<18} {:>14} {:>14}", + "Address", "Amount", "New Balance", "Percent Change", "APR" + )?; + for keyed_reward in &self.rewards { + match &keyed_reward.reward { + Some(reward) => { + writeln!( + f, + " {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.2}% {}", + keyed_reward.address, + lamports_to_sol(reward.amount), + lamports_to_sol(reward.post_balance), + reward.percent_change, + reward + .apr + .map(|apr| format!("{:>13.2}%", apr)) + .unwrap_or_default(), + )?; + } + None => { + writeln!(f, " {:<44} No rewards in epoch", keyed_reward.address,)?; + } + } + } + Ok(()) + } +} + fn show_votes_and_credits( f: &mut fmt::Formatter, votes: &[CliLockout], @@ -654,13 +724,13 @@ fn show_epoch_rewards( writeln!(f, "Epoch Rewards:")?; writeln!( f, - " {:<6} {:<11} {:<16} {:<16} {:>14} {:>14}", + " {:<6} {:<11} {:<18} {:<18} {:>14} {:>14}", "Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR" )?; for reward in epoch_rewards { writeln!( f, - " {:<6} {:<11} ◎{:<16.9} ◎{:<14.9} {:>13.2}% {}", + " {:<6} {:<11} ◎{:<17.9} ◎{:<17.9} {:>13.2}% {}", reward.epoch, reward.effective_slot, lamports_to_sol(reward.amount), diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 3f7d53c2cd..d591a2adb6 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -260,6 +260,7 @@ pub enum CliCommand { ShowStakeAccount { pubkey: Pubkey, use_lamports_unit: bool, + with_rewards: Option, }, StakeAuthorize { stake_account_pubkey: Pubkey, @@ -315,6 +316,7 @@ pub enum CliCommand { ShowVoteAccount { pubkey: Pubkey, use_lamports_unit: bool, + with_rewards: Option, }, WithdrawFromVoteAccount { vote_account_pubkey: Pubkey, @@ -1594,11 +1596,13 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::ShowStakeAccount { pubkey: stake_account_pubkey, use_lamports_unit, + with_rewards, } => process_show_stake_account( &rpc_client, config, &stake_account_pubkey, *use_lamports_unit, + *with_rewards, ), CliCommand::ShowStakeHistory { use_lamports_unit } => { process_show_stake_history(&rpc_client, config, *use_lamports_unit) @@ -1719,11 +1723,13 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::ShowVoteAccount { pubkey: vote_account_pubkey, use_lamports_unit, + with_rewards, } => process_show_vote_account( &rpc_client, config, &vote_account_pubkey, *use_lamports_unit, + *with_rewards, ), CliCommand::WithdrawFromVoteAccount { vote_account_pubkey, diff --git a/cli/src/inflation.rs b/cli/src/inflation.rs index 8267bc0ca7..11d3fbfb52 100644 --- a/cli/src/inflation.rs +++ b/cli/src/inflation.rs @@ -1,14 +1,22 @@ use crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult}; -use clap::{App, ArgMatches, SubCommand}; -use solana_clap_utils::keypair::*; -use solana_cli_output::CliInflation; +use clap::{App, Arg, ArgMatches, SubCommand}; +use solana_clap_utils::{ + input_parsers::{pubkeys_of, value_of}, + input_validators::is_valid_pubkey, + keypair::*, +}; +use solana_cli_output::{ + CliEpochRewardshMetadata, CliInflation, CliKeyedEpochReward, CliKeyedEpochRewards, +}; use solana_client::rpc_client::RpcClient; use solana_remote_wallet::remote_wallet::RemoteWalletManager; +use solana_sdk::{clock::Epoch, pubkey::Pubkey}; use std::sync::Arc; #[derive(Debug, PartialEq)] pub enum InflationCliCommand { Show, + Rewards(Vec, Option), } pub trait InflationSubCommands { @@ -17,17 +25,47 @@ pub trait InflationSubCommands { impl InflationSubCommands for App<'_, '_> { fn inflation_subcommands(self) -> Self { - self.subcommand(SubCommand::with_name("inflation").about("Show inflation information")) + self.subcommand( + SubCommand::with_name("inflation") + .about("Show inflation information") + .subcommand( + SubCommand::with_name("rewards") + .about("Show inflation rewards for a set of addresses") + .arg(pubkey!( + Arg::with_name("addresses") + .value_name("ADDRESS") + .index(1) + .multiple(true) + .required(true), + "Address of account to query for rewards. " + )) + .arg( + Arg::with_name("rewards_epoch") + .long("rewards-epoch") + .takes_value(true) + .value_name("EPOCH") + .help("Display rewards for specific epoch [default: latest epoch]"), + ), + ), + ) } } pub fn parse_inflation_subcommand( - _matches: &ArgMatches<'_>, + matches: &ArgMatches<'_>, _default_signer: &DefaultSigner, _wallet_manager: &mut Option>, ) -> Result { + let command = match matches.subcommand() { + ("rewards", Some(matches)) => { + let addresses = pubkeys_of(matches, "addresses").unwrap(); + let rewards_epoch = value_of(matches, "rewards_epoch"); + InflationCliCommand::Rewards(addresses, rewards_epoch) + } + _ => InflationCliCommand::Show, + }; Ok(CliCommandInfo { - command: CliCommand::Inflation(InflationCliCommand::Show), + command: CliCommand::Inflation(command), signers: vec![], }) } @@ -37,8 +75,15 @@ pub fn process_inflation_subcommand( config: &CliConfig, inflation_subcommand: &InflationCliCommand, ) -> ProcessResult { - assert_eq!(*inflation_subcommand, InflationCliCommand::Show); + match inflation_subcommand { + InflationCliCommand::Show => process_show(rpc_client, config), + InflationCliCommand::Rewards(ref addresses, rewards_epoch) => { + process_rewards(rpc_client, config, addresses, *rewards_epoch) + } + } +} +fn process_show(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult { let governor = rpc_client.get_inflation_governor()?; let current_rate = rpc_client.get_inflation_rate()?; @@ -49,3 +94,49 @@ pub fn process_inflation_subcommand( Ok(config.output_format.formatted_string(&inflation)) } + +fn process_rewards( + rpc_client: &RpcClient, + config: &CliConfig, + addresses: &[Pubkey], + rewards_epoch: Option, +) -> ProcessResult { + let rewards = rpc_client + .get_inflation_reward(&addresses, rewards_epoch) + .map_err(|err| { + if let Some(epoch) = rewards_epoch { + format!("Rewards not available for epoch {}", epoch) + } else { + format!("Rewards not available {}", err) + } + })?; + let epoch_schedule = rpc_client.get_epoch_schedule()?; + + let mut epoch_rewards: Vec = vec![]; + let epoch_metadata = if let Some(Some(first_reward)) = rewards.iter().find(|&v| v.is_some()) { + let (epoch_start_time, epoch_end_time) = + crate::stake::get_epoch_boundary_timestamps(rpc_client, first_reward, &epoch_schedule)?; + for (reward, address) in rewards.iter().zip(addresses) { + let cli_reward = reward.as_ref().and_then(|reward| { + crate::stake::make_cli_reward(reward, epoch_start_time, epoch_end_time) + }); + epoch_rewards.push(CliKeyedEpochReward { + address: address.to_string(), + reward: cli_reward, + }); + } + let block_time = rpc_client.get_block_time(first_reward.effective_slot)?; + Some(CliEpochRewardshMetadata { + epoch: first_reward.epoch, + effective_slot: first_reward.effective_slot, + block_time, + }) + } else { + None + }; + let cli_rewards = CliKeyedEpochRewards { + epoch_metadata, + rewards: epoch_rewards, + }; + Ok(config.output_format.formatted_string(&cli_rewards)) +} diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 1540770950..ac0a51697f 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -7,7 +7,6 @@ use crate::{ nonce::check_nonce_account, spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount}, }; -use chrono::{Local, TimeZone}; use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand}; use solana_clap_utils::{ fee_payer::{fee_payer_arg, FEE_PAYER_ARG}, @@ -23,19 +22,15 @@ use solana_cli_output::{ CliStakeState, CliStakeType, ReturnSignersConfig, }; use solana_client::{ - blockhash_query::BlockhashQuery, - client_error::{ClientError, ClientErrorKind}, - nonce_utils, - rpc_client::RpcClient, - rpc_config::RpcConfirmedBlockConfig, - rpc_custom_error, - rpc_request::{self, DELINQUENT_VALIDATOR_SLOT_DISTANCE}, + blockhash_query::BlockhashQuery, nonce_utils, rpc_client::RpcClient, + rpc_request::DELINQUENT_VALIDATOR_SLOT_DISTANCE, rpc_response::RpcInflationReward, }; use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ account::from_account, account_utils::StateMut, - clock::{Clock, Epoch, Slot, UnixTimestamp, SECONDS_PER_DAY}, + clock::{Clock, UnixTimestamp, SECONDS_PER_DAY}, + epoch_schedule::EpochSchedule, feature, feature_set, message::Message, pubkey::Pubkey, @@ -51,7 +46,7 @@ use solana_stake_program::{ stake_state::{Authorized, Lockup, Meta, StakeAuthorize, StakeState}, }; use solana_vote_program::vote_state::VoteState; -use std::{convert::TryInto, ops::Deref, sync::Arc}; +use std::{ops::Deref, sync::Arc}; pub const STAKE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant { name: "stake_authority", @@ -413,6 +408,22 @@ impl StakeSubCommands for App<'_, '_> { .long("lamports") .takes_value(false) .help("Display balance in lamports instead of SOL") + ) + .arg( + Arg::with_name("with_rewards") + .long("with-rewards") + .takes_value(false) + .help("Display inflation rewards"), + ) + .arg( + Arg::with_name("num_rewards_epochs") + .long("num-rewards-epochs") + .takes_value(true) + .value_name("NUM") + .validator(|s| is_within_range(s, 1, 10)) + .default_value_if("with_rewards", None, "1") + .requires("with_rewards") + .help("Display rewards for NUM recent epochs, max 10 [default: latest epoch only]"), ), ) .subcommand( @@ -850,10 +861,16 @@ pub fn parse_show_stake_account( let stake_account_pubkey = pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap(); let use_lamports_unit = matches.is_present("lamports"); + let with_rewards = if matches.is_present("with_rewards") { + Some(value_of(matches, "num_rewards_epochs").unwrap()) + } else { + None + }; Ok(CliCommandInfo { command: CliCommand::ShowStakeAccount { pubkey: stake_account_pubkey, use_lamports_unit, + with_rewards, }, signers: vec![], }) @@ -1650,104 +1667,85 @@ pub fn build_stake_state( } } +pub fn get_epoch_boundary_timestamps( + rpc_client: &RpcClient, + reward: &RpcInflationReward, + epoch_schedule: &EpochSchedule, +) -> Result<(UnixTimestamp, UnixTimestamp), Box> { + let epoch_end_time = rpc_client.get_block_time(reward.effective_slot)?; + let mut epoch_start_slot = epoch_schedule.get_first_slot_in_epoch(reward.epoch); + let epoch_start_time = loop { + if epoch_start_slot >= reward.effective_slot { + return Err("epoch_start_time not found".to_string().into()); + } + match rpc_client.get_block_time(epoch_start_slot) { + Ok(block_time) => { + break block_time; + } + Err(_) => { + epoch_start_slot += 1; + } + } + }; + Ok((epoch_start_time, epoch_end_time)) +} + +pub fn make_cli_reward( + reward: &RpcInflationReward, + epoch_start_time: UnixTimestamp, + epoch_end_time: UnixTimestamp, +) -> Option { + let wallclock_epoch_duration = epoch_end_time.checked_sub(epoch_start_time)?; + if reward.post_balance > reward.amount { + let rate_change = reward.amount as f64 / (reward.post_balance - reward.amount) as f64; + + let wallclock_epochs_per_year = + (SECONDS_PER_DAY * 356) as f64 / wallclock_epoch_duration as f64; + let apr = rate_change * wallclock_epochs_per_year; + + Some(CliEpochReward { + epoch: reward.epoch, + effective_slot: reward.effective_slot, + amount: reward.amount, + post_balance: reward.post_balance, + percent_change: rate_change * 100.0, + apr: Some(apr * 100.0), + }) + } else { + None + } +} + pub(crate) fn fetch_epoch_rewards( rpc_client: &RpcClient, address: &Pubkey, - lowest_epoch: Epoch, + mut num_epochs: usize, ) -> Result, Box> { let mut all_epoch_rewards = vec![]; - let epoch_schedule = rpc_client.get_epoch_schedule()?; - let slot = rpc_client.get_slot()?; - let first_available_block = rpc_client.get_first_available_block()?; + let mut rewards_epoch = rpc_client.get_epoch_info()?.epoch; - let mut epoch = epoch_schedule.get_epoch_and_slot_index(slot).0; - let mut epoch_info: Option<(Slot, UnixTimestamp, solana_transaction_status::Rewards)> = None; - while epoch > lowest_epoch { - let first_slot_in_epoch = epoch_schedule.get_first_slot_in_epoch(epoch); - if first_slot_in_epoch < first_available_block { - // RPC node is out of history data - break; - } - - let first_confirmed_block_in_epoch = *rpc_client - .get_confirmed_blocks_with_limit(first_slot_in_epoch, 1)? - .get(0) - .ok_or_else(|| format!("Unable to fetch first confirmed block for epoch {}", epoch))?; - - let first_confirmed_block = match rpc_client.get_confirmed_block_with_config( - first_confirmed_block_in_epoch, - RpcConfirmedBlockConfig::rewards_only(), - ) { - Ok(first_confirmed_block) => first_confirmed_block, - Err(ClientError { - kind: - ClientErrorKind::RpcError(rpc_request::RpcError::RpcResponseError { - code: rpc_custom_error::JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE, - .. - }), - .. - }) => { - // RPC node doesn't have this block - break; - } - Err(err) => { - return Err(err.into()); - } - }; - - let epoch_start_time = if let Some(block_time) = first_confirmed_block.block_time { - block_time - } else { - break; - }; - - // Rewards for the previous epoch are found in the first confirmed block of the current epoch - let previous_epoch_rewards = first_confirmed_block.rewards.unwrap_or_default(); - - if let Some((effective_slot, epoch_end_time, epoch_rewards)) = epoch_info { - let wallclock_epoch_duration = if epoch_end_time > epoch_start_time { - Some( - { Local.timestamp(epoch_end_time, 0) - Local.timestamp(epoch_start_time, 0) } - .to_std()? - .as_secs_f64(), - ) - } else { - None - }; - - if let Some(reward) = epoch_rewards - .into_iter() - .find(|reward| reward.pubkey == address.to_string()) - { - if reward.post_balance > reward.lamports.try_into().unwrap_or(0) { - let rate_change = reward.lamports.abs() as f64 - / (reward.post_balance as f64 - reward.lamports as f64); - - let apr = wallclock_epoch_duration.map(|wallclock_epoch_duration| { - let wallclock_epochs_per_year = - (SECONDS_PER_DAY * 356) as f64 / wallclock_epoch_duration; - rate_change * wallclock_epochs_per_year - }); - - all_epoch_rewards.push(CliEpochReward { - epoch, - effective_slot, - amount: reward.lamports.abs() as u64, - post_balance: reward.post_balance, - percent_change: rate_change * 100.0, - apr: apr.map(|r| r * 100.0), - }); + let mut process_reward = + |reward: &Option| -> Result<(), Box> { + if let Some(reward) = reward { + let (epoch_start_time, epoch_end_time) = + get_epoch_boundary_timestamps(rpc_client, reward, &epoch_schedule)?; + if let Some(cli_reward) = make_cli_reward(reward, epoch_start_time, epoch_end_time) + { + all_epoch_rewards.push(cli_reward); } } - } + Ok(()) + }; - epoch -= 1; - epoch_info = Some(( - first_confirmed_block_in_epoch, - epoch_start_time, - previous_epoch_rewards, - )); + while num_epochs > 0 && rewards_epoch > 0 { + rewards_epoch = rewards_epoch.saturating_sub(1); + if let Ok(rewards) = rpc_client.get_inflation_reward(&[*address], Some(rewards_epoch)) { + process_reward(&rewards[0])?; + } else { + eprintln!("Rewards not available for epoch {}", rewards_epoch); + } + num_epochs = num_epochs.saturating_sub(1); } Ok(all_epoch_rewards) @@ -1758,6 +1756,7 @@ pub fn process_show_stake_account( config: &CliConfig, stake_account_address: &Pubkey, use_lamports_unit: bool, + with_rewards: Option, ) -> ProcessResult { let stake_account = rpc_client.get_account(stake_account_address)?; if stake_account.owner != solana_stake_program::id() { @@ -1787,15 +1786,17 @@ pub fn process_show_stake_account( is_stake_program_v2_enabled(rpc_client)?, // At v1.6, this check can be removed and simply passed as `true` ); - if state.stake_type == CliStakeType::Stake { - if let Some(activation_epoch) = state.activation_epoch { - let rewards = - fetch_epoch_rewards(rpc_client, stake_account_address, activation_epoch); - match rewards { - Ok(rewards) => state.epoch_rewards = Some(rewards), - Err(error) => eprintln!("Failed to fetch epoch rewards: {:?}", error), - }; - } + if state.stake_type == CliStakeType::Stake && state.activation_epoch.is_some() { + let epoch_rewards = with_rewards.and_then(|num_epochs| { + match fetch_epoch_rewards(rpc_client, stake_account_address, num_epochs) { + Ok(rewards) => Some(rewards), + Err(error) => { + eprintln!("Failed to fetch epoch rewards: {:?}", error); + None + } + } + }); + state.epoch_rewards = epoch_rewards; } Ok(config.output_format.formatted_string(&state)) } diff --git a/cli/src/vote.rs b/cli/src/vote.rs index 5328f6cf48..6ff9291011 100644 --- a/cli/src/vote.rs +++ b/cli/src/vote.rs @@ -207,6 +207,22 @@ impl VoteSubCommands for App<'_, '_> { .long("lamports") .takes_value(false) .help("Display balance in lamports instead of SOL"), + ) + .arg( + Arg::with_name("with_rewards") + .long("with-rewards") + .takes_value(false) + .help("Display inflation rewards"), + ) + .arg( + Arg::with_name("num_rewards_epochs") + .long("num-rewards-epochs") + .takes_value(true) + .value_name("NUM") + .validator(|s| is_within_range(s, 1, 10)) + .default_value_if("with_rewards", None, "1") + .requires("with_rewards") + .help("Display rewards for NUM recent epochs, max 10 [default: latest epoch only]"), ), ) .subcommand( @@ -373,10 +389,16 @@ pub fn parse_vote_get_account_command( let vote_account_pubkey = pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap(); let use_lamports_unit = matches.is_present("lamports"); + let with_rewards = if matches.is_present("with_rewards") { + Some(value_of(matches, "num_rewards_epochs").unwrap()) + } else { + None + }; Ok(CliCommandInfo { command: CliCommand::ShowVoteAccount { pubkey: vote_account_pubkey, use_lamports_unit, + with_rewards, }, signers: vec![], }) @@ -647,6 +669,7 @@ pub fn process_show_vote_account( config: &CliConfig, vote_account_address: &Pubkey, use_lamports_unit: bool, + with_rewards: Option, ) -> ProcessResult { let (vote_account, vote_state) = get_vote_account(rpc_client, vote_account_address, config.commitment)?; @@ -672,14 +695,16 @@ pub fn process_show_vote_account( } } - let epoch_rewards = match crate::stake::fetch_epoch_rewards(rpc_client, vote_account_address, 1) - { - Ok(rewards) => Some(rewards), - Err(error) => { - eprintln!("Failed to fetch epoch rewards: {:?}", error); - None - } - }; + let epoch_rewards = + with_rewards.and_then(|num_epochs| { + match crate::stake::fetch_epoch_rewards(rpc_client, vote_account_address, num_epochs) { + Ok(rewards) => Some(rewards), + Err(error) => { + eprintln!("Failed to fetch epoch rewards: {:?}", error); + None + } + } + }); let vote_account_data = CliVoteAccount { account_balance: vote_account.lamports, diff --git a/client/src/rpc_config.rs b/client/src/rpc_config.rs index da8435d4d4..e2e1d145a3 100644 --- a/client/src/rpc_config.rs +++ b/client/src/rpc_config.rs @@ -157,6 +157,14 @@ impl RpcConfirmedBlockConfig { ..Self::default() } } + + pub fn rewards_with_commitment(commitment: Option) -> Self { + Self { + transaction_details: Some(TransactionDetails::None), + commitment, + ..Self::default() + } + } } impl From for RpcEncodingConfigWrapper { diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 643586ff8d..df33f1d602 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -424,7 +424,7 @@ impl JsonRpcRequestProcessor { let first_confirmed_block = if let Ok(Some(first_confirmed_block)) = self .get_confirmed_block( first_confirmed_block_in_epoch, - Some(RpcConfirmedBlockConfig::rewards_only().into()), + Some(RpcConfirmedBlockConfig::rewards_with_commitment(config.commitment).into()), ) { first_confirmed_block } else { @@ -1025,7 +1025,10 @@ impl JsonRpcRequestProcessor { // Maybe add confirmed blocks if commitment.is_confirmed() && blocks.len() < limit { - let last_element = blocks.last().cloned().unwrap_or_default(); + let last_element = blocks + .last() + .cloned() + .unwrap_or_else(|| start_slot.saturating_sub(1)); let confirmed_bank = self.bank(Some(CommitmentConfig::confirmed())); let mut confirmed_blocks = confirmed_bank .status_cache_ancestors()