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