solana_runtime/bank/partitioned_epoch_rewards/
mod.rs

1mod calculation;
2mod distribution;
3mod epoch_rewards_hasher;
4mod sysvar;
5
6use {
7    super::Bank,
8    crate::{
9        inflation_rewards::points::PointValue, stake_account::StakeAccount,
10        stake_history::StakeHistory,
11    },
12    solana_account::AccountSharedData,
13    solana_accounts_db::{
14        partitioned_rewards::PartitionedEpochRewardsConfig, stake_rewards::StakeReward,
15    },
16    solana_pubkey::Pubkey,
17    solana_reward_info::RewardInfo,
18    solana_stake_interface::state::{Delegation, Stake},
19    solana_vote::vote_account::VoteAccounts,
20    std::sync::Arc,
21};
22
23/// Number of blocks for reward calculation and storing vote accounts.
24/// Distributing rewards to stake accounts begins AFTER this many blocks.
25const REWARD_CALCULATION_NUM_BLOCKS: u64 = 1;
26
27#[derive(Debug, Clone, PartialEq)]
28pub(crate) struct PartitionedStakeReward {
29    /// Stake account address
30    pub stake_pubkey: Pubkey,
31    /// `Stake` state to be stored in account
32    pub stake: Stake,
33    /// Stake reward for recording in the Bank on distribution
34    pub stake_reward: u64,
35    /// Vote commission for recording reward info
36    pub commission: u8,
37}
38
39type PartitionedStakeRewards = Vec<PartitionedStakeReward>;
40
41#[derive(Debug, Clone, PartialEq)]
42pub(crate) struct StartBlockHeightAndRewards {
43    /// the block height of the slot at which rewards distribution began
44    pub(crate) distribution_starting_block_height: u64,
45    /// calculated epoch rewards before partitioning
46    pub(crate) all_stake_rewards: Arc<Vec<PartitionedStakeReward>>,
47}
48
49#[derive(Debug, Clone, PartialEq)]
50pub(crate) struct StartBlockHeightAndPartitionedRewards {
51    /// the block height of the slot at which rewards distribution began
52    pub(crate) distribution_starting_block_height: u64,
53
54    /// calculated epoch rewards pending distribution
55    pub(crate) all_stake_rewards: Arc<Vec<PartitionedStakeReward>>,
56
57    /// indices of calculated epoch rewards per partition, outer Vec is by
58    /// partition (one partition per block), inner Vec is the indices for one
59    /// partition.
60    pub(crate) partition_indices: Vec<Vec<usize>>,
61}
62
63/// Represent whether bank is in the reward phase or not.
64#[derive(Debug, Clone, PartialEq, Default)]
65pub(crate) enum EpochRewardStatus {
66    /// this bank is in the reward phase.
67    /// Contents are the start point for epoch reward calculation,
68    /// i.e. parent_slot and parent_block height for the starting
69    /// block of the current epoch.
70    Active(EpochRewardPhase),
71    /// this bank is outside of the rewarding phase.
72    #[default]
73    Inactive,
74}
75
76#[derive(Debug, Clone, PartialEq)]
77pub(crate) enum EpochRewardPhase {
78    Calculation(StartBlockHeightAndRewards),
79    Distribution(StartBlockHeightAndPartitionedRewards),
80}
81
82#[derive(Debug, Default)]
83pub(super) struct VoteRewardsAccounts {
84    /// reward info for each vote account pubkey.
85    /// This type is used by `update_reward_history()`
86    pub(super) rewards: Vec<(Pubkey, RewardInfo)>,
87    /// account to be stored, corresponds to pubkey in `rewards`
88    pub(super) accounts_to_store: Vec<(Pubkey, AccountSharedData)>,
89    /// total lamports across all `vote_rewards`
90    pub(super) total_vote_rewards_lamports: u64,
91}
92
93#[derive(Debug, Default)]
94/// result of calculating the stake rewards at end of epoch
95pub(super) struct StakeRewardCalculation {
96    /// each individual stake account to reward
97    stake_rewards: Arc<PartitionedStakeRewards>,
98    /// total lamports across all `stake_rewards`
99    total_stake_rewards_lamports: u64,
100}
101
102#[derive(Debug)]
103struct CalculateValidatorRewardsResult {
104    vote_rewards_accounts: VoteRewardsAccounts,
105    stake_reward_calculation: StakeRewardCalculation,
106    point_value: PointValue,
107}
108
109impl Default for CalculateValidatorRewardsResult {
110    fn default() -> Self {
111        Self {
112            vote_rewards_accounts: VoteRewardsAccounts::default(),
113            stake_reward_calculation: StakeRewardCalculation::default(),
114            point_value: PointValue {
115                points: 0,
116                rewards: 0,
117            },
118        }
119    }
120}
121
122/// hold reward calc info to avoid recalculation across functions
123pub(super) struct EpochRewardCalculateParamInfo<'a> {
124    pub(super) stake_history: StakeHistory,
125    pub(super) stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)>,
126    pub(super) cached_vote_accounts: &'a VoteAccounts,
127}
128
129/// Hold all results from calculating the rewards for partitioned distribution.
130/// This struct exists so we can have a function which does all the calculation with no
131/// side effects.
132#[derive(Debug)]
133pub(super) struct PartitionedRewardsCalculation {
134    pub(super) vote_account_rewards: VoteRewardsAccounts,
135    pub(super) stake_rewards: StakeRewardCalculation,
136    pub(super) validator_rate: f64,
137    pub(super) foundation_rate: f64,
138    pub(super) prev_epoch_duration_in_years: f64,
139    pub(super) capitalization: u64,
140    point_value: PointValue,
141}
142
143pub(super) struct CalculateRewardsAndDistributeVoteRewardsResult {
144    /// distributed vote rewards
145    pub(super) distributed_rewards: u64,
146    /// total rewards and points calculated for the current epoch, where points
147    /// equals the sum of (delegated stake * credits observed) for all
148    /// delegations and rewards are the lamports to split across all stake and
149    /// vote accounts
150    pub(super) point_value: PointValue,
151    /// stake rewards that still need to be distributed
152    pub(super) stake_rewards: Arc<Vec<PartitionedStakeReward>>,
153}
154
155pub(crate) type StakeRewards = Vec<StakeReward>;
156
157#[derive(Debug, PartialEq)]
158pub struct KeyedRewardsAndNumPartitions {
159    pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
160    pub num_partitions: Option<u64>,
161}
162
163impl KeyedRewardsAndNumPartitions {
164    pub fn should_record(&self) -> bool {
165        !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
166    }
167}
168
169impl Bank {
170    pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
171        let keyed_rewards = self.rewards.read().unwrap().clone();
172        let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
173        // If partitioned epoch rewards are active and this Bank is the
174        // epoch-boundary block, populate num_partitions
175        let epoch_schedule = self.epoch_schedule();
176        let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
177        let is_first_block_in_epoch = self.epoch() > parent_epoch;
178
179        let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
180            .then_some(epoch_rewards_sysvar.num_partitions);
181        KeyedRewardsAndNumPartitions {
182            keyed_rewards,
183            num_partitions,
184        }
185    }
186
187    pub(crate) fn set_epoch_reward_status_calculation(
188        &mut self,
189        distribution_starting_block_height: u64,
190        stake_rewards: Arc<Vec<PartitionedStakeReward>>,
191    ) {
192        self.epoch_reward_status =
193            EpochRewardStatus::Active(EpochRewardPhase::Calculation(StartBlockHeightAndRewards {
194                distribution_starting_block_height,
195                all_stake_rewards: stake_rewards,
196            }));
197    }
198
199    pub(crate) fn set_epoch_reward_status_distribution(
200        &mut self,
201        distribution_starting_block_height: u64,
202        all_stake_rewards: Arc<Vec<PartitionedStakeReward>>,
203        partition_indices: Vec<Vec<usize>>,
204    ) {
205        self.epoch_reward_status = EpochRewardStatus::Active(EpochRewardPhase::Distribution(
206            StartBlockHeightAndPartitionedRewards {
207                distribution_starting_block_height,
208                all_stake_rewards,
209                partition_indices,
210            },
211        ));
212    }
213
214    pub(super) fn partitioned_epoch_rewards_config(&self) -> &PartitionedEpochRewardsConfig {
215        &self
216            .rc
217            .accounts
218            .accounts_db
219            .partitioned_epoch_rewards_config
220    }
221
222    /// # stake accounts to store in one block during partitioned reward interval
223    pub(super) fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 {
224        self.partitioned_epoch_rewards_config()
225            .stake_account_stores_per_block
226    }
227
228    /// Calculate the number of blocks required to distribute rewards to all stake accounts.
229    pub(super) fn get_reward_distribution_num_blocks(
230        &self,
231        rewards: &PartitionedStakeRewards,
232    ) -> u64 {
233        let total_stake_accounts = rewards.len();
234        if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() {
235            1
236        } else {
237            const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10;
238            let num_chunks = solana_accounts_db::accounts_hash::AccountsHasher::div_ceil(
239                total_stake_accounts,
240                self.partitioned_rewards_stake_account_stores_per_block() as usize,
241            ) as u64;
242
243            // Limit the reward credit interval to 10% of the total number of slots in a epoch
244            num_chunks.clamp(
245                1,
246                (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1),
247            )
248        }
249    }
250
251    /// For testing only
252    pub fn force_reward_interval_end_for_tests(&mut self) {
253        self.epoch_reward_status = EpochRewardStatus::Inactive;
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use {
260        super::*,
261        crate::{
262            bank::tests::{create_genesis_config, new_bank_from_parent_with_bank_forks},
263            bank_forks::BankForks,
264            genesis_utils::{
265                create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs,
266            },
267            runtime_config::RuntimeConfig,
268        },
269        assert_matches::assert_matches,
270        solana_account::{state_traits::StateMut, Account},
271        solana_accounts_db::accounts_db::{AccountsDbConfig, ACCOUNTS_DB_CONFIG_FOR_TESTING},
272        solana_epoch_schedule::EpochSchedule,
273        solana_hash::Hash,
274        solana_keypair::Keypair,
275        solana_native_token::LAMPORTS_PER_SOL,
276        solana_reward_info::RewardType,
277        solana_signer::Signer,
278        solana_stake_interface::{error::StakeError, state::StakeStateV2},
279        solana_system_transaction as system_transaction,
280        solana_transaction::Transaction,
281        solana_vote::vote_transaction,
282        solana_vote_interface::state::{VoteStateVersions, MAX_LOCKOUT_HISTORY},
283        solana_vote_program::vote_state::{self, TowerSync},
284        std::sync::{Arc, RwLock},
285    };
286
287    impl PartitionedStakeReward {
288        fn maybe_from(stake_reward: &StakeReward) -> Option<Self> {
289            if let Ok(StakeStateV2::Stake(_meta, stake, _flags)) =
290                stake_reward.stake_account.state()
291            {
292                Some(Self {
293                    stake_pubkey: stake_reward.stake_pubkey,
294                    stake,
295                    stake_reward: stake_reward.stake_reward_info.lamports as u64,
296                    commission: stake_reward.stake_reward_info.commission.unwrap(),
297                })
298            } else {
299                None
300            }
301        }
302
303        pub fn new_random() -> Self {
304            Self::maybe_from(&StakeReward::new_random()).unwrap()
305        }
306    }
307
308    pub fn build_partitioned_stake_rewards(
309        stake_rewards: &[PartitionedStakeReward],
310        partition_indices: &[Vec<usize>],
311    ) -> Vec<Vec<PartitionedStakeReward>> {
312        partition_indices
313            .iter()
314            .map(|partition_index| {
315                // partition_index is a Vec<usize> that contains the indices of the stake rewards
316                // that belong to this partition
317                partition_index
318                    .iter()
319                    .map(|&index| stake_rewards[index].clone())
320                    .collect::<Vec<_>>()
321            })
322            .collect::<Vec<_>>()
323    }
324
325    pub fn convert_rewards(
326        stake_rewards: impl IntoIterator<Item = StakeReward>,
327    ) -> PartitionedStakeRewards {
328        stake_rewards
329            .into_iter()
330            .map(|stake_reward| PartitionedStakeReward::maybe_from(&stake_reward).unwrap())
331            .collect()
332    }
333
334    #[derive(Debug, PartialEq, Eq, Copy, Clone)]
335    enum RewardInterval {
336        /// the slot within the epoch is INSIDE the reward distribution interval
337        InsideInterval,
338        /// the slot within the epoch is OUTSIDE the reward distribution interval
339        OutsideInterval,
340    }
341
342    impl Bank {
343        /// Return `RewardInterval` enum for current bank
344        fn get_reward_interval(&self) -> RewardInterval {
345            if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) {
346                RewardInterval::InsideInterval
347            } else {
348                RewardInterval::OutsideInterval
349            }
350        }
351
352        fn is_calculated(&self) -> bool {
353            matches!(
354                self.epoch_reward_status,
355                EpochRewardStatus::Active(EpochRewardPhase::Calculation(_))
356            )
357        }
358
359        fn is_partitioned(&self) -> bool {
360            matches!(
361                self.epoch_reward_status,
362                EpochRewardStatus::Active(EpochRewardPhase::Distribution(_))
363            )
364        }
365
366        fn get_epoch_rewards_from_cache(
367            &self,
368            parent_hash: &Hash,
369        ) -> Option<Arc<PartitionedRewardsCalculation>> {
370            self.epoch_rewards_calculation_cache
371                .lock()
372                .unwrap()
373                .get(parent_hash)
374                .cloned()
375        }
376
377        fn get_epoch_rewards_cache_len(&self) -> usize {
378            self.epoch_rewards_calculation_cache.lock().unwrap().len()
379        }
380    }
381
382    pub(super) const SLOTS_PER_EPOCH: u64 = 32;
383
384    pub(super) struct RewardBank {
385        pub(super) bank: Arc<Bank>,
386        pub(super) voters: Vec<Pubkey>,
387        pub(super) stakers: Vec<Pubkey>,
388    }
389
390    /// Helper functions to create a bank that pays some rewards
391    pub(super) fn create_default_reward_bank(
392        expected_num_delegations: usize,
393        advance_num_slots: u64,
394    ) -> (RewardBank, Arc<RwLock<BankForks>>) {
395        create_reward_bank(
396            expected_num_delegations,
397            PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
398            advance_num_slots,
399        )
400    }
401
402    pub(super) fn create_reward_bank(
403        expected_num_delegations: usize,
404        stake_account_stores_per_block: u64,
405        advance_num_slots: u64,
406    ) -> (RewardBank, Arc<RwLock<BankForks>>) {
407        create_reward_bank_with_specific_stakes(
408            vec![2_000_000_000; expected_num_delegations],
409            stake_account_stores_per_block,
410            advance_num_slots,
411        )
412    }
413
414    pub(super) fn create_reward_bank_with_specific_stakes(
415        stakes: Vec<u64>,
416        stake_account_stores_per_block: u64,
417        advance_num_slots: u64,
418    ) -> (RewardBank, Arc<RwLock<BankForks>>) {
419        let validator_keypairs = (0..stakes.len())
420            .map(|_| ValidatorVoteKeypairs::new_rand())
421            .collect::<Vec<_>>();
422
423        let GenesisConfigInfo {
424            mut genesis_config, ..
425        } = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stakes);
426        genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
427
428        let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
429        accounts_db_config.partitioned_epoch_rewards_config =
430            PartitionedEpochRewardsConfig::new_for_test(stake_account_stores_per_block);
431
432        let bank = Bank::new_with_paths(
433            &genesis_config,
434            Arc::new(RuntimeConfig::default()),
435            Vec::new(),
436            None,
437            None,
438            false,
439            Some(accounts_db_config),
440            None,
441            Some(Pubkey::new_unique()),
442            Arc::default(),
443            None,
444            None,
445        );
446
447        // Fill bank_forks with banks with votes landing in the next slot
448        // Create enough banks such that vote account will root
449        for validator_vote_keypairs in &validator_keypairs {
450            let vote_id = validator_vote_keypairs.vote_keypair.pubkey();
451            let mut vote_account = bank.get_account(&vote_id).unwrap();
452            // generate some rewards
453            let mut vote_state = Some(vote_state::from(&vote_account).unwrap());
454            for i in 0..MAX_LOCKOUT_HISTORY + 42 {
455                if let Some(v) = vote_state.as_mut() {
456                    vote_state::process_slot_vote_unchecked(v, i as u64)
457                }
458                let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap()));
459                vote_state::to(&versioned, &mut vote_account).unwrap();
460                match versioned {
461                    VoteStateVersions::Current(v) => {
462                        vote_state = Some(*v);
463                    }
464                    _ => panic!("Has to be of type Current"),
465                };
466            }
467            bank.store_account_and_update_capitalization(&vote_id, &vote_account);
468        }
469
470        // Advance some num slots; usually to the next epoch boundary to update
471        // EpochStakes
472        let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
473        let bank = new_bank_from_parent_with_bank_forks(
474            &bank_forks,
475            bank,
476            &Pubkey::default(),
477            advance_num_slots,
478        );
479
480        (
481            RewardBank {
482                bank,
483                voters: validator_keypairs
484                    .iter()
485                    .map(|k| k.vote_keypair.pubkey())
486                    .collect(),
487                stakers: validator_keypairs
488                    .iter()
489                    .map(|k| k.stake_keypair.pubkey())
490                    .collect(),
491            },
492            bank_forks,
493        )
494    }
495
496    #[test]
497    fn test_force_reward_interval_end() {
498        let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
499        let mut bank = Bank::new_for_tests(&genesis_config);
500
501        let expected_num = 100;
502
503        let stake_rewards = (0..expected_num)
504            .map(|_| PartitionedStakeReward::new_random())
505            .collect::<Vec<_>>();
506
507        let partition_indices = vec![(0..expected_num).collect()];
508
509        bank.set_epoch_reward_status_distribution(
510            bank.block_height() + REWARD_CALCULATION_NUM_BLOCKS,
511            Arc::new(stake_rewards),
512            partition_indices,
513        );
514        assert!(bank.get_reward_interval() == RewardInterval::InsideInterval);
515
516        bank.force_reward_interval_end_for_tests();
517        assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval);
518    }
519
520    /// Test get_reward_distribution_num_blocks during small epoch
521    /// The num_credit_blocks should be cap to 10% of the total number of blocks in the epoch.
522    #[test]
523    fn test_get_reward_distribution_num_blocks_cap() {
524        let (mut genesis_config, _mint_keypair) =
525            create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
526        genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false);
527
528        // Config stake reward distribution to be 10 per block
529        let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
530        accounts_db_config.partitioned_epoch_rewards_config =
531            PartitionedEpochRewardsConfig::new_for_test(10);
532
533        let bank = Bank::new_with_paths(
534            &genesis_config,
535            Arc::new(RuntimeConfig::default()),
536            Vec::new(),
537            None,
538            None,
539            false,
540            Some(accounts_db_config),
541            None,
542            Some(Pubkey::new_unique()),
543            Arc::default(),
544            None,
545            None,
546        );
547
548        let stake_account_stores_per_block =
549            bank.partitioned_rewards_stake_account_stores_per_block();
550        assert_eq!(stake_account_stores_per_block, 10);
551
552        let check_num_reward_distribution_blocks =
553            |num_stakes: u64, expected_num_reward_distribution_blocks: u64| {
554                // Given the short epoch, i.e. 32 slots, we should cap the number of reward distribution blocks to 32/10 = 3.
555                let stake_rewards = (0..num_stakes)
556                    .map(|_| PartitionedStakeReward::new_random())
557                    .collect::<Vec<_>>();
558
559                assert_eq!(
560                    bank.get_reward_distribution_num_blocks(&stake_rewards),
561                    expected_num_reward_distribution_blocks
562                );
563            };
564
565        for test_record in [
566            // num_stakes, expected_num_reward_distribution_blocks
567            (0, 1),
568            (1, 1),
569            (stake_account_stores_per_block, 1),
570            (2 * stake_account_stores_per_block - 1, 2),
571            (2 * stake_account_stores_per_block, 2),
572            (3 * stake_account_stores_per_block - 1, 3),
573            (3 * stake_account_stores_per_block, 3),
574            (4 * stake_account_stores_per_block, 3), // cap at 3
575            (5 * stake_account_stores_per_block, 3), //cap at 3
576        ] {
577            check_num_reward_distribution_blocks(test_record.0, test_record.1);
578        }
579    }
580
581    /// Test get_reward_distribution_num_blocks during normal epoch gives the expected result
582    #[test]
583    fn test_get_reward_distribution_num_blocks_normal() {
584        solana_logger::setup();
585        let (mut genesis_config, _mint_keypair) =
586            create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
587        genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
588
589        let bank = Bank::new_for_tests(&genesis_config);
590
591        // Given 8k rewards, it will take 2 blocks to credit all the rewards
592        let expected_num = 8192;
593        let stake_rewards = (0..expected_num)
594            .map(|_| PartitionedStakeReward::new_random())
595            .collect::<Vec<_>>();
596
597        assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2);
598    }
599
600    /// Test get_reward_distribution_num_blocks during warm up epoch gives the expected result.
601    /// The num_credit_blocks should be 1 during warm up epoch.
602    #[test]
603    fn test_get_reward_distribution_num_blocks_warmup() {
604        let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
605
606        let bank = Bank::new_for_tests(&genesis_config);
607        let rewards = vec![];
608        assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
609    }
610
611    #[test]
612    fn test_rewards_computation_and_partitioned_distribution_one_block() {
613        solana_logger::setup();
614
615        let starting_slot = SLOTS_PER_EPOCH - 1;
616        let (
617            RewardBank {
618                bank: mut previous_bank,
619                ..
620            },
621            bank_forks,
622        ) = create_default_reward_bank(100, starting_slot - 1);
623
624        // simulate block progress
625        for slot in starting_slot..=(2 * SLOTS_PER_EPOCH) + 2 {
626            let pre_cap = previous_bank.capitalization();
627            let curr_bank = new_bank_from_parent_with_bank_forks(
628                bank_forks.as_ref(),
629                previous_bank.clone(),
630                &Pubkey::default(),
631                slot,
632            );
633            let post_cap = curr_bank.capitalization();
634
635            if slot % SLOTS_PER_EPOCH == 0 {
636                // This is the first block of the epoch. Reward computation should happen in this block.
637                // assert reward compute status activated at epoch boundary
638                assert_matches!(
639                    curr_bank.get_reward_interval(),
640                    RewardInterval::InsideInterval
641                );
642
643                assert!(curr_bank.is_calculated());
644
645                // after reward calculation, the cache should be filled.
646                assert!(curr_bank
647                    .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
648                    .is_some());
649                assert_eq!(post_cap, pre_cap);
650
651                // Make a root the bank, which is the first bank in the epoch.
652                // This will clear the cache.
653                let _ = bank_forks.write().unwrap().set_root(slot, None, None);
654                assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
655            } else if slot == SLOTS_PER_EPOCH + 1 {
656                // 1. when curr_slot == SLOTS_PER_EPOCH + 1, the 2nd block of
657                // epoch 1, reward distribution should happen in this block.
658                // however, all stake rewards are paid at this block therefore
659                // reward_status should have transitioned to inactive. The cap
660                // should increase accordingly.
661                assert_matches!(
662                    curr_bank.get_reward_interval(),
663                    RewardInterval::OutsideInterval
664                );
665                let account = curr_bank
666                    .get_account(&solana_sysvar::epoch_rewards::id())
667                    .unwrap();
668                let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
669                    solana_account::from_account(&account).unwrap();
670                assert_eq!(post_cap, pre_cap + epoch_rewards.distributed_rewards);
671            } else {
672                // 2. when curr_slot == SLOTS_PER_EPOCH + 2, the 3rd block of
673                // epoch 1 (or any other slot). reward distribution should have
674                // already completed. Therefore, reward_status should stay
675                // inactive and cap should stay the same.
676                assert_matches!(
677                    curr_bank.get_reward_interval(),
678                    RewardInterval::OutsideInterval
679                );
680
681                // slot is not in rewards, cap should not change
682                assert_eq!(post_cap, pre_cap);
683            }
684            // EpochRewards sysvar is created in the first block of epoch 1.
685            // Ensure the sysvar persists thereafter.
686            if slot >= SLOTS_PER_EPOCH {
687                let epoch_rewards_lamports =
688                    curr_bank.get_balance(&solana_sysvar::epoch_rewards::id());
689                assert!(epoch_rewards_lamports > 0);
690            }
691            previous_bank = curr_bank;
692        }
693    }
694
695    /// Test rewards computation and partitioned rewards distribution at the epoch boundary (two reward distribution blocks)
696    #[test]
697    fn test_rewards_computation_and_partitioned_distribution_two_blocks() {
698        solana_logger::setup();
699
700        let starting_slot = SLOTS_PER_EPOCH - 1;
701        let (
702            RewardBank {
703                bank: mut previous_bank,
704                ..
705            },
706            bank_forks,
707        ) = create_reward_bank(100, 50, starting_slot - 1);
708        let mut starting_hash = None;
709
710        // simulate block progress
711        for slot in starting_slot..=SLOTS_PER_EPOCH + 3 {
712            let pre_cap = previous_bank.capitalization();
713
714            let pre_sysvar_account = previous_bank
715                .get_account(&solana_sysvar::epoch_rewards::id())
716                .unwrap_or_default();
717            let pre_epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
718                solana_account::from_account(&pre_sysvar_account).unwrap_or_default();
719            let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
720            let curr_bank = new_bank_from_parent_with_bank_forks(
721                bank_forks.as_ref(),
722                previous_bank.clone(),
723                &Pubkey::default(),
724                slot,
725            );
726            let post_cap = curr_bank.capitalization();
727
728            if slot == SLOTS_PER_EPOCH {
729                // This is the first block of epoch 1. Reward computation should happen in this block.
730                // assert reward compute status activated at epoch boundary
731                assert_matches!(
732                    curr_bank.get_reward_interval(),
733                    RewardInterval::InsideInterval
734                );
735
736                // calculation block, state should be calculated.
737                assert!(curr_bank.is_calculated());
738
739                // after reward calculation, the cache should be filled.
740                assert!(curr_bank
741                    .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
742                    .is_some());
743                assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
744                starting_hash = Some(curr_bank.parent_hash);
745            } else if slot == SLOTS_PER_EPOCH + 1 {
746                // When curr_slot == SLOTS_PER_EPOCH + 1, the 2nd block of
747                // epoch 1, reward distribution should happen in this block. The
748                // cap should increase accordingly.
749                assert_matches!(
750                    curr_bank.get_reward_interval(),
751                    RewardInterval::InsideInterval
752                );
753
754                // The first block of the epoch has not rooted yet, so the cache
755                // should still have the results.
756                assert!(curr_bank
757                    .get_epoch_rewards_from_cache(&starting_hash.unwrap())
758                    .is_some());
759                assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
760
761                // 1st reward distribution block, state should be partitioned.
762                assert!(curr_bank.is_partitioned());
763
764                let account = curr_bank
765                    .get_account(&solana_sysvar::epoch_rewards::id())
766                    .unwrap();
767                let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
768                    solana_account::from_account(&account).unwrap();
769                assert_eq!(
770                    post_cap,
771                    pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
772                );
773
774                // Now make a root the  first bank in the epoch.
775                // This should clear the cache.
776                let _ = bank_forks.write().unwrap().set_root(slot - 1, None, None);
777                assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
778            } else if slot == SLOTS_PER_EPOCH + 2 {
779                // When curr_slot == SLOTS_PER_EPOCH + 2, the 3nd block of
780                // epoch 1, reward distribution should happen in this block.
781                // however, all stake rewards are paid at the this block
782                // therefore reward_status should have transitioned to inactive.
783                // The cap should increase accordingly.
784                assert_matches!(
785                    curr_bank.get_reward_interval(),
786                    RewardInterval::OutsideInterval
787                );
788
789                let account = curr_bank
790                    .get_account(&solana_sysvar::epoch_rewards::id())
791                    .unwrap();
792                let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
793                    solana_account::from_account(&account).unwrap();
794                assert_eq!(
795                    post_cap,
796                    pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
797                );
798            } else {
799                // When curr_slot == SLOTS_PER_EPOCH + 3, the 4th block of
800                // epoch 1 (or any other slot). reward distribution should have
801                // already completed. Therefore, reward_status should stay
802                // inactive and cap should stay the same.
803                assert_matches!(
804                    curr_bank.get_reward_interval(),
805                    RewardInterval::OutsideInterval
806                );
807
808                // slot is not in rewards, cap should not change
809                assert_eq!(post_cap, pre_cap);
810            }
811            previous_bank = curr_bank;
812        }
813    }
814
815    /// Test that program execution that attempts to mutate a stake account
816    /// incorrectly should fail during reward period. A credit should succeed,
817    /// but a withdrawal should fail.
818    #[test]
819    fn test_program_execution_restricted_for_stake_account_in_reward_period() {
820        use solana_transaction_error::TransactionError::InstructionError;
821
822        let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
823        let validator_keypairs = vec![&validator_vote_keypairs];
824        let GenesisConfigInfo {
825            mut genesis_config,
826            mint_keypair,
827            ..
828        } = create_genesis_config_with_vote_accounts(
829            1_000_000_000,
830            &validator_keypairs,
831            vec![1_000_000_000; 1],
832        );
833
834        // Add stake account to try to mutate
835        let vote_key = validator_keypairs[0].vote_keypair.pubkey();
836        let vote_account = genesis_config
837            .accounts
838            .iter()
839            .find(|(&address, _)| address == vote_key)
840            .map(|(_, account)| account)
841            .unwrap()
842            .clone();
843
844        let new_stake_signer = Keypair::new();
845        let new_stake_address = new_stake_signer.pubkey();
846        let new_stake_account = Account::from(solana_stake_program::stake_state::create_account(
847            &new_stake_address,
848            &vote_key,
849            &vote_account.into(),
850            &genesis_config.rent,
851            2_000_000_000,
852        ));
853        genesis_config
854            .accounts
855            .extend(vec![(new_stake_address, new_stake_account)]);
856
857        let (mut previous_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
858        let num_slots_in_epoch = previous_bank.get_slots_in_epoch(previous_bank.epoch());
859        assert_eq!(num_slots_in_epoch, 32);
860
861        let transfer_amount = 5_000;
862
863        for slot in 1..=num_slots_in_epoch + 2 {
864            let bank = new_bank_from_parent_with_bank_forks(
865                bank_forks.as_ref(),
866                previous_bank.clone(),
867                &Pubkey::default(),
868                slot,
869            );
870
871            // Fill bank_forks with banks with votes landing in the next slot
872            // So that rewards will be paid out at the epoch boundary, i.e. slot = 32
873            let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash());
874            let vote = vote_transaction::new_tower_sync_transaction(
875                tower_sync,
876                previous_bank.last_blockhash(),
877                &validator_vote_keypairs.node_keypair,
878                &validator_vote_keypairs.vote_keypair,
879                &validator_vote_keypairs.vote_keypair,
880                None,
881            );
882            bank.process_transaction(&vote).unwrap();
883
884            // Insert a transfer transaction from the mint to new stake account
885            let system_tx = system_transaction::transfer(
886                &mint_keypair,
887                &new_stake_address,
888                transfer_amount,
889                bank.last_blockhash(),
890            );
891            let system_result = bank.process_transaction(&system_tx);
892
893            // Credits should always succeed
894            assert!(system_result.is_ok());
895
896            // Attempt to withdraw from new stake account to the mint
897            let stake_ix = solana_stake_interface::instruction::withdraw(
898                &new_stake_address,
899                &new_stake_address,
900                &mint_keypair.pubkey(),
901                transfer_amount,
902                None,
903            );
904            let stake_tx = Transaction::new_signed_with_payer(
905                &[stake_ix],
906                Some(&mint_keypair.pubkey()),
907                &[&mint_keypair, &new_stake_signer],
908                bank.last_blockhash(),
909            );
910            let stake_result = bank.process_transaction(&stake_tx);
911
912            if slot == num_slots_in_epoch {
913                // When the bank is at the beginning of the new epoch, i.e. slot
914                // 32, StakeError::EpochRewardsActive should be thrown for
915                // actions like StakeInstruction::Withdraw
916                assert_eq!(
917                    stake_result,
918                    Err(InstructionError(0, StakeError::EpochRewardsActive.into()))
919                );
920            } else {
921                // When the bank is outside of reward interval, the withdraw
922                // transaction should not be affected and will succeed.
923                assert!(stake_result.is_ok());
924            }
925
926            // Push a dummy blockhash, so that the latest_blockhash() for the transfer transaction in each
927            // iteration are different. Otherwise, all those transactions will be the same, and will not be
928            // executed by the bank except the first one.
929            bank.register_unique_recent_blockhash_for_test();
930            previous_bank = bank;
931        }
932    }
933
934    #[test]
935    fn test_get_rewards_and_partitions() {
936        let starting_slot = SLOTS_PER_EPOCH - 1;
937        let num_rewards = 100;
938        let stake_account_stores_per_block = 50;
939        let (RewardBank { bank, .. }, _) =
940            create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
941
942        // Slot before the epoch boundary contains empty rewards (since fees are
943        // off), and no partitions because not at the epoch boundary
944        assert_eq!(
945            bank.get_rewards_and_num_partitions(),
946            KeyedRewardsAndNumPartitions {
947                keyed_rewards: vec![],
948                num_partitions: None,
949            }
950        );
951
952        let epoch_boundary_bank = Arc::new(Bank::new_from_parent(
953            bank,
954            &Pubkey::default(),
955            SLOTS_PER_EPOCH,
956        ));
957        // Slot at the epoch boundary contains voting rewards only, as well as partition data
958        let KeyedRewardsAndNumPartitions {
959            keyed_rewards,
960            num_partitions,
961        } = epoch_boundary_bank.get_rewards_and_num_partitions();
962        for (_pubkey, reward) in keyed_rewards.iter() {
963            assert_eq!(reward.reward_type, RewardType::Voting);
964        }
965        assert_eq!(keyed_rewards.len(), num_rewards);
966        assert_eq!(
967            num_partitions,
968            Some(num_rewards as u64 / stake_account_stores_per_block)
969        );
970
971        let mut total_staking_rewards = 0;
972
973        let partition0_bank = Arc::new(Bank::new_from_parent(
974            epoch_boundary_bank,
975            &Pubkey::default(),
976            SLOTS_PER_EPOCH + 1,
977        ));
978        // Slot after the epoch boundary contains first partition of staking
979        // rewards, and no partitions because not at the epoch boundary
980        let KeyedRewardsAndNumPartitions {
981            keyed_rewards,
982            num_partitions,
983        } = partition0_bank.get_rewards_and_num_partitions();
984        for (_pubkey, reward) in keyed_rewards.iter() {
985            assert_eq!(reward.reward_type, RewardType::Staking);
986        }
987        total_staking_rewards += keyed_rewards.len();
988        assert_eq!(num_partitions, None);
989
990        let partition1_bank = Arc::new(Bank::new_from_parent(
991            partition0_bank,
992            &Pubkey::default(),
993            SLOTS_PER_EPOCH + 2,
994        ));
995        // Slot 2 after the epoch boundary contains second partition of staking
996        // rewards, and no partitions because not at the epoch boundary
997        let KeyedRewardsAndNumPartitions {
998            keyed_rewards,
999            num_partitions,
1000        } = partition1_bank.get_rewards_and_num_partitions();
1001        for (_pubkey, reward) in keyed_rewards.iter() {
1002            assert_eq!(reward.reward_type, RewardType::Staking);
1003        }
1004        total_staking_rewards += keyed_rewards.len();
1005        assert_eq!(num_partitions, None);
1006
1007        // All rewards are recorded
1008        assert_eq!(total_staking_rewards, num_rewards);
1009
1010        let bank = Bank::new_from_parent(partition1_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 3);
1011        // Next slot contains empty rewards (since fees are off), and no
1012        // partitions because not at the epoch boundary
1013        assert_eq!(
1014            bank.get_rewards_and_num_partitions(),
1015            KeyedRewardsAndNumPartitions {
1016                keyed_rewards: vec![],
1017                num_partitions: None,
1018            }
1019        );
1020    }
1021
1022    #[test]
1023    fn test_rewards_and_partitions_should_record() {
1024        let reward = RewardInfo {
1025            reward_type: RewardType::Voting,
1026            lamports: 55,
1027            post_balance: 5555,
1028            commission: Some(5),
1029        };
1030
1031        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1032            keyed_rewards: vec![],
1033            num_partitions: None,
1034        };
1035        assert!(!rewards_and_partitions.should_record());
1036
1037        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1038            keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1039            num_partitions: None,
1040        };
1041        assert!(rewards_and_partitions.should_record());
1042
1043        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1044            keyed_rewards: vec![],
1045            num_partitions: Some(42),
1046        };
1047        assert!(rewards_and_partitions.should_record());
1048
1049        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1050            keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1051            num_partitions: Some(42),
1052        };
1053        assert!(rewards_and_partitions.should_record());
1054    }
1055}