diff --git a/Cargo.lock b/Cargo.lock index 34e7c42062..855adefc2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/cli/src/cli.rs b/cli/src/cli.rs index fae1c4492d..f11108a7ae 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -98,7 +98,7 @@ pub enum CliCommand { Fees, FirstAvailableBlock, GetBlock { - slot: Slot, + slot: Option, }, GetBlockTime { slot: Option, diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index cb2406f8de..63ce3098ab 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -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 { - 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, +) -> 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) ); diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index de42e5ec12..83be7431c1 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -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>, leader_schedule_cache: &Arc, subscriptions: &Arc, - rewards_recorder_sender: Option, 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, + rewards_recorder_sender: &Option, ) -> 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, leader_schedule_cache: &Arc, subscriptions: &Arc, - rewards_recorder_sender: Option, 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, subscriptions: &Arc, ) -> 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) { 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(), ); diff --git a/core/src/rewards_recorder_service.rs b/core/src/rewards_recorder_service.rs index e5deb0187a..ef0ad17958 100644 --- a/core/src/rewards_recorder_service.rs +++ b/core/src/rewards_recorder_service.rs @@ -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(); diff --git a/docs/src/apps/jsonrpc-api.md b/docs/src/apps/jsonrpc-api.md index 3169848eb3..f73921e471 100644 --- a/docs/src/apps/jsonrpc-api.md +++ b/docs/src/apps/jsonrpc-api.md @@ -342,6 +342,7 @@ The result field will be an object with the following fields: - `pubkey: ` - The public key, as base-58 encoded string, of the account that received the reward - `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" - `blockTime: ` - estimated production time, as Unix timestamp (seconds since the Unix epoch). null if not available #### Example: diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index da69bde607..034be27a1a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -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>, + /// Protocol-level rewards that were distributed by this bank + pub rewards: RwLock>, 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::() - ) - .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::() + ) + .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 { @@ -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::() + ); } #[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] diff --git a/storage-bigtable/proto/solana.bigtable.confirmed_block.rs b/storage-bigtable/proto/solana.bigtable.confirmed_block.rs index 961f25654f..ea9636c410 100644 --- a/storage-bigtable/proto/solana.bigtable.confirmed_block.rs +++ b/storage-bigtable/proto/solana.bigtable.confirmed_block.rs @@ -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, +} diff --git a/storage-bigtable/src/confirmed_block.proto b/storage-bigtable/src/confirmed_block.proto index 6ee9ed49ba..483d5d0cd4 100644 --- a/storage-bigtable/src/confirmed_block.proto +++ b/storage-bigtable/src/confirmed_block.proto @@ -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 { diff --git a/storage-bigtable/src/convert.rs b/storage-bigtable/src/convert.rs index 4f61b41eb2..542a5cbf68 100644 --- a/storage-bigtable/src/convert.rs +++ b/storage-bigtable/src/convert.rs @@ -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 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 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, + }, } } } diff --git a/storage-bigtable/src/lib.rs b/storage-bigtable/src/lib.rs index e492f444dc..0290fa5bfc 100644 --- a/storage-bigtable/src/lib.rs +++ b/storage-bigtable/src/lib.rs @@ -222,6 +222,7 @@ impl From for Reward { pubkey, lamports, post_balance: 0, + reward_type: None, } } } diff --git a/transaction-status/Cargo.toml b/transaction-status/Cargo.toml index a2450fcd89..340b986573 100644 --- a/transaction-status/Cargo.toml +++ b/transaction-status/Cargo.toml @@ -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"] } diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index cc1ece28aa..8581135ce5 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -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, } pub type Rewards = Vec;