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