solana_runtime/bank/partitioned_epoch_rewards/
mod.rs

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