From 4098af3b5bd69d51729f338b7aab0799bd2f699c Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Sat, 10 Jul 2021 23:18:42 -0700 Subject: [PATCH] Record vote account commission with voting/staking rewards and surface in RPC --- cli-output/src/cli_output.rs | 37 +++++++++++++++------- cli/src/stake.rs | 1 + client/src/rpc_response.rs | 5 +-- core/src/rewards_recorder_service.rs | 1 + docs/src/developing/clients/jsonrpc-api.md | 4 +++ ledger-tool/src/main.rs | 12 ++++--- ledger/benches/protobuf.rs | 1 + ledger/src/blockstore.rs | 2 ++ programs/stake/src/stake_state.rs | 5 ++- rpc/src/rpc.rs | 1 + rpc/src/transaction_status_service.rs | 1 + runtime/src/bank.rs | 32 ++++++++++++++++--- storage-bigtable/src/lib.rs | 1 + storage-proto/proto/confirmed_block.proto | 1 + storage-proto/src/convert.rs | 3 ++ storage-proto/src/lib.rs | 7 +++- transaction-status/src/lib.rs | 2 ++ 17 files changed, 90 insertions(+), 26 deletions(-) diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index dd50572dc5..368ca5bea2 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -771,6 +771,7 @@ pub struct CliEpochReward { pub post_balance: u64, // lamports pub percent_change: f64, pub apr: Option, + pub commission: Option, } #[derive(Serialize, Deserialize)] @@ -815,23 +816,27 @@ impl fmt::Display for CliKeyedEpochRewards { writeln!(f, "Epoch Rewards:")?; writeln!( f, - " {:<44} {:<18} {:<18} {:>14} {:>14}", - "Address", "Amount", "New Balance", "Percent Change", "APR" + " {:<44} {:<18} {:<18} {:>14} {:>14} {:>10}", + "Address", "Amount", "New Balance", "Percent Change", "APR", "Commission" )?; for keyed_reward in &self.rewards { match &keyed_reward.reward { Some(reward) => { writeln!( f, - " {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.2}% {}", + " {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>14} {:>10}", 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)) + .map(|apr| format!("{:.2}%", apr)) .unwrap_or_default(), + reward + .commission + .map(|commission| format!("{}%", commission)) + .unwrap_or_else(|| "-".to_string()) )?; } None => { @@ -948,13 +953,13 @@ fn show_epoch_rewards( writeln!(f, "Epoch Rewards:")?; writeln!( f, - " {:<6} {:<11} {:<18} {:<18} {:>14} {:>14}", - "Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR" + " {:<6} {:<11} {:<18} {:<18} {:>14} {:>14} {:>10}", + "Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR", "Commission" )?; for reward in epoch_rewards { writeln!( f, - " {:<6} {:<11} ◎{:<17.9} ◎{:<17.9} {:>13.2}% {}", + " {:<6} {:<11} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>14} {:>10}", reward.epoch, reward.effective_slot, lamports_to_sol(reward.amount), @@ -962,8 +967,12 @@ fn show_epoch_rewards( reward.percent_change, reward .apr - .map(|apr| format!("{:>13.2}%", apr)) + .map(|apr| format!("{:.2}%", apr)) .unwrap_or_default(), + reward + .commission + .map(|commission| format!("{}%", commission)) + .unwrap_or_else(|| "-".to_string()) )?; } } @@ -2178,8 +2187,8 @@ impl fmt::Display for CliBlock { writeln!(f, "Rewards:")?; writeln!( f, - " {:<44} {:^15} {:<15} {:<20} {:>14}", - "Address", "Type", "Amount", "New Balance", "Percent Change" + " {:<44} {:^15} {:<15} {:<20} {:>14} {:>10}", + "Address", "Type", "Amount", "New Balance", "Percent Change", "Commission" )?; for reward in rewards { let sign = if reward.lamports < 0 { "-" } else { "" }; @@ -2187,7 +2196,7 @@ impl fmt::Display for CliBlock { total_rewards += reward.lamports; writeln!( f, - " {:<44} {:^15} {:>15} {}", + " {:<44} {:^15} {:>15} {} {}", reward.pubkey, if let Some(reward_type) = reward.reward_type { format!("{}", reward_type) @@ -2209,7 +2218,11 @@ impl fmt::Display for CliBlock { / (reward.post_balance as f64 - reward.lamports as f64)) * 100.0 ) - } + }, + reward + .commission + .map(|commission| format!("{:>9}%", commission)) + .unwrap_or_else(|| " -".to_string()) )?; } diff --git a/cli/src/stake.rs b/cli/src/stake.rs index f2a074973f..2e5855f2aa 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -1832,6 +1832,7 @@ pub fn make_cli_reward( post_balance: reward.post_balance, percent_change: rate_change * 100.0, apr: Some(apr * 100.0), + commission: reward.commission, }) } else { None diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index a712739e60..ba18118fd8 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -394,8 +394,9 @@ pub struct RpcPerfSample { pub struct RpcInflationReward { pub epoch: Epoch, pub effective_slot: Slot, - pub amount: u64, // lamports - pub post_balance: u64, // lamports + pub amount: u64, // lamports + pub post_balance: u64, // lamports + pub commission: Option, // Vote account commission when the reward was credited } impl From for RpcConfirmedTransactionStatusWithSignature { diff --git a/core/src/rewards_recorder_service.rs b/core/src/rewards_recorder_service.rs index ef0ad17958..65c66fe49a 100644 --- a/core/src/rewards_recorder_service.rs +++ b/core/src/rewards_recorder_service.rs @@ -55,6 +55,7 @@ impl RewardsRecorderService { lamports: reward_info.lamports, post_balance: reward_info.post_balance, reward_type: Some(reward_info.reward_type), + commission: reward_info.commission, }) .collect(); diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index aa3f0649d3..1ee7d5ebe6 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -402,6 +402,7 @@ The result field will be an object with the following fields: - `lamports: `- number of reward lamports credited or debited by the account, as a i64 - `postBalance: ` - account balance in lamports after the reward was applied - `rewardType: ` - type of reward: "fee", "rent", "voting", "staking" + - `commission: ` - vote account commission when the reward was credited, only present for voting and staking rewards - `blockTime: ` - estimated production time, as Unix timestamp (seconds since the Unix epoch). null if not available - `blockHeight: ` - the number of blocks beneath this block @@ -1372,6 +1373,7 @@ The result field will be a JSON array with the following fields: - `effectiveSlot: `, the slot in which the rewards are effective - `amount: `, reward amount in lamports - `postBalance: `, post balance of the account in lamports +- `commission: ` - vote account commission when the reward was credited #### Example @@ -2820,6 +2822,7 @@ Returns transaction details for a confirmed transaction - `lamports: `- number of reward lamports credited or debited by the account, as a i64 - `postBalance: ` - account balance in lamports after the reward was applied - `rewardType: ` - type of reward: currently only "rent", other types may be added in the future + - `commission: ` - vote account commission when the reward was credited, only present for voting and staking rewards #### Example: @@ -4164,6 +4167,7 @@ The result field will be an object with the following fields: - `lamports: `- number of reward lamports credited or debited by the account, as a i64 - `postBalance: ` - account balance in lamports after the reward was applied - `rewardType: ` - type of reward: "fee", "rent", "voting", "staking" + - `commission: ` - vote account commission when the reward was credited, only present for voting and staking rewards - `blockTime: ` - estimated production time, as Unix timestamp (seconds since the Unix epoch). null if not available #### Example: diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 99fddebcb7..267f497cdd 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -79,14 +79,14 @@ fn output_slot_rewards(blockstore: &Blockstore, slot: Slot, method: &LedgerOutpu if !rewards.is_empty() { println!(" Rewards:"); println!( - " {:<44} {:^15} {:<15} {:<20}", - "Address", "Type", "Amount", "New Balance" + " {:<44} {:^15} {:<15} {:<20} {:>10}", + "Address", "Type", "Amount", "New Balance", "Commission", ); for reward in rewards { let sign = if reward.lamports < 0 { "-" } else { "" }; println!( - " {:<44} {:^15} {:<15} {}", + " {:<44} {:^15} {:<15} {} {}", reward.pubkey, if let Some(reward_type) = reward.reward_type { format!("{}", reward_type) @@ -98,7 +98,11 @@ fn output_slot_rewards(blockstore: &Blockstore, slot: Slot, method: &LedgerOutpu sign, lamports_to_sol(reward.lamports.abs() as u64) ), - format!("◎{:<18.9}", lamports_to_sol(reward.post_balance)) + format!("◎{:<18.9}", lamports_to_sol(reward.post_balance)), + reward + .commission + .map(|commission| format!("{:>9}%", commission)) + .unwrap_or_else(|| " -".to_string()) ); } } diff --git a/ledger/benches/protobuf.rs b/ledger/benches/protobuf.rs index 6b511b2925..e0244efa07 100644 --- a/ledger/benches/protobuf.rs +++ b/ledger/benches/protobuf.rs @@ -21,6 +21,7 @@ fn create_rewards() -> Rewards { lamports: 42 + i, post_balance: std::u64::MAX, reward_type: Some(RewardType::Fee), + commission: None, }) .collect() } diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 3bc4528c8d..8aa393e922 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -8346,6 +8346,7 @@ pub mod tests { lamports: 42 + i, post_balance: std::u64::MAX, reward_type: Some(RewardType::Fee), + commission: None, }) .collect(); let protobuf_rewards: generated::Rewards = rewards.into(); @@ -8412,6 +8413,7 @@ pub mod tests { lamports: -42, post_balance: 42, reward_type: Some(RewardType::Rent), + commission: None, }]), }; let deprecated_status: StoredTransactionStatusMeta = status.clone().into(); diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index a82a0f25fb..81c8aac52a 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -1029,14 +1029,13 @@ pub fn redeem_rewards( rewarded_epoch: Epoch, stake_account: &mut AccountSharedData, vote_account: &mut AccountSharedData, + vote_state: &VoteState, point_value: &PointValue, stake_history: Option<&StakeHistory>, inflation_point_calc_tracer: &mut Option, fix_stake_deactivate: bool, ) -> Result<(u64, u64), InstructionError> { if let StakeState::Stake(meta, mut stake) = stake_account.state()? { - let vote_state: VoteState = - StateMut::::state(vote_account)?.convert_to_current(); if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { inflation_point_calc_tracer( &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake.stake( @@ -1056,7 +1055,7 @@ pub fn redeem_rewards( if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards( &mut stake, point_value, - &vote_state, + vote_state, stake_history, inflation_point_calc_tracer, fix_stake_deactivate, diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 13d9917f3b..33c1e2904a 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -480,6 +480,7 @@ impl JsonRpcRequestProcessor { effective_slot: first_confirmed_block_in_epoch, amount: reward.lamports.abs() as u64, post_balance: reward.post_balance, + commission: reward.commission, }); } None diff --git a/rpc/src/transaction_status_service.rs b/rpc/src/transaction_status_service.rs index 24bb8c4d74..ce246961b5 100644 --- a/rpc/src/transaction_status_service.rs +++ b/rpc/src/transaction_status_service.rs @@ -136,6 +136,7 @@ impl TransactionStatusService { lamports: reward_info.lamports, post_balance: reward_info.post_balance, reward_type: Some(reward_info.reward_type), + commission: reward_info.commission, }) .collect(), ); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index ff93f12568..66d96cc548 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -71,6 +71,7 @@ use solana_sdk::{ create_account_shared_data_with_fields as create_account, from_account, Account, AccountSharedData, InheritableAccountFields, ReadableAccount, WritableAccount, }, + account_utils::StateMut, clock::{ BankId, Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, MAX_RECENT_BLOCKHASHES, @@ -107,7 +108,10 @@ use solana_sdk::{ transaction::{self, Result, Transaction, TransactionError}, }; use solana_stake_program::stake_state::{self, InflationPointCalculationEvent, PointValue}; -use solana_vote_program::vote_instruction::VoteInstruction; +use solana_vote_program::{ + vote_instruction::VoteInstruction, + vote_state::{VoteState, VoteStateVersions}, +}; use std::{ borrow::Cow, cell::RefCell, @@ -142,6 +146,7 @@ impl RentDebits { reward_type: RewardType::Rent, lamports: rent_debit, post_balance, + commission: None, // Not applicable }; self.0.push((*account, reward_info)); } else { @@ -830,8 +835,9 @@ pub trait DropCallback: fmt::Debug { #[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 + pub lamports: i64, // Reward amount + pub post_balance: u64, // Account balance in lamports after `lamports` was applied + pub commission: Option, // Vote account commission when the reward was credited, only present for voting and staking rewards } #[derive(Debug, Default)] @@ -2044,6 +2050,17 @@ impl Bank { 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(); + let vote_state: VoteState = match StateMut::::state(vote_account) { + Ok(vote_state) => vote_state.convert_to_current(), + Err(err) => { + debug!( + "failed to deserialize vote account {}: {}", + vote_pubkey, err + ); + continue; + } + }; + let commission = Some(vote_state.commission); for (stake_pubkey, stake_account) in stake_group.iter_mut() { // curry closure to add the contextual stake_pubkey @@ -2058,6 +2075,7 @@ impl Bank { rewarded_epoch, stake_account, vote_account, + &vote_state, &point_value, Some(&stake_history), &mut reward_calc_tracer.as_mut(), @@ -2074,6 +2092,7 @@ impl Bank { reward_type: RewardType::Staking, lamports: stakers_reward as i64, post_balance: stake_account.lamports(), + commission, }, )); } @@ -2095,6 +2114,7 @@ impl Bank { reward_type: RewardType::Voting, lamports, post_balance, + commission, }, )); } @@ -2206,6 +2226,7 @@ impl Bank { reward_type: RewardType::Fee, lamports: deposit as i64, post_balance, + commission: None, }, )); } @@ -3589,6 +3610,7 @@ impl Bank { reward_type: RewardType::Rent, lamports: rent_to_be_paid as i64, post_balance: account.lamports(), + commission: None, }, )); } @@ -5452,7 +5474,6 @@ pub(crate) mod tests { use crossbeam_channel::{bounded, unbounded}; use solana_sdk::{ account::Account, - account_utils::StateMut, clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT}, epoch_schedule::MINIMUM_SLOTS_PER_EPOCH, feature::Feature, @@ -7479,6 +7500,7 @@ pub(crate) mod tests { reward_type: RewardType::Staking, lamports: (rewards.validator_point_value * validator_points as f64) as i64, post_balance: bank1.get_balance(&stake_id), + commission: Some(0), } )] ); @@ -7980,6 +8002,7 @@ pub(crate) mod tests { reward_type: RewardType::Fee, lamports: expected_fee_collected as i64, post_balance: initial_balance + expected_fee_collected, + commission: None, } )] ); @@ -8015,6 +8038,7 @@ pub(crate) mod tests { reward_type: RewardType::Fee, lamports: expected_fee_collected as i64, post_balance: initial_balance + 2 * expected_fee_collected, + commission: None, } )] ); diff --git a/storage-bigtable/src/lib.rs b/storage-bigtable/src/lib.rs index fe99b994d0..d4e4c763d5 100644 --- a/storage-bigtable/src/lib.rs +++ b/storage-bigtable/src/lib.rs @@ -238,6 +238,7 @@ impl From for Reward { lamports, post_balance: 0, reward_type: None, + commission: None, } } } diff --git a/storage-proto/proto/confirmed_block.proto b/storage-proto/proto/confirmed_block.proto index 6e0d88f3cf..57f119febe 100644 --- a/storage-proto/proto/confirmed_block.proto +++ b/storage-proto/proto/confirmed_block.proto @@ -88,6 +88,7 @@ message Reward { int64 lamports = 2; uint64 post_balance = 3; RewardType reward_type = 4; + string commission = 5; } message Rewards { diff --git a/storage-proto/src/convert.rs b/storage-proto/src/convert.rs index 7a7e3d1b6e..a2304bec7b 100644 --- a/storage-proto/src/convert.rs +++ b/storage-proto/src/convert.rs @@ -89,6 +89,7 @@ impl From for generated::Reward { Some(RewardType::Staking) => generated::RewardType::Staking, Some(RewardType::Voting) => generated::RewardType::Voting, } as i32, + commission: reward.commission.map(|c| c.to_string()).unwrap_or_default(), } } } @@ -107,6 +108,7 @@ impl From for Reward { 4 => Some(RewardType::Voting), _ => None, }, + commission: reward.commission.parse::().ok(), } } } @@ -841,6 +843,7 @@ mod test { lamports: 123, post_balance: 321, reward_type: None, + commission: None, }; let gen_reward: generated::Reward = reward.clone().into(); assert_eq!(reward, gen_reward.into()); diff --git a/storage-proto/src/lib.rs b/storage-proto/src/lib.rs index 2a21b6b937..bcb876e65a 100644 --- a/storage-proto/src/lib.rs +++ b/storage-proto/src/lib.rs @@ -23,6 +23,8 @@ pub struct StoredExtendedReward { post_balance: u64, #[serde(deserialize_with = "default_on_eof")] reward_type: Option, + #[serde(deserialize_with = "default_on_eof")] + commission: Option, } impl From for Reward { @@ -32,12 +34,14 @@ impl From for Reward { lamports, post_balance, reward_type, + commission, } = value; Self { pubkey, lamports, post_balance, reward_type, + commission, } } } @@ -49,13 +53,14 @@ impl From for StoredExtendedReward { lamports, post_balance, reward_type, - .. + commission, } = value; Self { pubkey, lamports, post_balance, reward_type, + commission, } } } diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index 84a90d5317..c0126311af 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -339,6 +339,8 @@ pub struct Reward { pub lamports: i64, pub post_balance: u64, // Account balance in lamports after `lamports` was applied pub reward_type: Option, + #[serde(deserialize_with = "default_on_eof")] + pub commission: Option, // Vote account commission when the reward was credited, only present for voting and staking rewards } pub type Rewards = Vec;