From 352bff0092f7e31758880be39fcfde6bb53bad39 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Fri, 29 Mar 2024 11:26:12 -0600 Subject: [PATCH 1/5] Add bank::partitioned_epoch_rewards submodule --- runtime/src/bank.rs | 1 + runtime/src/bank/partitioned_epoch_rewards/mod.rs | 1 + 2 files changed, 2 insertions(+) create mode 100644 runtime/src/bank/partitioned_epoch_rewards/mod.rs diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index f9a785eb9b5a57..2a46020a770c9a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -220,6 +220,7 @@ pub mod builtins; pub mod epoch_accounts_hash_utils; mod fee_distribution; mod metrics; +mod partitioned_epoch_rewards; mod serde_snapshot; mod sysvar_cache; #[cfg(test)] diff --git a/runtime/src/bank/partitioned_epoch_rewards/mod.rs b/runtime/src/bank/partitioned_epoch_rewards/mod.rs new file mode 100644 index 00000000000000..c215b7d2955318 --- /dev/null +++ b/runtime/src/bank/partitioned_epoch_rewards/mod.rs @@ -0,0 +1 @@ +use super::Bank; From f59b7b021a27a04414c8aaacaa943d4cf58abc0d Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Fri, 29 Mar 2024 12:07:02 -0600 Subject: [PATCH 2/5] Move helper structs and types to submodule --- runtime/src/bank.rs | 89 ++---------------- .../src/bank/partitioned_epoch_rewards/mod.rs | 92 ++++++++++++++++++- runtime/src/bank/serde_snapshot.rs | 4 +- runtime/src/epoch_rewards_hasher.rs | 2 +- runtime/src/serde_snapshot/newer.rs | 2 +- 5 files changed, 103 insertions(+), 86 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 2a46020a770c9a..0e9389f545d79c 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -41,6 +41,12 @@ use { bank::{ builtins::{BuiltinPrototype, BUILTINS}, metrics::*, + partitioned_epoch_rewards::{ + CalculateRewardsAndDistributeVoteRewardsResult, EpochRewardCalculateParamInfo, + EpochRewardStatus, PartitionedRewardsCalculation, RewardInterval, + StakeRewardCalculationPartitioned, StakeRewards, StartBlockHeightAndRewards, + VoteRewardsAccounts, + }, }, bank_forks::BankForks, epoch_rewards_hasher::hash_rewards_into_partitions, @@ -173,7 +179,7 @@ use { }, }, solana_system_program::{get_system_account_kind, SystemAccountKind}, - solana_vote::vote_account::{VoteAccount, VoteAccounts, VoteAccountsHashMap}, + solana_vote::vote_account::{VoteAccount, VoteAccountsHashMap}, solana_vote_program::vote_state::VoteState, std::{ borrow::Cow, @@ -220,7 +226,7 @@ pub mod builtins; pub mod epoch_accounts_hash_utils; mod fee_distribution; mod metrics; -mod partitioned_epoch_rewards; +pub(crate) mod partitioned_epoch_rewards; mod serde_snapshot; mod sysvar_cache; #[cfg(test)] @@ -643,27 +649,6 @@ impl AbiExample for OptionalDropCallback { } } -#[derive(AbiExample, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub(crate) struct StartBlockHeightAndRewards { - /// the block height of the slot at which rewards distribution began - pub(crate) start_block_height: u64, - /// calculated epoch rewards pending distribution, outer Vec is by partition (one partition per block) - pub(crate) stake_rewards_by_partition: Arc>, -} - -/// Represent whether bank is in the reward phase or not. -#[derive(AbiExample, AbiEnumVisitor, Debug, Clone, PartialEq, Serialize, Deserialize, Default)] -pub(crate) enum EpochRewardStatus { - /// this bank is in the reward phase. - /// Contents are the start point for epoch reward calculation, - /// i.e. parent_slot and parent_block height for the starting - /// block of the current epoch. - Active(StartBlockHeightAndRewards), - /// this bank is outside of the rewarding phase. - #[default] - Inactive, -} - /// Manager for the state of all accounts and programs after processing its entries. /// AbiExample is needed even without Serialize/Deserialize; actual (de-)serialization /// are implemented elsewhere for versioning @@ -866,56 +851,6 @@ struct VoteReward { } type VoteRewards = DashMap; -#[derive(Debug, Default)] -struct VoteRewardsAccounts { - /// reward info for each vote account pubkey. - /// This type is used by `update_reward_history()` - rewards: Vec<(Pubkey, RewardInfo)>, - /// corresponds to pubkey in `rewards` - /// Some if account is to be stored. - /// None if to be skipped. - accounts_to_store: Vec>, -} - -/// hold reward calc info to avoid recalculation across functions -struct EpochRewardCalculateParamInfo<'a> { - stake_history: StakeHistory, - stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount)>, - cached_vote_accounts: &'a VoteAccounts, -} - -/// Hold all results from calculating the rewards for partitioned distribution. -/// This struct exists so we can have a function which does all the calculation with no -/// side effects. -struct PartitionedRewardsCalculation { - vote_account_rewards: VoteRewardsAccounts, - stake_rewards_by_partition: StakeRewardCalculationPartitioned, - old_vote_balance_and_staked: u64, - validator_rewards: u64, - validator_rate: f64, - foundation_rate: f64, - prev_epoch_duration_in_years: f64, - capitalization: u64, -} - -/// result of calculating the stake rewards at beginning of new epoch -struct StakeRewardCalculationPartitioned { - /// each individual stake account to reward, grouped by partition - stake_rewards_by_partition: Vec, - /// total lamports across all `stake_rewards` - total_stake_rewards_lamports: u64, -} - -struct CalculateRewardsAndDistributeVoteRewardsResult { - /// total rewards for the epoch (including both vote rewards and stake rewards) - total_rewards: u64, - /// distributed vote rewards - distributed_rewards: u64, - /// stake rewards that still need to be distributed, grouped by partition - stake_rewards_by_partition: Vec, -} - -pub(crate) type StakeRewards = Vec; #[derive(Debug, Default)] pub struct NewBankOptions { @@ -951,14 +886,6 @@ struct StakeRewardCalculation { total_stake_rewards_lamports: u64, } -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub(super) enum RewardInterval { - /// the slot within the epoch is INSIDE the reward distribution interval - InsideInterval, - /// the slot within the epoch is OUTSIDE the reward distribution interval - OutsideInterval, -} - impl Bank { fn default_with_accounts(accounts: Accounts) -> Self { let mut bank = Self { diff --git a/runtime/src/bank/partitioned_epoch_rewards/mod.rs b/runtime/src/bank/partitioned_epoch_rewards/mod.rs index c215b7d2955318..626e86fe825d80 100644 --- a/runtime/src/bank/partitioned_epoch_rewards/mod.rs +++ b/runtime/src/bank/partitioned_epoch_rewards/mod.rs @@ -1 +1,91 @@ -use super::Bank; +use { + super::Bank, + crate::{stake_account::StakeAccount, stake_history::StakeHistory}, + solana_accounts_db::stake_rewards::StakeReward, + solana_sdk::{ + account::AccountSharedData, pubkey::Pubkey, reward_info::RewardInfo, + stake::state::Delegation, + }, + solana_vote::vote_account::VoteAccounts, + std::sync::Arc, +}; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub(super) enum RewardInterval { + /// the slot within the epoch is INSIDE the reward distribution interval + InsideInterval, + /// the slot within the epoch is OUTSIDE the reward distribution interval + OutsideInterval, +} + +#[derive(AbiExample, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub(crate) struct StartBlockHeightAndRewards { + /// the block height of the slot at which rewards distribution began + pub(crate) start_block_height: u64, + /// calculated epoch rewards pending distribution, outer Vec is by partition (one partition per block) + pub(crate) stake_rewards_by_partition: Arc>, +} + +/// Represent whether bank is in the reward phase or not. +#[derive(AbiExample, AbiEnumVisitor, Debug, Clone, PartialEq, Serialize, Deserialize, Default)] +pub(crate) enum EpochRewardStatus { + /// this bank is in the reward phase. + /// Contents are the start point for epoch reward calculation, + /// i.e. parent_slot and parent_block height for the starting + /// block of the current epoch. + Active(StartBlockHeightAndRewards), + /// this bank is outside of the rewarding phase. + #[default] + Inactive, +} + +#[derive(Debug, Default)] +pub(super) struct VoteRewardsAccounts { + /// reward info for each vote account pubkey. + /// This type is used by `update_reward_history()` + pub(super) rewards: Vec<(Pubkey, RewardInfo)>, + /// corresponds to pubkey in `rewards` + /// Some if account is to be stored. + /// None if to be skipped. + pub(super) accounts_to_store: Vec>, +} + +/// hold reward calc info to avoid recalculation across functions +pub(super) struct EpochRewardCalculateParamInfo<'a> { + pub(super) stake_history: StakeHistory, + pub(super) stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount)>, + pub(super) cached_vote_accounts: &'a VoteAccounts, +} + +/// Hold all results from calculating the rewards for partitioned distribution. +/// This struct exists so we can have a function which does all the calculation with no +/// side effects. +pub(super) struct PartitionedRewardsCalculation { + pub(super) vote_account_rewards: VoteRewardsAccounts, + pub(super) stake_rewards_by_partition: StakeRewardCalculationPartitioned, + pub(super) old_vote_balance_and_staked: u64, + pub(super) validator_rewards: u64, + pub(super) validator_rate: f64, + pub(super) foundation_rate: f64, + pub(super) prev_epoch_duration_in_years: f64, + pub(super) capitalization: u64, +} + +/// result of calculating the stake rewards at beginning of new epoch +pub(super) struct StakeRewardCalculationPartitioned { + /// each individual stake account to reward, grouped by partition + pub(super) stake_rewards_by_partition: Vec, + /// total lamports across all `stake_rewards` + pub(super) total_stake_rewards_lamports: u64, +} + +pub(super) struct CalculateRewardsAndDistributeVoteRewardsResult { + /// total rewards for the epoch (including both vote rewards and stake rewards) + pub(super) total_rewards: u64, + /// distributed vote rewards + pub(super) distributed_rewards: u64, + /// stake rewards that still need to be distributed, grouped by partition + pub(super) stake_rewards_by_partition: Vec, +} + +pub(crate) type StakeRewards = Vec; diff --git a/runtime/src/bank/serde_snapshot.rs b/runtime/src/bank/serde_snapshot.rs index 372baec2e4aee0..45cc4d8362bfc0 100644 --- a/runtime/src/bank/serde_snapshot.rs +++ b/runtime/src/bank/serde_snapshot.rs @@ -3,8 +3,8 @@ mod tests { use { crate::{ bank::{ - epoch_accounts_hash_utils, test_utils as bank_test_utils, Bank, EpochRewardStatus, - StartBlockHeightAndRewards, + epoch_accounts_hash_utils, partitioned_epoch_rewards::StartBlockHeightAndRewards, + test_utils as bank_test_utils, Bank, EpochRewardStatus, }, genesis_utils::activate_all_features, serde_snapshot::{ diff --git a/runtime/src/epoch_rewards_hasher.rs b/runtime/src/epoch_rewards_hasher.rs index b594b05a5cfe3b..ddf45a9095a3e8 100644 --- a/runtime/src/epoch_rewards_hasher.rs +++ b/runtime/src/epoch_rewards_hasher.rs @@ -1,5 +1,5 @@ use { - crate::bank::StakeRewards, + crate::bank::partitioned_epoch_rewards::StakeRewards, solana_sdk::{epoch_rewards_hasher::EpochRewardsHasher, hash::Hash}, }; diff --git a/runtime/src/serde_snapshot/newer.rs b/runtime/src/serde_snapshot/newer.rs index 004e6e61d54868..d9c73d04422199 100644 --- a/runtime/src/serde_snapshot/newer.rs +++ b/runtime/src/serde_snapshot/newer.rs @@ -5,7 +5,7 @@ use { *, }, crate::{ - bank::EpochRewardStatus, + bank::partitioned_epoch_rewards::EpochRewardStatus, stakes::{serde_stakes_enum_compat, StakesEnum}, }, solana_accounts_db::{accounts_hash::AccountsHash, ancestors::AncestorsForSerialization}, From 19d4fc403396860613bfee3fa77933c1d0dc5633 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Fri, 29 Mar 2024 15:11:02 -0600 Subject: [PATCH 3/5] Move partitioned-rewards-specific Bank methods to submodule --- runtime/src/bank.rs | 90 +---------------- .../src/bank/partitioned_epoch_rewards/mod.rs | 96 ++++++++++++++++++- 2 files changed, 94 insertions(+), 92 deletions(-) diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 0e9389f545d79c..47a63b9fc98962 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -44,8 +44,7 @@ use { partitioned_epoch_rewards::{ CalculateRewardsAndDistributeVoteRewardsResult, EpochRewardCalculateParamInfo, EpochRewardStatus, PartitionedRewardsCalculation, RewardInterval, - StakeRewardCalculationPartitioned, StakeRewards, StartBlockHeightAndRewards, - VoteRewardsAccounts, + StakeRewardCalculationPartitioned, StakeRewards, VoteRewardsAccounts, }, }, bank_forks::BankForks, @@ -89,7 +88,6 @@ use { ancestors::{Ancestors, AncestorsForSerialization}, blockhash_queue::BlockhashQueue, epoch_accounts_hash::EpochAccountsHash, - partitioned_rewards::PartitionedEpochRewardsConfig, sorted_storages::SortedStorages, stake_rewards::StakeReward, storable_accounts::StorableAccounts, @@ -1072,76 +1070,6 @@ impl Bank { rent_collector.clone_with_epoch(epoch) } - fn is_partitioned_rewards_feature_enabled(&self) -> bool { - self.feature_set - .is_active(&feature_set::enable_partitioned_epoch_reward::id()) - } - - pub(crate) fn set_epoch_reward_status_active( - &mut self, - stake_rewards_by_partition: Vec, - ) { - self.epoch_reward_status = EpochRewardStatus::Active(StartBlockHeightAndRewards { - start_block_height: self.block_height, - stake_rewards_by_partition: Arc::new(stake_rewards_by_partition), - }); - } - - fn partitioned_epoch_rewards_config(&self) -> &PartitionedEpochRewardsConfig { - &self - .rc - .accounts - .accounts_db - .partitioned_epoch_rewards_config - } - - /// # stake accounts to store in one block during partitioned reward interval - fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 { - self.partitioned_epoch_rewards_config() - .stake_account_stores_per_block - } - - /// reward calculation happens synchronously during the first block of the epoch boundary. - /// So, # blocks for reward calculation is 1. - fn get_reward_calculation_num_blocks(&self) -> Slot { - self.partitioned_epoch_rewards_config() - .reward_calculation_num_blocks - } - - /// Calculate the number of blocks required to distribute rewards to all stake accounts. - fn get_reward_distribution_num_blocks(&self, rewards: &StakeRewards) -> u64 { - let total_stake_accounts = rewards.len(); - if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() { - 1 - } else { - const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10; - let num_chunks = solana_accounts_db::accounts_hash::AccountsHasher::div_ceil( - total_stake_accounts, - self.partitioned_rewards_stake_account_stores_per_block() as usize, - ) as u64; - - // Limit the reward credit interval to 10% of the total number of slots in a epoch - num_chunks.clamp( - 1, - (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1), - ) - } - } - - /// Return `RewardInterval` enum for current bank - fn get_reward_interval(&self) -> RewardInterval { - if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) { - RewardInterval::InsideInterval - } else { - RewardInterval::OutsideInterval - } - } - - /// For testing only - pub fn force_reward_interval_end_for_tests(&mut self) { - self.epoch_reward_status = EpochRewardStatus::Inactive; - } - fn _new_from_parent( parent: Arc, collector_id: &Pubkey, @@ -1570,13 +1498,6 @@ impl Bank { } } - fn force_partition_rewards_in_first_block_of_epoch(&self) -> bool { - self.partitioned_epoch_rewards_config() - .test_enable_partitioned_rewards - && self.get_reward_calculation_num_blocks() == 0 - && self.partitioned_rewards_stake_account_stores_per_block() == u64::MAX - } - /// Begin the process of calculating and distributing rewards. /// This process can take multiple slots. fn begin_partitioned_rewards( @@ -3519,15 +3440,6 @@ impl Bank { report_partitioned_reward_metrics(self, metrics); } - /// true if it is ok to run partitioned rewards code. - /// This means the feature is activated or certain testing situations. - fn is_partitioned_rewards_code_enabled(&self) -> bool { - self.is_partitioned_rewards_feature_enabled() - || self - .partitioned_epoch_rewards_config() - .test_enable_partitioned_rewards - } - /// Helper fn to log epoch_rewards sysvar fn log_epoch_rewards_sysvar(&self, prefix: &str) { if let Some(account) = self.get_account(&sysvar::epoch_rewards::id()) { diff --git a/runtime/src/bank/partitioned_epoch_rewards/mod.rs b/runtime/src/bank/partitioned_epoch_rewards/mod.rs index 626e86fe825d80..4585e5db548612 100644 --- a/runtime/src/bank/partitioned_epoch_rewards/mod.rs +++ b/runtime/src/bank/partitioned_epoch_rewards/mod.rs @@ -1,10 +1,12 @@ use { super::Bank, crate::{stake_account::StakeAccount, stake_history::StakeHistory}, - solana_accounts_db::stake_rewards::StakeReward, + solana_accounts_db::{ + partitioned_rewards::PartitionedEpochRewardsConfig, stake_rewards::StakeReward, + }, solana_sdk::{ - account::AccountSharedData, pubkey::Pubkey, reward_info::RewardInfo, - stake::state::Delegation, + account::AccountSharedData, clock::Slot, feature_set, pubkey::Pubkey, + reward_info::RewardInfo, stake::state::Delegation, }, solana_vote::vote_account::VoteAccounts, std::sync::Arc, @@ -89,3 +91,91 @@ pub(super) struct CalculateRewardsAndDistributeVoteRewardsResult { } pub(crate) type StakeRewards = Vec; + +impl Bank { + pub(super) fn is_partitioned_rewards_feature_enabled(&self) -> bool { + self.feature_set + .is_active(&feature_set::enable_partitioned_epoch_reward::id()) + } + + pub(crate) fn set_epoch_reward_status_active( + &mut self, + stake_rewards_by_partition: Vec, + ) { + self.epoch_reward_status = EpochRewardStatus::Active(StartBlockHeightAndRewards { + start_block_height: self.block_height, + stake_rewards_by_partition: Arc::new(stake_rewards_by_partition), + }); + } + + pub(super) fn partitioned_epoch_rewards_config(&self) -> &PartitionedEpochRewardsConfig { + &self + .rc + .accounts + .accounts_db + .partitioned_epoch_rewards_config + } + + /// # stake accounts to store in one block during partitioned reward interval + pub(super) fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 { + self.partitioned_epoch_rewards_config() + .stake_account_stores_per_block + } + + /// reward calculation happens synchronously during the first block of the epoch boundary. + /// So, # blocks for reward calculation is 1. + pub(super) fn get_reward_calculation_num_blocks(&self) -> Slot { + self.partitioned_epoch_rewards_config() + .reward_calculation_num_blocks + } + + /// Calculate the number of blocks required to distribute rewards to all stake accounts. + pub(super) fn get_reward_distribution_num_blocks(&self, rewards: &StakeRewards) -> u64 { + let total_stake_accounts = rewards.len(); + if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() { + 1 + } else { + const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10; + let num_chunks = solana_accounts_db::accounts_hash::AccountsHasher::div_ceil( + total_stake_accounts, + self.partitioned_rewards_stake_account_stores_per_block() as usize, + ) as u64; + + // Limit the reward credit interval to 10% of the total number of slots in a epoch + num_chunks.clamp( + 1, + (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1), + ) + } + } + + /// Return `RewardInterval` enum for current bank + pub(super) fn get_reward_interval(&self) -> RewardInterval { + if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) { + RewardInterval::InsideInterval + } else { + RewardInterval::OutsideInterval + } + } + + /// true if it is ok to run partitioned rewards code. + /// This means the feature is activated or certain testing situations. + pub(super) fn is_partitioned_rewards_code_enabled(&self) -> bool { + self.is_partitioned_rewards_feature_enabled() + || self + .partitioned_epoch_rewards_config() + .test_enable_partitioned_rewards + } + + /// For testing only + pub fn force_reward_interval_end_for_tests(&mut self) { + self.epoch_reward_status = EpochRewardStatus::Inactive; + } + + pub(super) fn force_partition_rewards_in_first_block_of_epoch(&self) -> bool { + self.partitioned_epoch_rewards_config() + .test_enable_partitioned_rewards + && self.get_reward_calculation_num_blocks() == 0 + && self.partitioned_rewards_stake_account_stores_per_block() == u64::MAX + } +} From 42d03820ec9084ea798b0773ca60c35f26a128cc Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Fri, 29 Mar 2024 15:11:20 -0600 Subject: [PATCH 4/5] Move unit tests into submodule --- .../src/bank/partitioned_epoch_rewards/mod.rs | 188 ++++++++++++++++++ runtime/src/bank/tests.rs | 169 +--------------- 2 files changed, 189 insertions(+), 168 deletions(-) diff --git a/runtime/src/bank/partitioned_epoch_rewards/mod.rs b/runtime/src/bank/partitioned_epoch_rewards/mod.rs index 4585e5db548612..21f4fdcabf79ed 100644 --- a/runtime/src/bank/partitioned_epoch_rewards/mod.rs +++ b/runtime/src/bank/partitioned_epoch_rewards/mod.rs @@ -179,3 +179,191 @@ impl Bank { && self.partitioned_rewards_stake_account_stores_per_block() == u64::MAX } } + +#[cfg(test)] +mod tests { + use { + super::*, + crate::bank::tests::create_genesis_config, + solana_accounts_db::{ + accounts_db::{ + AccountShrinkThreshold, AccountsDbConfig, ACCOUNTS_DB_CONFIG_FOR_TESTING, + }, + accounts_index::AccountSecondaryIndexes, + partitioned_rewards::TestPartitionedEpochRewards, + }, + solana_program_runtime::runtime_config::RuntimeConfig, + solana_sdk::{epoch_schedule::EpochSchedule, native_token::LAMPORTS_PER_SOL}, + }; + + impl Bank { + /// Return the total number of blocks in reward interval (including both calculation and crediting). + pub(in crate::bank) fn get_reward_total_num_blocks(&self, rewards: &StakeRewards) -> u64 { + self.get_reward_calculation_num_blocks() + + self.get_reward_distribution_num_blocks(rewards) + } + } + + #[test] + fn test_force_reward_interval_end() { + let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); + let mut bank = Bank::new_for_tests(&genesis_config); + + let expected_num = 100; + + let stake_rewards = (0..expected_num) + .map(|_| StakeReward::new_random()) + .collect::>(); + + bank.set_epoch_reward_status_active(vec![stake_rewards]); + assert!(bank.get_reward_interval() == RewardInterval::InsideInterval); + + bank.force_reward_interval_end_for_tests(); + assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval); + } + + #[test] + fn test_is_partitioned_reward_feature_enable() { + let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); + + let mut bank = Bank::new_for_tests(&genesis_config); + assert!(!bank.is_partitioned_rewards_feature_enabled()); + bank.activate_feature(&feature_set::enable_partitioned_epoch_reward::id()); + assert!(bank.is_partitioned_rewards_feature_enabled()); + } + + #[test] + fn test_deactivate_epoch_reward_status() { + let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); + let mut bank = Bank::new_for_tests(&genesis_config); + + let expected_num = 100; + + let stake_rewards = (0..expected_num) + .map(|_| StakeReward::new_random()) + .collect::>(); + + bank.set_epoch_reward_status_active(vec![stake_rewards]); + + assert!(bank.get_reward_interval() == RewardInterval::InsideInterval); + bank.deactivate_epoch_reward_status(); + assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval); + } + + /// Test get_reward_distribution_num_blocks, get_reward_calculation_num_blocks, get_reward_total_num_blocks during small epoch + /// The num_credit_blocks should be cap to 10% of the total number of blocks in the epoch. + #[test] + fn test_get_reward_distribution_num_blocks_cap() { + let (mut genesis_config, _mint_keypair) = + create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); + genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false); + + // Config stake reward distribution to be 10 per block + let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone(); + accounts_db_config.test_partitioned_epoch_rewards = + TestPartitionedEpochRewards::PartitionedEpochRewardsConfigRewardBlocks { + reward_calculation_num_blocks: 1, + stake_account_stores_per_block: 10, + }; + + let bank = Bank::new_with_paths( + &genesis_config, + Arc::new(RuntimeConfig::default()), + Vec::new(), + None, + None, + AccountSecondaryIndexes::default(), + AccountShrinkThreshold::default(), + false, + Some(accounts_db_config), + None, + Some(Pubkey::new_unique()), + Arc::default(), + ); + + let stake_account_stores_per_block = + bank.partitioned_rewards_stake_account_stores_per_block(); + assert_eq!(stake_account_stores_per_block, 10); + + let check_num_reward_distribution_blocks = + |num_stakes: u64, + expected_num_reward_distribution_blocks: u64, + expected_num_reward_computation_blocks: u64| { + // Given the short epoch, i.e. 32 slots, we should cap the number of reward distribution blocks to 32/10 = 3. + let stake_rewards = (0..num_stakes) + .map(|_| StakeReward::new_random()) + .collect::>(); + + assert_eq!( + bank.get_reward_distribution_num_blocks(&stake_rewards), + expected_num_reward_distribution_blocks + ); + assert_eq!( + bank.get_reward_calculation_num_blocks(), + expected_num_reward_computation_blocks + ); + assert_eq!( + bank.get_reward_total_num_blocks(&stake_rewards), + bank.get_reward_distribution_num_blocks(&stake_rewards) + + bank.get_reward_calculation_num_blocks(), + ); + }; + + for test_record in [ + // num_stakes, expected_num_reward_distribution_blocks, expected_num_reward_computation_blocks + (0, 1, 1), + (1, 1, 1), + (stake_account_stores_per_block, 1, 1), + (2 * stake_account_stores_per_block - 1, 2, 1), + (2 * stake_account_stores_per_block, 2, 1), + (3 * stake_account_stores_per_block - 1, 3, 1), + (3 * stake_account_stores_per_block, 3, 1), + (4 * stake_account_stores_per_block, 3, 1), // cap at 3 + (5 * stake_account_stores_per_block, 3, 1), //cap at 3 + ] { + check_num_reward_distribution_blocks(test_record.0, test_record.1, test_record.2); + } + } + + /// Test get_reward_distribution_num_blocks, get_reward_calculation_num_blocks, get_reward_total_num_blocks during normal epoch gives the expected result + #[test] + fn test_get_reward_distribution_num_blocks_normal() { + solana_logger::setup(); + let (mut genesis_config, _mint_keypair) = + create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); + genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false); + + let bank = Bank::new_for_tests(&genesis_config); + + // Given 8k rewards, it will take 2 blocks to credit all the rewards + let expected_num = 8192; + let stake_rewards = (0..expected_num) + .map(|_| StakeReward::new_random()) + .collect::>(); + + assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2); + assert_eq!(bank.get_reward_calculation_num_blocks(), 1); + assert_eq!( + bank.get_reward_total_num_blocks(&stake_rewards), + bank.get_reward_distribution_num_blocks(&stake_rewards) + + bank.get_reward_calculation_num_blocks(), + ); + } + + /// Test get_reward_distribution_num_blocks, get_reward_calculation_num_blocks, get_reward_total_num_blocks during warm up epoch gives the expected result. + /// The num_credit_blocks should be 1 during warm up epoch. + #[test] + fn test_get_reward_distribution_num_blocks_warmup() { + let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); + + let bank = Bank::new_for_tests(&genesis_config); + let rewards = vec![]; + assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1); + assert_eq!(bank.get_reward_calculation_num_blocks(), 1); + assert_eq!( + bank.get_reward_total_num_blocks(&rewards), + bank.get_reward_distribution_num_blocks(&rewards) + + bank.get_reward_calculation_num_blocks(), + ); + } +} diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index dc3a3121558ea3..0b9f2757deba74 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -191,7 +191,7 @@ fn create_genesis_config_no_tx_fee(lamports: u64) -> (GenesisConfig, Keypair) { (genesis_config, mint_keypair) } -fn create_genesis_config(lamports: u64) -> (GenesisConfig, Keypair) { +pub(in crate::bank) fn create_genesis_config(lamports: u64) -> (GenesisConfig, Keypair) { solana_sdk::genesis_config::create_genesis_config(lamports) } @@ -12225,34 +12225,6 @@ fn test_rewards_point_calculation_empty() { assert!(point_value.is_none()); } -#[test] -fn test_force_reward_interval_end() { - let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); - let mut bank = Bank::new_for_tests(&genesis_config); - - let expected_num = 100; - - let stake_rewards = (0..expected_num) - .map(|_| StakeReward::new_random()) - .collect::>(); - - bank.set_epoch_reward_status_active(vec![stake_rewards]); - assert!(bank.get_reward_interval() == RewardInterval::InsideInterval); - - bank.force_reward_interval_end_for_tests(); - assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval); -} - -#[test] -fn test_is_partitioned_reward_feature_enable() { - let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); - - let mut bank = Bank::new_for_tests(&genesis_config); - assert!(!bank.is_partitioned_rewards_feature_enabled()); - bank.activate_feature(&feature_set::enable_partitioned_epoch_reward::id()); - assert!(bank.is_partitioned_rewards_feature_enabled()); -} - /// Test that reward partition range panics when passing out of range partition index #[test] #[should_panic(expected = "index out of bounds: the len is 10 but the index is 15")] @@ -12276,24 +12248,6 @@ fn test_get_stake_rewards_partition_range_panic() { let _range = &stake_rewards_bucket[15]; } -#[test] -fn test_deactivate_epoch_reward_status() { - let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); - let mut bank = Bank::new_for_tests(&genesis_config); - - let expected_num = 100; - - let stake_rewards = (0..expected_num) - .map(|_| StakeReward::new_random()) - .collect::>(); - - bank.set_epoch_reward_status_active(vec![stake_rewards]); - - assert!(bank.get_reward_interval() == RewardInterval::InsideInterval); - bank.deactivate_epoch_reward_status(); - assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval); -} - #[test] fn test_distribute_partitioned_epoch_rewards() { let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); @@ -13286,127 +13240,6 @@ fn test_calc_vote_accounts_to_store_normal() { } } -impl Bank { - /// Return the total number of blocks in reward interval (including both calculation and crediting). - fn get_reward_total_num_blocks(&self, rewards: &StakeRewards) -> u64 { - self.get_reward_calculation_num_blocks() + self.get_reward_distribution_num_blocks(rewards) - } -} - -/// Test get_reward_distribution_num_blocks, get_reward_calculation_num_blocks, get_reward_total_num_blocks during normal epoch gives the expected result -#[test] -fn test_get_reward_distribution_num_blocks_normal() { - solana_logger::setup(); - let (mut genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); - genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false); - - let bank = Bank::new_for_tests(&genesis_config); - - // Given 8k rewards, it will take 2 blocks to credit all the rewards - let expected_num = 8192; - let stake_rewards = (0..expected_num) - .map(|_| StakeReward::new_random()) - .collect::>(); - - assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2); - assert_eq!(bank.get_reward_calculation_num_blocks(), 1); - assert_eq!( - bank.get_reward_total_num_blocks(&stake_rewards), - bank.get_reward_distribution_num_blocks(&stake_rewards) - + bank.get_reward_calculation_num_blocks(), - ); -} - -/// Test get_reward_distribution_num_blocks, get_reward_calculation_num_blocks, get_reward_total_num_blocks during small epoch -/// The num_credit_blocks should be cap to 10% of the total number of blocks in the epoch. -#[test] -fn test_get_reward_distribution_num_blocks_cap() { - let (mut genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); - genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false); - - // Config stake reward distribution to be 10 per block - let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone(); - accounts_db_config.test_partitioned_epoch_rewards = - TestPartitionedEpochRewards::PartitionedEpochRewardsConfigRewardBlocks { - reward_calculation_num_blocks: 1, - stake_account_stores_per_block: 10, - }; - - let bank = Bank::new_with_paths( - &genesis_config, - Arc::new(RuntimeConfig::default()), - Vec::new(), - None, - None, - AccountSecondaryIndexes::default(), - AccountShrinkThreshold::default(), - false, - Some(accounts_db_config), - None, - Some(Pubkey::new_unique()), - Arc::default(), - ); - - let stake_account_stores_per_block = bank.partitioned_rewards_stake_account_stores_per_block(); - assert_eq!(stake_account_stores_per_block, 10); - - let check_num_reward_distribution_blocks = - |num_stakes: u64, - expected_num_reward_distribution_blocks: u64, - expected_num_reward_computation_blocks: u64| { - // Given the short epoch, i.e. 32 slots, we should cap the number of reward distribution blocks to 32/10 = 3. - let stake_rewards = (0..num_stakes) - .map(|_| StakeReward::new_random()) - .collect::>(); - - assert_eq!( - bank.get_reward_distribution_num_blocks(&stake_rewards), - expected_num_reward_distribution_blocks - ); - assert_eq!( - bank.get_reward_calculation_num_blocks(), - expected_num_reward_computation_blocks - ); - assert_eq!( - bank.get_reward_total_num_blocks(&stake_rewards), - bank.get_reward_distribution_num_blocks(&stake_rewards) - + bank.get_reward_calculation_num_blocks(), - ); - }; - - for test_record in [ - // num_stakes, expected_num_reward_distribution_blocks, expected_num_reward_computation_blocks - (0, 1, 1), - (1, 1, 1), - (stake_account_stores_per_block, 1, 1), - (2 * stake_account_stores_per_block - 1, 2, 1), - (2 * stake_account_stores_per_block, 2, 1), - (3 * stake_account_stores_per_block - 1, 3, 1), - (3 * stake_account_stores_per_block, 3, 1), - (4 * stake_account_stores_per_block, 3, 1), // cap at 3 - (5 * stake_account_stores_per_block, 3, 1), //cap at 3 - ] { - check_num_reward_distribution_blocks(test_record.0, test_record.1, test_record.2); - } -} - -/// Test get_reward_distribution_num_blocks, get_reward_calculation_num_blocks, get_reward_total_num_blocks during warm up epoch gives the expected result. -/// The num_credit_blocks should be 1 during warm up epoch. -#[test] -fn test_get_reward_distribution_num_blocks_warmup() { - let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL); - - let bank = Bank::new_for_tests(&genesis_config); - let rewards = vec![]; - assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1); - assert_eq!(bank.get_reward_calculation_num_blocks(), 1); - assert_eq!( - bank.get_reward_total_num_blocks(&rewards), - bank.get_reward_distribution_num_blocks(&rewards) - + bank.get_reward_calculation_num_blocks(), - ); -} - #[test] fn test_calculate_stake_vote_rewards() { solana_logger::setup(); From 4d668bbebed8abd9611f37b7687371f1cbe88a67 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Fri, 29 Mar 2024 19:47:46 -0600 Subject: [PATCH 5/5] Update BankAbiTestWrapperNewer frozen_abi hash --- runtime/src/bank/serde_snapshot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/bank/serde_snapshot.rs b/runtime/src/bank/serde_snapshot.rs index 45cc4d8362bfc0..2ddaf8adf5ba92 100644 --- a/runtime/src/bank/serde_snapshot.rs +++ b/runtime/src/bank/serde_snapshot.rs @@ -605,7 +605,7 @@ mod tests { // This some what long test harness is required to freeze the ABI of // Bank's serialization due to versioned nature - #[frozen_abi(digest = "7BH2s2Y1yKy396c3ixC4TTyvvpkyenAvWDSiZvY5yb7P")] + #[frozen_abi(digest = "8BVfyLYrPt1ranknjF4sLePjZaZjpKXXrHt4wKf47g3W")] #[derive(Serialize, AbiExample)] pub struct BankAbiTestWrapperNewer { #[serde(serialize_with = "wrapper_newer")]