Skip to main content

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