solana_runtime/bank/partitioned_epoch_rewards/
mod.rs

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