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