Expose all rewards (fees, rent, voting and staking) in RPC getConfirmedBlock and the cli

This commit is contained in:
Michael Vines 2020-10-09 12:55:35 -07:00
parent 403790760c
commit c5c8da1ac0
13 changed files with 239 additions and 75 deletions

1
Cargo.lock generated
View File

@ -4659,6 +4659,7 @@ dependencies = [
"serde_derive",
"serde_json",
"solana-account-decoder",
"solana-runtime",
"solana-sdk 1.5.0",
"solana-stake-program",
"solana-vote-program",

View File

@ -98,7 +98,7 @@ pub enum CliCommand {
Fees,
FirstAvailableBlock,
GetBlock {
slot: Slot,
slot: Option<Slot>,
},
GetBlockTime {
slot: Option<Slot>,

View File

@ -73,8 +73,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.validator(is_slot)
.value_name("SLOT")
.takes_value(true)
.index(1)
.required(true),
.index(1),
),
)
.subcommand(
@ -363,7 +362,7 @@ pub fn parse_cluster_ping(
}
pub fn parse_get_block(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let slot = value_t_or_exit!(matches, "slot", Slot);
let slot = value_of(matches, "slot");
Ok(CliCommandInfo {
command: CliCommand::GetBlock { slot },
signers: vec![],
@ -700,7 +699,17 @@ pub fn process_leader_schedule(rpc_client: &RpcClient) -> ProcessResult {
Ok("".to_string())
}
pub fn process_get_block(rpc_client: &RpcClient, _config: &CliConfig, slot: Slot) -> ProcessResult {
pub fn process_get_block(
rpc_client: &RpcClient,
_config: &CliConfig,
slot: Option<Slot>,
) -> ProcessResult {
let slot = if let Some(slot) = slot {
slot
} else {
rpc_client.get_slot()?
};
let mut block =
rpc_client.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Base64)?;
@ -716,18 +725,23 @@ pub fn process_get_block(rpc_client: &RpcClient, _config: &CliConfig, slot: Slot
let mut total_rewards = 0;
println!("Rewards:",);
println!(
" {:<44} {:<15} {:<13} {:>14}",
"Address", "Amount", "New Balance", "Percent Change"
" {:<44} {:^15} {:<15} {:<20} {:>14}",
"Address", "Type", "Amount", "New Balance", "Percent Change"
);
for reward in block.rewards {
let sign = if reward.lamports < 0 { "-" } else { "" };
total_rewards += reward.lamports;
println!(
" {:<44} {:>15} {}",
" {:<44} {:^15} {:>15} {}",
reward.pubkey,
if let Some(reward_type) = reward.reward_type {
format!("{}", reward_type)
} else {
"-".to_string()
},
format!(
"{}◎{:<14.4}",
"{}◎{:<14.9}",
sign,
lamports_to_sol(reward.lamports.abs() as u64)
),
@ -735,7 +749,7 @@ pub fn process_get_block(rpc_client: &RpcClient, _config: &CliConfig, slot: Slot
" - -".to_string()
} else {
format!(
"◎{:<12.4} {:>13.9}%",
"◎{:<19.9} {:>13.9}%",
lamports_to_sol(reward.post_balance),
reward.lamports.abs() as f64
/ (reward.post_balance as f64 - reward.lamports as f64)
@ -746,7 +760,7 @@ pub fn process_get_block(rpc_client: &RpcClient, _config: &CliConfig, slot: Slot
let sign = if total_rewards < 0 { "-" } else { "" };
println!(
"Total Rewards: {}◎{:12.9}",
"Total Rewards: {}◎{:<12.9}",
sign,
lamports_to_sol(total_rewards.abs() as u64)
);

View File

@ -291,7 +291,6 @@ impl ReplayStage {
&bank_forks,
&leader_schedule_cache,
&subscriptions,
rewards_recorder_sender.clone(),
&mut progress,
&mut all_pubkeys,
);
@ -313,6 +312,7 @@ impl ReplayStage {
&mut heaviest_subtree_fork_choice,
&replay_vote_sender,
&bank_notification_sender,
&rewards_recorder_sender,
);
replay_active_banks_time.stop();
Self::report_memory(&allocated, "replay_active_banks", start);
@ -554,7 +554,6 @@ impl ReplayStage {
&poh_recorder,
&leader_schedule_cache,
&subscriptions,
rewards_recorder_sender.clone(),
&progress,
&retransmit_slots_sender,
&mut skipped_slots_info,
@ -854,7 +853,6 @@ impl ReplayStage {
poh_recorder: &Arc<Mutex<PohRecorder>>,
leader_schedule_cache: &Arc<LeaderScheduleCache>,
subscriptions: &Arc<RpcSubscriptions>,
rewards_recorder_sender: Option<RewardsRecorderSender>,
progress_map: &ProgressMap,
retransmit_slots_sender: &RetransmitSlotsSender,
skipped_slots_info: &mut SkippedSlotsInfo,
@ -953,7 +951,6 @@ impl ReplayStage {
poh_slot,
root_slot,
my_pubkey,
&rewards_recorder_sender,
subscriptions,
);
@ -1265,6 +1262,7 @@ impl ReplayStage {
heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice,
replay_vote_sender: &ReplayVoteSender,
bank_notification_sender: &Option<BankNotificationSender>,
rewards_recorder_sender: &Option<RewardsRecorderSender>,
) -> bool {
let mut did_complete_bank = false;
let mut tx_count = 0;
@ -1340,6 +1338,8 @@ impl ReplayStage {
.send(BankNotification::Frozen(bank.clone()))
.unwrap_or_else(|err| warn!("bank_notification_sender failed: {:?}", err));
}
Self::record_rewards(&bank, &rewards_recorder_sender);
} else {
trace!(
"bank {} not completed tick_height: {}, max_tick_height: {}",
@ -1817,7 +1817,6 @@ impl ReplayStage {
bank_forks: &RwLock<BankForks>,
leader_schedule_cache: &Arc<LeaderScheduleCache>,
subscriptions: &Arc<RpcSubscriptions>,
rewards_recorder_sender: Option<RewardsRecorderSender>,
progress: &mut ProgressMap,
all_pubkeys: &mut PubkeyReferences,
) {
@ -1863,7 +1862,6 @@ impl ReplayStage {
child_slot,
forks.root(),
&leader,
&rewards_recorder_sender,
subscriptions,
);
let empty: Vec<&Pubkey> = vec![];
@ -1891,21 +1889,18 @@ impl ReplayStage {
slot: u64,
root_slot: u64,
leader: &Pubkey,
rewards_recorder_sender: &Option<RewardsRecorderSender>,
subscriptions: &Arc<RpcSubscriptions>,
) -> Bank {
subscriptions.notify_slot(slot, parent.slot(), root_slot);
let child_bank = Bank::new_from_parent(parent, leader, slot);
Self::record_rewards(&child_bank, &rewards_recorder_sender);
child_bank
Bank::new_from_parent(parent, leader, slot)
}
fn record_rewards(bank: &Bank, rewards_recorder_sender: &Option<RewardsRecorderSender>) {
if let Some(rewards_recorder_sender) = rewards_recorder_sender {
if let Some(ref rewards) = bank.rewards {
let rewards = bank.rewards.read().unwrap();
if !rewards.is_empty() {
rewards_recorder_sender
.send((bank.slot(), rewards.iter().copied().collect()))
.send((bank.slot(), rewards.clone()))
.unwrap_or_else(|err| warn!("rewards_recorder_sender failed: {:?}", err));
}
}
@ -2155,7 +2150,6 @@ pub(crate) mod tests {
&bank_forks,
&leader_schedule_cache,
&rpc_subscriptions,
None,
&mut progress,
&mut PubkeyReferences::default(),
);
@ -2179,7 +2173,6 @@ pub(crate) mod tests {
&bank_forks,
&leader_schedule_cache,
&rpc_subscriptions,
None,
&mut progress,
&mut PubkeyReferences::default(),
);

View File

@ -54,6 +54,7 @@ impl RewardsRecorderService {
pubkey: pubkey.to_string(),
lamports: reward_info.lamports,
post_balance: reward_info.post_balance,
reward_type: Some(reward_info.reward_type),
})
.collect();

View File

@ -342,6 +342,7 @@ The result field will be an object with the following fields:
- `pubkey: <string>` - The public key, as base-58 encoded string, of the account that received the reward
- `lamports: <i64>`- number of reward lamports credited or debited by the account, as a i64
- `postBalance: <u64>` - account balance in lamports after the reward was applied
- `rewardType: <string|undefined>` - type of reward: "fee", "rent", "voting", "staking"
- `blockTime: <i64 | null>` - estimated production time, as Unix timestamp (seconds since the Unix epoch). null if not available
#### Example:

View File

@ -69,7 +69,7 @@ use std::{
cell::RefCell,
collections::{HashMap, HashSet},
convert::{TryFrom, TryInto},
mem,
fmt, mem,
ops::RangeInclusive,
path::PathBuf,
ptr,
@ -525,8 +525,32 @@ impl PartialEq for Bank {
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, AbiExample, Default, Clone, Copy)]
#[derive(Debug, PartialEq, Serialize, Deserialize, AbiExample, AbiEnumVisitor, Clone, Copy)]
pub enum RewardType {
Fee,
Rent,
Staking,
Voting,
}
impl fmt::Display for RewardType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
RewardType::Fee => "fee",
RewardType::Rent => "rent",
RewardType::Staking => "staking",
RewardType::Voting => "voting",
}
)
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, AbiExample, Clone, Copy)]
pub struct RewardInfo {
pub reward_type: RewardType,
pub lamports: i64, // Reward amount
pub post_balance: u64, // Account balance in lamports after `lamports` was applied
}
@ -613,7 +637,7 @@ pub struct Bank {
/// Track cluster signature throughput and adjust fee rate
fee_rate_governor: FeeRateGovernor,
/// Rent that have been collected
/// Rent that has been collected
collected_rent: AtomicU64,
/// latest rent collector, knows the epoch
@ -645,8 +669,8 @@ pub struct Bank {
/// Last time when the cluster info vote listener has synced with this bank
pub last_vote_sync: AtomicU64,
/// Rewards that were paid out immediately after this bank was created
pub rewards: Option<Vec<(Pubkey, RewardInfo)>>,
/// Protocol-level rewards that were distributed by this bank
pub rewards: RwLock<Vec<(Pubkey, RewardInfo)>>,
pub skip_drop: AtomicBool,
@ -780,7 +804,7 @@ impl Bank {
feature_builtins: parent.feature_builtins.clone(),
hard_forks: parent.hard_forks.clone(),
last_vote_sync: AtomicU64::new(parent.last_vote_sync.load(Relaxed)),
rewards: None,
rewards: RwLock::new(vec![]),
skip_drop: AtomicBool::new(false),
cluster_type: parent.cluster_type,
lazy_rent_collection: AtomicBool::new(parent.lazy_rent_collection.load(Relaxed)),
@ -1168,7 +1192,7 @@ impl Bank {
});
}
// update reward for previous epoch
// update rewards based on the previous epoch
fn update_rewards(&mut self, prev_epoch: Epoch) {
if prev_epoch == self.epoch() {
return;
@ -1216,18 +1240,23 @@ impl Bank {
let validator_rewards_paid =
self.stakes.read().unwrap().vote_balance_and_staked() - vote_balance_and_staked;
if let Some(rewards) = self.rewards.as_ref() {
assert_eq!(
validator_rewards_paid,
u64::try_from(
rewards
.iter()
.map(|(_pubkey, reward_info)| reward_info.lamports)
.sum::<i64>()
)
.unwrap()
);
}
assert_eq!(
validator_rewards_paid,
u64::try_from(
self.rewards
.read()
.unwrap()
.iter()
.map(|(_address, reward_info)| {
match reward_info.reward_type {
RewardType::Voting | RewardType::Staking => reward_info.lamports,
_ => 0,
}
})
.sum::<i64>()
)
.unwrap()
);
// verify that we didn't pay any more than we expected to
assert!(validator_rewards >= validator_rewards_paid);
@ -1318,11 +1347,12 @@ impl Bank {
let point_value = PointValue { rewards, points };
let mut rewards = HashMap::new();
let mut rewards = vec![];
// pay according to point value
for (vote_pubkey, (stake_group, vote_account)) in stake_delegation_accounts.iter_mut() {
let mut vote_account_changed = false;
let voters_account_pre_balance = vote_account.lamports;
for (stake_pubkey, stake_account) in stake_group.iter_mut() {
let redeemed = stake_state::redeem_rewards(
stake_account,
@ -1330,24 +1360,19 @@ impl Bank {
&point_value,
Some(&stake_history),
);
if let Ok((stakers_reward, voters_reward)) = redeemed {
if let Ok((stakers_reward, _voters_reward)) = redeemed {
self.store_account(&stake_pubkey, &stake_account);
vote_account_changed = true;
if voters_reward > 0 {
let reward_info = rewards
.entry(*vote_pubkey)
.or_insert_with(RewardInfo::default);
reward_info.lamports += voters_reward as i64;
reward_info.post_balance = vote_account.lamports;
}
if stakers_reward > 0 {
let reward_info = rewards
.entry(*stake_pubkey)
.or_insert_with(RewardInfo::default);
reward_info.lamports += stakers_reward as i64;
reward_info.post_balance = stake_account.lamports;
rewards.push((
*stake_pubkey,
RewardInfo {
reward_type: RewardType::Staking,
lamports: stakers_reward as i64,
post_balance: stake_account.lamports,
},
));
}
} else {
debug!(
@ -1358,12 +1383,23 @@ impl Bank {
}
if vote_account_changed {
let post_balance = vote_account.lamports;
let lamports = (post_balance - voters_account_pre_balance) as i64;
if lamports != 0 {
rewards.push((
*vote_pubkey,
RewardInfo {
reward_type: RewardType::Voting,
lamports,
post_balance,
},
));
}
self.store_account(&vote_pubkey, &vote_account);
}
}
self.rewards.write().unwrap().append(&mut rewards);
assert_eq!(self.rewards, None);
self.rewards = Some(rewards.drain().collect());
point_value.rewards as f64 / point_value.points as f64
}
@ -1446,7 +1482,16 @@ impl Bank {
"distributed fee: {} (rounded from: {}, burned: {})",
unburned, collector_fees, burned
);
self.deposit(&self.collector_id, unburned);
let post_balance = self.deposit(&self.collector_id, unburned);
self.rewards.write().unwrap().push((
self.collector_id,
RewardInfo {
reward_type: RewardType::Fee,
lamports: unburned as i64,
post_balance,
},
));
self.capitalization.fetch_sub(burned, Relaxed);
}
}
@ -2621,6 +2666,7 @@ impl Bank {
// holder
let mut leftover_lamports = rent_to_be_distributed - rent_distributed_in_initial_round;
let mut rewards = vec![];
validator_rent_shares
.into_iter()
.for_each(|(pubkey, rent_share)| {
@ -2633,7 +2679,16 @@ impl Bank {
let mut account = self.get_account(&pubkey).unwrap_or_default();
account.lamports += rent_to_be_paid;
self.store_account(&pubkey, &account);
rewards.push((
pubkey,
RewardInfo {
reward_type: RewardType::Rent,
lamports: rent_to_be_paid as i64,
post_balance: account.lamports,
},
));
});
self.rewards.write().unwrap().append(&mut rewards);
if enforce_fix {
assert_eq!(leftover_lamports, 0);
@ -3224,7 +3279,7 @@ impl Bank {
}
}
pub fn deposit(&self, pubkey: &Pubkey, lamports: u64) {
pub fn deposit(&self, pubkey: &Pubkey, lamports: u64) -> u64 {
let mut account = self.get_account(pubkey).unwrap_or_default();
let should_be_in_new_behavior = match self.cluster_type() {
@ -3248,6 +3303,7 @@ impl Bank {
account.lamports += lamports;
self.store_account(pubkey, &account);
account.lamports
}
pub fn accounts(&self) -> Arc<Accounts> {
@ -4688,8 +4744,26 @@ mod tests {
previous_capitalization - current_capitalization,
burned_portion
);
bank.freeze();
assert!(bank.calculate_and_verify_capitalization());
assert_eq!(
rent_to_be_distributed,
bank.rewards
.read()
.unwrap()
.iter()
.map(|(address, reward)| {
assert_eq!(reward.reward_type, RewardType::Rent);
if *address == validator_2_pubkey {
assert_eq!(reward.post_balance, validator_2_portion + 42 - tweak_2);
} else if *address == validator_3_pubkey {
assert_eq!(reward.post_balance, validator_3_portion + 42);
}
reward.lamports as u64
})
.sum::<u64>()
);
}
#[test]
@ -5676,7 +5750,7 @@ mod tests {
}
#[test]
fn test_bank_update_rewards() {
fn test_bank_update_vote_stake_rewards() {
solana_logger::setup();
// create a bank that ticks really slowly...
@ -5709,7 +5783,7 @@ mod tests {
bank.lazy_rent_collection.store(true, Relaxed);
assert_eq!(bank.capitalization(), 42 * 1_000_000_000);
assert_eq!(bank.rewards, None);
assert!(bank.rewards.read().unwrap().is_empty());
let ((vote_id, mut vote_account), (stake_id, stake_account)) =
crate::stakes::tests::create_staked_node_accounts(1_0000);
@ -5782,14 +5856,15 @@ mod tests {
// verify validator rewards show up in bank1.rewards vector
assert_eq!(
bank1.rewards,
Some(vec![(
*bank1.rewards.read().unwrap(),
vec![(
stake_id,
RewardInfo {
reward_type: RewardType::Staking,
lamports: (rewards.validator_point_value * validator_points as f64) as i64,
post_balance: bank1.get_balance(&stake_id),
}
)])
)]
);
bank1.freeze();
assert!(bank1.calculate_and_verify_capitalization());
@ -5826,7 +5901,7 @@ mod tests {
bank.lazy_rent_collection.store(true, Relaxed);
assert_eq!(bank.capitalization(), 42 * 1_000_000_000);
assert_eq!(bank.rewards, None);
assert!(bank.rewards.read().unwrap().is_empty());
let vote_id = Pubkey::new_rand();
let mut vote_account = vote_state::create_account(&vote_id, &Pubkey::new_rand(), 50, 100);
@ -5866,6 +5941,18 @@ mod tests {
bank1.freeze();
assert!(bank1.calculate_and_verify_capitalization());
// verify voting and staking rewards are recorded
let rewards = bank1.rewards.read().unwrap();
rewards
.iter()
.find(|(_address, reward)| reward.reward_type == RewardType::Voting)
.unwrap();
rewards
.iter()
.find(|(_address, reward)| reward.reward_type == RewardType::Staking)
.unwrap();
bank1.capitalization()
}
@ -6127,11 +6214,13 @@ mod tests {
// Test new account
let key = Keypair::new();
bank.deposit(&key.pubkey(), 10);
let new_balance = bank.deposit(&key.pubkey(), 10);
assert_eq!(new_balance, 10);
assert_eq!(bank.get_balance(&key.pubkey()), 10);
// Existing account
bank.deposit(&key.pubkey(), 3);
let new_balance = bank.deposit(&key.pubkey(), 3);
assert_eq!(new_balance, 13);
assert_eq!(bank.get_balance(&key.pubkey()), 13);
}
@ -6248,6 +6337,18 @@ mod tests {
// verify capitalization
assert_eq!(capitalization - expected_fee_burned, bank.capitalization());
assert_eq!(
*bank.rewards.read().unwrap(),
vec![(
leader,
RewardInfo {
reward_type: RewardType::Fee,
lamports: expected_fee_collected as i64,
post_balance: initial_balance + expected_fee_collected,
}
)]
);
// Verify that an InstructionError collects fees, too
let mut bank = Bank::new_from_parent(&Arc::new(bank), &leader, 1);
let mut tx =
@ -6270,6 +6371,18 @@ mod tests {
bank.get_balance(&leader),
initial_balance + 2 * expected_fee_collected
);
assert_eq!(
*bank.rewards.read().unwrap(),
vec![(
leader,
RewardInfo {
reward_type: RewardType::Fee,
lamports: expected_fee_collected as i64,
post_balance: initial_balance + 2 * expected_fee_collected,
}
)]
);
}
#[test]

View File

@ -91,9 +91,20 @@ pub struct Reward {
pub lamports: i64,
#[prost(uint64, tag = "3")]
pub post_balance: u64,
#[prost(enumeration = "RewardType", tag = "4")]
pub reward_type: i32,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct UnixTimestamp {
#[prost(int64, tag = "1")]
pub timestamp: i64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum RewardType {
Unspecified = 0,
Fee = 1,
Rent = 2,
Staking = 3,
Voting = 4,
}

View File

@ -57,10 +57,19 @@ message CompiledInstruction {
bytes data = 3;
}
enum RewardType {
Unspecified = 0;
Fee = 1;
Rent = 2;
Staking = 3;
Voting = 4;
}
message Reward {
string pubkey = 1;
int64 lamports = 2;
uint64 post_balance = 3;
RewardType reward_type = 4;
}
message UnixTimestamp {

View File

@ -7,7 +7,8 @@ use solana_sdk::{
transaction::Transaction,
};
use solana_transaction_status::{
ConfirmedBlock, InnerInstructions, Reward, TransactionStatusMeta, TransactionWithStatusMeta,
ConfirmedBlock, InnerInstructions, Reward, RewardType, TransactionStatusMeta,
TransactionWithStatusMeta,
};
use std::convert::{TryFrom, TryInto};
@ -24,6 +25,13 @@ impl From<Reward> for generated::Reward {
pubkey: reward.pubkey,
lamports: reward.lamports,
post_balance: reward.post_balance,
reward_type: match reward.reward_type {
None => generated::RewardType::Unspecified,
Some(RewardType::Fee) => generated::RewardType::Fee,
Some(RewardType::Rent) => generated::RewardType::Rent,
Some(RewardType::Staking) => generated::RewardType::Staking,
Some(RewardType::Voting) => generated::RewardType::Voting,
} as i32,
}
}
}
@ -34,6 +42,14 @@ impl From<generated::Reward> for Reward {
pubkey: reward.pubkey,
lamports: reward.lamports,
post_balance: reward.post_balance,
reward_type: match reward.reward_type {
0 => None,
1 => Some(RewardType::Fee),
2 => Some(RewardType::Rent),
3 => Some(RewardType::Voting),
4 => Some(RewardType::Staking),
_ => None,
},
}
}
}

View File

@ -222,6 +222,7 @@ impl From<StoredConfirmedBlockReward> for Reward {
pubkey,
lamports,
post_balance: 0,
reward_type: None,
}
}
}

View File

@ -19,6 +19,7 @@ serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "1.5.0" }
solana-sdk = { path = "../sdk", version = "1.5.0" }
solana-runtime = { path = "../runtime", version = "1.5.0" }
solana-stake-program = { path = "../programs/stake", version = "1.5.0" }
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
spl-memo-v1-0 = { package = "spl-memo", version = "=1.0.7", features = ["skip-no-mangle"] }

View File

@ -11,6 +11,7 @@ use crate::{
parse_accounts::{parse_accounts, ParsedAccount},
parse_instruction::{parse, ParsedInstruction},
};
pub use solana_runtime::bank::RewardType;
use solana_sdk::{
clock::{Slot, UnixTimestamp},
commitment_config::CommitmentConfig,
@ -241,6 +242,8 @@ pub struct Reward {
pub lamports: i64,
#[serde(deserialize_with = "default_on_eof")]
pub post_balance: u64, // Account balance in lamports after `lamports` was applied
#[serde(default, deserialize_with = "default_on_eof")]
pub reward_type: Option<RewardType>,
}
pub type Rewards = Vec<Reward>;