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