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    /// Safety: all `total_len` elements must be initialized in `self.rewards`.
100    /// `num_stake_rewards` is the number of those elements that are `Some`.
101    unsafe fn assume_init(&mut self, num_stake_rewards: usize, total_len: usize) {
102        debug_assert!(num_stake_rewards <= total_len);
103        unsafe {
104            self.rewards.set_len(total_len);
105        }
106        self.num_rewards = num_stake_rewards;
107    }
108}
109
110#[cfg(test)]
111impl FromIterator<Option<PartitionedStakeReward>> for PartitionedStakeRewards {
112    fn from_iter<T: IntoIterator<Item = Option<PartitionedStakeReward>>>(iter: T) -> Self {
113        let mut len_some: usize = 0;
114        let rewards = Vec::from_iter(iter.into_iter().inspect(|reward| {
115            if reward.is_some() {
116                len_some = len_some.saturating_add(1);
117            }
118        }));
119        Self {
120            rewards,
121            num_rewards: len_some,
122        }
123    }
124}
125
126#[cfg(test)]
127impl FromIterator<PartitionedStakeReward> for PartitionedStakeRewards {
128    fn from_iter<T: IntoIterator<Item = PartitionedStakeReward>>(iter: T) -> Self {
129        let rewards = Vec::from_iter(iter.into_iter().map(Some));
130        let num_rewards = rewards.len();
131        Self {
132            rewards,
133            num_rewards,
134        }
135    }
136}
137
138#[derive(Debug, Clone, PartialEq)]
139pub(crate) struct StartBlockHeightAndRewards {
140    /// the block height of the slot at which rewards distribution began
141    pub(crate) distribution_starting_block_height: u64,
142    /// calculated epoch rewards before partitioning
143    pub(crate) all_stake_rewards: Arc<PartitionedStakeRewards>,
144}
145
146#[derive(Debug, Clone, PartialEq)]
147pub(crate) struct StartBlockHeightAndPartitionedRewards {
148    /// the block height of the slot at which rewards distribution began
149    pub(crate) distribution_starting_block_height: u64,
150
151    /// calculated epoch rewards pending distribution
152    pub(crate) all_stake_rewards: Arc<PartitionedStakeRewards>,
153
154    /// indices of calculated epoch rewards per partition, outer Vec is by
155    /// partition (one partition per block), inner Vec is the indices for one
156    /// partition.
157    pub(crate) partition_indices: Vec<Vec<usize>>,
158}
159
160/// Represent whether bank is in the reward phase or not.
161#[derive(Debug, Clone, PartialEq, Default)]
162pub(crate) enum EpochRewardStatus {
163    /// this bank is in the reward phase.
164    /// Contents are the start point for epoch reward calculation,
165    /// i.e. parent_slot and parent_block height for the starting
166    /// block of the current epoch.
167    Active(EpochRewardPhase),
168    /// this bank is outside of the rewarding phase.
169    #[default]
170    Inactive,
171}
172
173#[derive(Debug, Clone, PartialEq)]
174pub(crate) enum EpochRewardPhase {
175    Calculation(StartBlockHeightAndRewards),
176    Distribution(StartBlockHeightAndPartitionedRewards),
177}
178
179#[derive(Debug)]
180#[cfg_attr(test, derive(Clone))]
181pub(super) struct RewardCommission {
182    // Note: This field becomes always `None` once SIMD-0232 is activated.
183    // After full activation, it can be removed on feature cleanup.
184    pub(super) commission_bps: Option<u16>,
185    pub(super) commission_lamports: u64,
186    pub(super) burned_lamports: u64,
187    pub(super) is_vote_account: bool,
188}
189
190pub(super) type RewardCommissions = HashMap<Pubkey, RewardCommission, PubkeyHasherBuilder>;
191
192/// Helper struct to give the amounts distributed to commission accounts or
193/// burned in different manners
194#[derive(Debug, Default)]
195pub(super) struct RewardLamportAmounts {
196    /// Lamports distributed across all commission collectors, except the
197    /// incinerator.
198    ///
199    /// Also the maximum capitalization increase by the end of the first block
200    /// of the epoch.
201    pub(super) distributed_lamports: u64,
202    /// lamports distributed to the incinerator.
203    ///
204    /// Tracked separately to give a better upper bound for capitalization at
205    /// the end of the first block of an epoch, needed for Alpenglow's
206    /// `EpochInflationAccountState`
207    pub(super) distributed_to_incinerator_lamports: u64,
208    /// lamports burned from undistributed commissions
209    pub(super) burned_lamports: u64,
210    /// Pending delegator reward lamports swept from vote accounts
211    pub(super) block_rewards: u64,
212}
213
214#[derive(Debug, Default)]
215pub(super) struct EpochBoundaryAccounts {
216    /// accounts with rewards to be stored
217    pub(super) accounts_with_rewards: Vec<(Pubkey, RewardInfo, AccountSharedData)>,
218    /// vote accounts whose pending delegator rewards were swept, to be stored
219    pub(super) swept_vote_accounts: Vec<(Pubkey, AccountSharedData)>,
220    /// amounts distributed to those accounts, and burned after calculation
221    pub(super) amounts: RewardLamportAmounts,
222}
223
224/// Wrapper struct to implement StorableAccounts for EpochBoundaryAccounts
225pub(super) struct EpochBoundaryAccountsStorable<'a> {
226    pub slot: Slot,
227    pub epoch_boundary_accounts: &'a EpochBoundaryAccounts,
228}
229
230impl<'a> EpochBoundaryAccountsStorable<'a> {
231    fn get_unchecked(&self, index: usize) -> (&Pubkey, &AccountSharedData) {
232        let num_accounts_with_rewards = self.epoch_boundary_accounts.accounts_with_rewards.len();
233        if index >= num_accounts_with_rewards {
234            let (pubkey, account) = &self.epoch_boundary_accounts.swept_vote_accounts
235                [index - num_accounts_with_rewards];
236            (pubkey, account)
237        } else {
238            let (pubkey, _, account) = &self.epoch_boundary_accounts.accounts_with_rewards[index];
239            (pubkey, account)
240        }
241    }
242}
243
244impl<'a> StorableAccounts<'a> for EpochBoundaryAccountsStorable<'a> {
245    fn account<Ret>(
246        &self,
247        index: usize,
248        mut callback: impl for<'local> FnMut(AccountForStorage<'local>) -> Ret,
249    ) -> Ret {
250        let (pubkey, account) = self.get_unchecked(index);
251        callback((pubkey, account).into())
252    }
253
254    fn account_for_geyser<Ret>(
255        &self,
256        index: usize,
257        mut callback: impl for<'local> FnMut(&'local Pubkey, &'local AccountSharedData) -> Ret,
258    ) -> Ret {
259        let (pubkey, account) = self.get_unchecked(index);
260        callback(pubkey, account)
261    }
262
263    fn is_zero_lamport(&self, index: usize) -> bool {
264        self.get_unchecked(index).1.lamports() == 0
265    }
266
267    fn data_len(&self, index: usize) -> usize {
268        self.get_unchecked(index).1.data().len()
269    }
270
271    fn pubkey(&self, index: usize) -> &Pubkey {
272        self.get_unchecked(index).0
273    }
274
275    fn slot(&self, _index: usize) -> Slot {
276        self.target_slot()
277    }
278
279    fn target_slot(&self) -> Slot {
280        self.slot
281    }
282
283    fn len(&self) -> usize {
284        self.epoch_boundary_accounts.accounts_with_rewards.len()
285            + self.epoch_boundary_accounts.swept_vote_accounts.len()
286    }
287}
288
289#[derive(Debug, Default)]
290/// result of calculating the stake rewards at end of epoch
291pub(super) struct StakeRewardCalculation {
292    /// each individual stake account to reward
293    stake_rewards: Arc<PartitionedStakeRewards>,
294    /// total lamports across all `stake_rewards`
295    total_stake_rewards_lamports: u64,
296}
297
298#[derive(Debug)]
299struct CalculateValidatorRewardsResult {
300    reward_commissions: RewardCommissions,
301    stake_reward_calculation: StakeRewardCalculation,
302    point_value: PointValue,
303}
304
305impl Default for CalculateValidatorRewardsResult {
306    fn default() -> Self {
307        Self {
308            reward_commissions: RewardCommissions::default(),
309            stake_reward_calculation: StakeRewardCalculation::default(),
310            point_value: PointValue {
311                points: 0,
312                rewards: 0,
313            },
314        }
315    }
316}
317
318pub(super) struct CachedVoteAccounts<'a> {
319    /// Snapshot of vote account state from the beginning of the epoch prior to
320    /// the rewarded epoch. This snapshot state is saved a full epoch before
321    /// being used to prevent last minute commission rugs.
322    ///
323    /// Developer note: This field is `Option` to handle large bank warps
324    pub(super) snapshot_epoch_vote_accounts: Option<&'a VoteAccounts>,
325    /// Vote account state from the beginning of the rewarded epoch.
326    ///
327    /// Developer note: This field is `Option` to handle large bank warps
328    pub(super) rewarded_epoch_vote_accounts: Option<&'a VoteAccounts>,
329    /// Vote account state from the end of the rewarded epoch / beginning of the
330    /// distribution epoch.
331    pub(super) distribution_epoch_vote_accounts: &'a VoteAccounts,
332}
333
334/// hold reward calc info to avoid recalculation across functions
335pub(super) struct EpochRewardCalculateParamInfo<'a> {
336    pub(super) stake_history: StakeHistory,
337    pub(super) stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)>,
338    pub(super) cached_vote_accounts: CachedVoteAccounts<'a>,
339}
340
341/// Hold all results from calculating the rewards for partitioned distribution.
342/// This struct exists so we can have a function which does all the calculation with no
343/// side effects.
344#[derive(Debug)]
345pub(super) struct PartitionedRewardsCalculation {
346    reward_commissions: RewardCommissions,
347    stake_rewards: StakeRewardCalculation,
348    capitalization: u64,
349    point_value: PointValue,
350    /// Number of vote accounts in the distribution-epoch snapshot after
351    /// SIMD-0357 VAT filtering.
352    /// Surfaced for the `epoch_rewards` datapoint without re-running the
353    /// filter at distribution time.
354    num_filtered_vote_accounts: usize,
355}
356
357pub(crate) type StakeRewards = Vec<StakeReward>;
358
359#[derive(Debug, PartialEq)]
360pub struct KeyedRewardsAndNumPartitions {
361    pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
362    pub num_partitions: Option<u64>,
363}
364
365impl KeyedRewardsAndNumPartitions {
366    pub fn should_record(&self) -> bool {
367        !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
368    }
369}
370
371impl Bank {
372    pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
373        let keyed_rewards = self.rewards.read().unwrap().clone();
374        let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
375        // If partitioned epoch rewards are active and this Bank is the
376        // epoch-boundary block, populate num_partitions
377        let epoch_schedule = self.epoch_schedule();
378        let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
379        let is_first_block_in_epoch = self.epoch() > parent_epoch;
380
381        let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
382            .then_some(epoch_rewards_sysvar.num_partitions);
383        KeyedRewardsAndNumPartitions {
384            keyed_rewards,
385            num_partitions,
386        }
387    }
388
389    pub(crate) fn set_epoch_reward_status_calculation(
390        &mut self,
391        distribution_starting_block_height: u64,
392        stake_rewards: Arc<PartitionedStakeRewards>,
393    ) {
394        self.epoch_reward_status =
395            EpochRewardStatus::Active(EpochRewardPhase::Calculation(StartBlockHeightAndRewards {
396                distribution_starting_block_height,
397                all_stake_rewards: stake_rewards,
398            }));
399    }
400
401    pub(crate) fn set_epoch_reward_status_distribution(
402        &mut self,
403        distribution_starting_block_height: u64,
404        all_stake_rewards: Arc<PartitionedStakeRewards>,
405        partition_indices: Vec<Vec<usize>>,
406    ) {
407        self.epoch_reward_status = EpochRewardStatus::Active(EpochRewardPhase::Distribution(
408            StartBlockHeightAndPartitionedRewards {
409                distribution_starting_block_height,
410                all_stake_rewards,
411                partition_indices,
412            },
413        ));
414    }
415
416    /// # stake accounts to store in one block during partitioned reward interval
417    pub(super) fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 {
418        self.partitioned_rewards_stake_account_stores_per_block
419    }
420
421    /// Calculate the number of blocks required to distribute rewards to all stake accounts.
422    pub(super) fn get_reward_distribution_num_blocks(
423        &self,
424        rewards: &PartitionedStakeRewards,
425    ) -> u64 {
426        let total_stake_accounts = rewards.num_rewards();
427        if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() {
428            1
429        } else {
430            const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10;
431            let num_chunks = total_stake_accounts
432                .div_ceil(self.partitioned_rewards_stake_account_stores_per_block() as usize)
433                as u64;
434
435            // Limit the reward credit interval to 10% of the total number of slots in a epoch
436            num_chunks.clamp(
437                1,
438                (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1),
439            )
440        }
441    }
442
443    /// For testing only
444    pub fn force_reward_interval_end_for_tests(&mut self) {
445        self.epoch_reward_status = EpochRewardStatus::Inactive;
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use {
452        super::*,
453        crate::{
454            alpenglow_epoch_type::RewardEpochDelegatedStakes,
455            bank::{
456                NewEpochBundle, RewardsMetrics, SlotLeader, null_tracer,
457                tests::create_genesis_config,
458            },
459            bank_forks::BankForks,
460            genesis_utils::{
461                GenesisConfigInfo, ValidatorVoteKeypairs, activate_all_features_alpenglow,
462                create_genesis_config_with_leader, create_genesis_config_with_vote_accounts,
463                deactivate_features,
464            },
465            runtime_config::RuntimeConfig,
466            stake_utils,
467            sysvar_account::from_account,
468        },
469        assert_matches::assert_matches,
470        rand::Rng,
471        rayon::ThreadPoolBuilder,
472        solana_account::{
473            Account, ReadableAccount, WritableAccount, state_traits::StateMutWincode as _,
474        },
475        solana_accounts_db::{
476            accounts_db::{ACCOUNTS_DB_CONFIG_FOR_TESTING, AccountsDbConfig},
477            partitioned_rewards::PartitionedEpochRewardsConfig,
478        },
479        solana_clock::Epoch,
480        solana_epoch_schedule::EpochSchedule,
481        solana_hash::Hash,
482        solana_keypair::Keypair,
483        solana_native_token::LAMPORTS_PER_SOL,
484        solana_rent::Rent,
485        solana_reward_info::RewardType,
486        solana_signer::Signer,
487        solana_stake_interface::{stake_flags::StakeFlags, state::StakeStateV2},
488        solana_system_transaction as system_transaction,
489        solana_sysvar::epoch_rewards::{self, EpochRewards},
490        solana_vote::vote_transaction,
491        solana_vote_interface::state::{MAX_LOCKOUT_HISTORY, VoteStateV4, VoteStateVersions},
492        solana_vote_program::vote_state::{self, TowerSync, handler::VoteStateHandler},
493        std::{
494            collections::HashSet,
495            sync::{Arc, RwLock},
496        },
497    };
498
499    impl PartitionedStakeReward {
500        pub fn new_random() -> Self {
501            let mut rng = rand::rng();
502            let stake_reward = rng.random_range(1..200);
503            Self {
504                stake_pubkey: Pubkey::new_unique(),
505                inflation: InflationReward {
506                    stake: Stake {
507                        delegation: Delegation {
508                            voter_pubkey: Pubkey::new_unique(),
509                            stake: rng.random_range(1..200) + stake_reward,
510                            activation_epoch: 0,
511                            deactivation_epoch: u64::MAX,
512                            ..Default::default()
513                        },
514                        credits_observed: rng.random_range(1..200),
515                    },
516                    stake_reward,
517                    commission_bps: None,
518                },
519                block_reward: rng.random_range(0..10_000_000_000),
520            }
521        }
522
523        pub fn new_with_lamport_amounts(stake_reward: u64, block_reward: u64, stake: u64) -> Self {
524            Self {
525                stake_pubkey: Pubkey::new_unique(),
526                inflation: InflationReward {
527                    stake: Stake {
528                        delegation: Delegation {
529                            voter_pubkey: Pubkey::new_unique(),
530                            stake: stake + stake_reward,
531                            activation_epoch: 0,
532                            deactivation_epoch: u64::MAX,
533                            ..Default::default()
534                        },
535                        credits_observed: 0,
536                    },
537                    stake_reward,
538                    commission_bps: None,
539                },
540                block_reward,
541            }
542        }
543    }
544
545    pub fn build_partitioned_stake_rewards(
546        stake_rewards: &PartitionedStakeRewards,
547        partition_indices: &[Vec<usize>],
548    ) -> Vec<PartitionedStakeRewards> {
549        partition_indices
550            .iter()
551            .map(|partition_index| {
552                // partition_index is a Vec<usize> that contains the indices of the stake rewards
553                // that belong to this partition
554                partition_index
555                    .iter()
556                    .map(|&index| stake_rewards.get(index).unwrap().clone())
557                    .collect::<PartitionedStakeRewards>()
558            })
559            .collect::<Vec<_>>()
560    }
561
562    #[derive(Debug, PartialEq, Eq, Copy, Clone)]
563    enum RewardInterval {
564        /// the slot within the epoch is INSIDE the reward distribution interval
565        InsideInterval,
566        /// the slot within the epoch is OUTSIDE the reward distribution interval
567        OutsideInterval,
568    }
569
570    impl Bank {
571        /// Return `RewardInterval` enum for current bank
572        fn get_reward_interval(&self) -> RewardInterval {
573            if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) {
574                RewardInterval::InsideInterval
575            } else {
576                RewardInterval::OutsideInterval
577            }
578        }
579
580        fn is_calculated(&self) -> bool {
581            matches!(
582                self.epoch_reward_status,
583                EpochRewardStatus::Active(EpochRewardPhase::Calculation(_))
584            )
585        }
586
587        fn is_partitioned(&self) -> bool {
588            matches!(
589                self.epoch_reward_status,
590                EpochRewardStatus::Active(EpochRewardPhase::Distribution(_))
591            )
592        }
593
594        fn get_epoch_rewards_from_cache(
595            &self,
596            parent_hash: &Hash,
597        ) -> Option<Arc<PartitionedRewardsCalculation>> {
598            self.epoch_rewards_calculation_cache
599                .lock()
600                .unwrap()
601                .get(parent_hash)
602                .cloned()
603        }
604
605        fn get_epoch_rewards_cache_len(&self) -> usize {
606            self.epoch_rewards_calculation_cache.lock().unwrap().len()
607        }
608    }
609
610    pub(super) const SLOTS_PER_EPOCH: u64 = 32;
611
612    pub(super) struct RewardBank {
613        pub(super) bank: Arc<Bank>,
614        pub(super) voters: Vec<Pubkey>,
615        pub(super) stakers: Vec<Pubkey>,
616    }
617
618    /// Helper functions to create a bank that pays some rewards
619    pub(super) fn create_default_reward_bank(
620        expected_num_delegations: usize,
621        advance_num_slots: u64,
622    ) -> (RewardBank, Arc<RwLock<BankForks>>) {
623        create_reward_bank(
624            expected_num_delegations,
625            PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
626            advance_num_slots,
627        )
628    }
629
630    pub(super) fn create_reward_bank(
631        expected_num_delegations: usize,
632        stake_account_stores_per_block: u64,
633        advance_num_slots: u64,
634    ) -> (RewardBank, Arc<RwLock<BankForks>>) {
635        create_reward_bank_with_specific_stakes(
636            vec![2_000_000_000; expected_num_delegations],
637            stake_account_stores_per_block,
638            advance_num_slots,
639        )
640    }
641
642    pub(super) fn create_reward_bank_with_specific_stakes(
643        stakes: Vec<u64>,
644        stake_account_stores_per_block: u64,
645        advance_num_slots: u64,
646    ) -> (RewardBank, Arc<RwLock<BankForks>>) {
647        // Disable slot time reduction features as they will override the custom
648        // stores per block provided in this test helper.
649        let features_to_deactivate = crate::slot_params::slot_time_feature_ids().to_vec();
650        let validator_keypairs = (0..stakes.len())
651            .map(|_| ValidatorVoteKeypairs::new_rand())
652            .collect::<Vec<_>>();
653
654        let GenesisConfigInfo {
655            mut genesis_config, ..
656        } = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stakes);
657        genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
658        deactivate_features(&mut genesis_config, &features_to_deactivate);
659
660        let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING;
661        accounts_db_config.partitioned_epoch_rewards_config =
662            PartitionedEpochRewardsConfig::new_for_test(stake_account_stores_per_block);
663
664        let bank = Bank::new_from_genesis(
665            &genesis_config,
666            Arc::new(RuntimeConfig::default()),
667            Vec::new(),
668            None,
669            accounts_db_config,
670            None,
671            None,
672            Arc::default(),
673            None,
674            None,
675        );
676
677        // Fill bank_forks with banks with votes landing in the next slot
678        // Create enough banks such that vote account will root
679        populate_vote_accounts_with_votes(
680            &bank,
681            validator_keypairs.iter().map(|k| k.vote_keypair.pubkey()),
682            0,
683        );
684
685        // Advance some num slots; usually to the next epoch boundary to update
686        // EpochStakes
687        let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
688        let bank = Bank::new_from_parent_with_bank_forks(
689            &bank_forks,
690            bank,
691            SlotLeader::default(),
692            advance_num_slots,
693        );
694
695        (
696            RewardBank {
697                bank,
698                voters: validator_keypairs
699                    .iter()
700                    .map(|k| k.vote_keypair.pubkey())
701                    .collect(),
702                stakers: validator_keypairs
703                    .iter()
704                    .map(|k| k.stake_keypair.pubkey())
705                    .collect(),
706            },
707            bank_forks,
708        )
709    }
710
711    pub(super) fn modify_vote_state(
712        bank: &Bank,
713        vote_address: &Pubkey,
714        modify_fn: &dyn Fn(&mut VoteStateV4),
715    ) {
716        let mut vote_account = bank.get_account_with_fixed_root(vote_address).unwrap();
717        let vote_state_versions: VoteStateVersions = vote_account.state().unwrap();
718        let VoteStateVersions::V4(mut vote_state) = vote_state_versions else {
719            panic!("unexpected version");
720        };
721
722        modify_fn(&mut vote_state);
723
724        vote_account
725            .set_state(&VoteStateVersions::V4(vote_state))
726            .unwrap();
727        bank.store_account(vote_address, &vote_account);
728    }
729
730    pub(super) fn add_vote_and_stake_accounts(
731        bank: &Bank,
732        voters: &mut HashSet<Pubkey>,
733        stakers: &mut HashSet<Pubkey>,
734        stake_lamports: u64,
735        vote_lamports: u64,
736    ) -> Pubkey {
737        add_vote_and_stake_accounts_with_activation_and_deactivation_epochs(
738            bank,
739            voters,
740            stakers,
741            stake_lamports,
742            vote_lamports,
743            Epoch::MAX,
744            Epoch::MAX,
745        )
746    }
747
748    pub(super) fn add_vote_and_stake_accounts_with_activation_and_deactivation_epochs(
749        bank: &Bank,
750        voters: &mut HashSet<Pubkey>,
751        stakers: &mut HashSet<Pubkey>,
752        stake_lamports: u64,
753        vote_lamports: u64,
754        activation_epoch: Epoch,
755        deactivation_epoch: Epoch,
756    ) -> Pubkey {
757        let ((vote_address, mut vote_account), (stake_address, stake_account)) =
758            crate::stakes::tests::create_staked_node_accounts_with_activation_and_deactivation_epochs(
759                stake_lamports,
760                &bank.rent_collector.rent,
761                activation_epoch,
762                deactivation_epoch,
763            );
764        vote_account.checked_add_lamports(vote_lamports).unwrap();
765        bank.store_account_and_update_capitalization(&vote_address, &vote_account);
766        bank.store_account_and_update_capitalization(&stake_address, &stake_account);
767        voters.insert(vote_address);
768        stakers.insert(stake_address);
769
770        vote_address
771    }
772
773    /// Vote account addresses for cases with notable behavior during block
774    /// revenue sharing
775    #[derive(Debug)]
776    pub(super) struct BlockRevenueSharingCases {
777        /// Receives commission
778        pub(super) commission_collector: Pubkey,
779        /// Receives inflation commission to itself
780        pub(super) self_collector: Pubkey,
781        /// Receives inflation commission to an external collector
782        pub(super) external_collector: Pubkey,
783        /// Nothing staked to the validator
784        pub(super) zero_stake: Pubkey,
785        /// Does not pay VAT in the distribution epoch, normally receives
786        /// inflation commission to itself
787        pub(super) non_vat_self_collector: Pubkey,
788        /// Does not pay VAT in the distribution epoch, normally receives
789        /// inflation commission to an external collector
790        pub(super) non_vat_external_collector: Pubkey,
791        /// Stake goes to 0 in distribution epoch
792        pub(super) deactivating: Pubkey,
793        /// Stake goes to non-zero in distribution epoch.
794        ///
795        /// NOTE: This is the only vote account that receives its pending
796        /// delegator rewards.
797        pub(super) activating: Pubkey,
798    }
799
800    impl BlockRevenueSharingCases {
801        /// Validator vote accounts that are expected to pay out distributions
802        ///
803        /// NOTE: Zero stake and deactivating vote accounts do not distribute
804        /// pending delegator rewards, as they are all filtered from the VAT
805        /// set.
806        fn distributed(&self) -> [Pubkey; 2] {
807            [self.self_collector, self.external_collector]
808        }
809
810        /// Validators that have pending delegator rewards deducted, which is
811        /// all except for the activating one: it has zero stake in the rewarded
812        /// epoch, but non-zero in the distribution epoch.
813        fn swept(&self) -> [Pubkey; 6] {
814            [
815                self.self_collector,
816                self.external_collector,
817                self.zero_stake,
818                self.non_vat_self_collector,
819                self.non_vat_external_collector,
820                self.deactivating,
821            ]
822        }
823
824        /// Validators that have pending delegator rewards burned at the end of
825        /// distribution. Essentially, `swept() - distributed()`
826        fn burned(&self) -> [Pubkey; 4] {
827            [
828                self.zero_stake,
829                self.non_vat_self_collector,
830                self.non_vat_external_collector,
831                self.deactivating,
832            ]
833        }
834    }
835
836    pub(super) fn create_reward_bank_for_block_revenue_sharing(
837        stake_lamports: u64,
838        vote_lamports: u64,
839        pending_delegator_rewards: u64,
840        stake_account_stores_per_block: u64,
841    ) -> (RewardBank, Arc<RwLock<BankForks>>, BlockRevenueSharingCases) {
842        let GenesisConfigInfo {
843            mut genesis_config, ..
844        } = create_genesis_config_with_leader(
845            1_000_000 * LAMPORTS_PER_SOL,
846            &Pubkey::new_unique(),
847            42 * LAMPORTS_PER_SOL,
848        );
849        activate_all_features_alpenglow(&mut genesis_config);
850
851        // Disable slot time reduction features as they will override the custom
852        // stores per block provided in this test helper.
853        let features_to_deactivate = crate::slot_params::slot_time_feature_ids().to_vec();
854        deactivate_features(&mut genesis_config, &features_to_deactivate);
855
856        genesis_config.rent = Rent::default();
857        genesis_config.epoch_schedule = EpochSchedule::without_warmup();
858        let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING;
859        accounts_db_config.partitioned_epoch_rewards_config =
860            PartitionedEpochRewardsConfig::new_for_test(stake_account_stores_per_block);
861
862        let bank = Bank::new_from_genesis(
863            &genesis_config,
864            Arc::new(RuntimeConfig::default()),
865            Vec::new(),
866            None,
867            accounts_db_config,
868            None,
869            None,
870            Arc::default(),
871            None,
872            None,
873        );
874
875        let commission_collector = Pubkey::new_unique();
876        {
877            let mut commission_account = AccountSharedData::default();
878            commission_account.set_lamports(genesis_config.rent.minimum_balance(0));
879            bank.store_account_and_update_capitalization(
880                &commission_collector,
881                &commission_account,
882            );
883        }
884
885        let mut voters = HashSet::new();
886        let mut stakers = HashSet::new();
887
888        // Vote account receives commission
889        let self_collector = add_vote_and_stake_accounts(
890            &bank,
891            &mut voters,
892            &mut stakers,
893            stake_lamports,
894            vote_lamports + pending_delegator_rewards,
895        );
896        modify_vote_state(&bank, &self_collector, &|vote_state| {
897            vote_state.pending_delegator_rewards = pending_delegator_rewards;
898        });
899
900        // External commission collector
901        let external_collector = add_vote_and_stake_accounts(
902            &bank,
903            &mut voters,
904            &mut stakers,
905            stake_lamports,
906            vote_lamports + pending_delegator_rewards,
907        );
908        modify_vote_state(&bank, &external_collector, &|vote_state| {
909            vote_state.pending_delegator_rewards = pending_delegator_rewards;
910            vote_state.inflation_rewards_collector = commission_collector;
911        });
912
913        // Zero stake just resets rewards
914        let zero_stake = add_vote_and_stake_accounts(
915            &bank,
916            &mut voters,
917            &mut stakers,
918            0,
919            vote_lamports + pending_delegator_rewards,
920        );
921        modify_vote_state(&bank, &zero_stake, &|vote_state| {
922            vote_state.pending_delegator_rewards = pending_delegator_rewards;
923        });
924
925        // Non-VAT paying, loses all rewards
926        let non_vat_self_collector = add_vote_and_stake_accounts(
927            &bank,
928            &mut voters,
929            &mut stakers,
930            stake_lamports,
931            pending_delegator_rewards,
932        );
933        modify_vote_state(&bank, &non_vat_self_collector, &|vote_state| {
934            vote_state.pending_delegator_rewards = pending_delegator_rewards;
935        });
936
937        // Non-VAT paying external collector, loses all rewards
938        let non_vat_external_collector = add_vote_and_stake_accounts(
939            &bank,
940            &mut voters,
941            &mut stakers,
942            stake_lamports,
943            pending_delegator_rewards,
944        );
945        modify_vote_state(&bank, &non_vat_external_collector, &|vote_state| {
946            vote_state.pending_delegator_rewards = pending_delegator_rewards;
947            vote_state.inflation_rewards_collector = commission_collector;
948        });
949
950        // Activating stake gets everything back, same as zero stake
951        let activating = add_vote_and_stake_accounts_with_activation_and_deactivation_epochs(
952            &bank,
953            &mut voters,
954            &mut stakers,
955            stake_lamports,
956            vote_lamports + pending_delegator_rewards,
957            bank.epoch(),
958            Epoch::MAX,
959        );
960        modify_vote_state(&bank, &activating, &|vote_state| {
961            vote_state.pending_delegator_rewards = pending_delegator_rewards;
962        });
963
964        // Deactivating stake functions like activated stake
965        let deactivating = add_vote_and_stake_accounts_with_activation_and_deactivation_epochs(
966            &bank,
967            &mut voters,
968            &mut stakers,
969            stake_lamports,
970            vote_lamports + pending_delegator_rewards,
971            Epoch::MAX,
972            bank.epoch(),
973        );
974        modify_vote_state(&bank, &deactivating, &|vote_state| {
975            vote_state.pending_delegator_rewards = pending_delegator_rewards;
976        });
977
978        // Simplify capitalization calculations by giving everything to stakers
979        let commission = 0;
980        populate_vote_accounts_with_votes(&bank, voters.iter().copied(), commission);
981
982        // Go right before the first boundary
983        let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
984        let slots_per_epoch = bank.epoch_schedule().slots_per_epoch;
985        let bank = Bank::new_from_parent_with_bank_forks(
986            &bank_forks,
987            bank,
988            SlotLeader::default(),
989            slots_per_epoch - 1,
990        );
991
992        (
993            RewardBank {
994                bank,
995                voters: voters.into_iter().collect(),
996                stakers: stakers.into_iter().collect(),
997            },
998            bank_forks,
999            BlockRevenueSharingCases {
1000                commission_collector,
1001                self_collector,
1002                external_collector,
1003                zero_stake,
1004                non_vat_self_collector,
1005                non_vat_external_collector,
1006                deactivating,
1007                activating,
1008            },
1009        )
1010    }
1011
1012    pub(super) fn populate_vote_accounts_with_votes(
1013        bank: &Bank,
1014        vote_pubkeys: impl IntoIterator<Item = Pubkey>,
1015        commission: u8,
1016    ) {
1017        for vote_pubkey in vote_pubkeys {
1018            let mut vote_account = bank
1019                .get_account(&vote_pubkey)
1020                .unwrap_or_else(|| panic!("missing vote account {vote_pubkey:?}"));
1021            let mut vote_state = VoteStateHandler::new_v4(
1022                VoteStateV4::deserialize(vote_account.data(), &vote_pubkey).unwrap(),
1023            );
1024            vote_state.set_commission(commission);
1025            for i in 0..MAX_LOCKOUT_HISTORY + 42 {
1026                vote_state::process_slot_vote_unchecked(&mut vote_state, i as u64);
1027                let versioned = VoteStateVersions::V4(Box::new(vote_state.as_ref_v4().clone()));
1028                vote_account.set_state(&versioned).unwrap();
1029            }
1030            bank.store_account_and_update_capitalization(&vote_pubkey, &vote_account);
1031        }
1032    }
1033
1034    #[test]
1035    fn test_force_reward_interval_end() {
1036        let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
1037        let mut bank = Bank::new_for_tests(&genesis_config);
1038
1039        let expected_num = 100;
1040
1041        let stake_rewards = (0..expected_num)
1042            .map(|_| Some(PartitionedStakeReward::new_random()))
1043            .collect::<PartitionedStakeRewards>();
1044
1045        let partition_indices = vec![(0..expected_num).collect()];
1046
1047        bank.set_epoch_reward_status_distribution(
1048            bank.block_height() + REWARD_CALCULATION_NUM_BLOCKS,
1049            Arc::new(stake_rewards),
1050            partition_indices,
1051        );
1052        assert!(bank.get_reward_interval() == RewardInterval::InsideInterval);
1053
1054        bank.force_reward_interval_end_for_tests();
1055        assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval);
1056    }
1057
1058    /// Test get_reward_distribution_num_blocks during small epoch
1059    /// The num_credit_blocks should be cap to 10% of the total number of blocks in the epoch.
1060    #[test]
1061    fn test_get_reward_distribution_num_blocks_cap() {
1062        let (mut genesis_config, _mint_keypair) =
1063            create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
1064        genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false);
1065
1066        // Config stake reward distribution to be 10 per block
1067        let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING;
1068        accounts_db_config.partitioned_epoch_rewards_config =
1069            PartitionedEpochRewardsConfig::new_for_test(10);
1070
1071        let bank = Bank::new_from_genesis(
1072            &genesis_config,
1073            Arc::new(RuntimeConfig::default()),
1074            Vec::new(),
1075            None,
1076            accounts_db_config,
1077            None,
1078            Some(SlotLeader::new_unique()),
1079            Arc::default(),
1080            None,
1081            None,
1082        );
1083
1084        let stake_account_stores_per_block =
1085            bank.partitioned_rewards_stake_account_stores_per_block();
1086        assert_eq!(stake_account_stores_per_block, 10);
1087
1088        let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
1089        let bank =
1090            Bank::new_from_parent_with_bank_forks(&bank_forks, bank, SlotLeader::default(), 1);
1091        assert_eq!(
1092            bank.partitioned_rewards_stake_account_stores_per_block(),
1093            stake_account_stores_per_block
1094        );
1095
1096        let check_num_reward_distribution_blocks =
1097            |num_stakes: u64, expected_num_reward_distribution_blocks: u64| {
1098                // Given the short epoch, i.e. 32 slots, we should cap the number of reward distribution blocks to 32/10 = 3.
1099                let stake_rewards = (0..num_stakes)
1100                    .map(|_| Some(PartitionedStakeReward::new_random()))
1101                    .collect::<PartitionedStakeRewards>();
1102
1103                assert_eq!(
1104                    bank.get_reward_distribution_num_blocks(&stake_rewards),
1105                    expected_num_reward_distribution_blocks
1106                );
1107            };
1108
1109        for test_record in [
1110            // num_stakes, expected_num_reward_distribution_blocks
1111            (0, 1),
1112            (1, 1),
1113            (stake_account_stores_per_block, 1),
1114            (2 * stake_account_stores_per_block - 1, 2),
1115            (2 * stake_account_stores_per_block, 2),
1116            (3 * stake_account_stores_per_block - 1, 3),
1117            (3 * stake_account_stores_per_block, 3),
1118            (4 * stake_account_stores_per_block, 3), // cap at 3
1119            (5 * stake_account_stores_per_block, 3), //cap at 3
1120        ] {
1121            check_num_reward_distribution_blocks(test_record.0, test_record.1);
1122        }
1123    }
1124
1125    /// Test get_reward_distribution_num_blocks during normal epoch gives the expected result
1126    #[test]
1127    fn test_get_reward_distribution_num_blocks_normal() {
1128        agave_logger::setup();
1129        let (mut genesis_config, _mint_keypair) =
1130            create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
1131        genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
1132
1133        let bank = Bank::new_for_tests(&genesis_config);
1134
1135        // Given 8k rewards, it will take 2 blocks to credit all the rewards
1136        let expected_num = 8192;
1137        let stake_rewards = (0..expected_num)
1138            .map(|_| Some(PartitionedStakeReward::new_random()))
1139            .collect::<PartitionedStakeRewards>();
1140
1141        assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2);
1142    }
1143
1144    /// Test get_reward_distribution_num_blocks during warm up epoch gives the expected result.
1145    /// The num_credit_blocks should be 1 during warm up epoch.
1146    #[test]
1147    fn test_get_reward_distribution_num_blocks_warmup() {
1148        let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
1149
1150        let bank = Bank::new_for_tests(&genesis_config);
1151        let rewards = PartitionedStakeRewards::default();
1152        assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
1153    }
1154
1155    /// Test get_reward_distribution_num_blocks with `None` elements in the
1156    /// partitioned stake rewards. `None` elements can occur if for any stake
1157    /// delegation:
1158    /// * there is no payout or if any deserved payout is < 1 lamport
1159    /// * corresponding vote account was not found in cache and accounts-db
1160    #[test]
1161    fn test_get_reward_distribution_num_blocks_none() {
1162        let rewards_all = 8192;
1163        let expected_rewards_some = 6144;
1164
1165        let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
1166        let bank = Bank::new_for_tests(&genesis_config);
1167
1168        let rewards = (0..rewards_all)
1169            .map(|i| {
1170                if i % 4 == 0 {
1171                    None
1172                } else {
1173                    Some(PartitionedStakeReward::new_random())
1174                }
1175            })
1176            .collect::<PartitionedStakeRewards>();
1177        assert_eq!(rewards.rewards.len(), rewards_all);
1178        assert_eq!(rewards.num_rewards(), expected_rewards_some);
1179
1180        assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
1181    }
1182
1183    #[test]
1184    fn test_rewards_computation_and_partitioned_distribution_one_block() {
1185        agave_logger::setup();
1186
1187        let starting_slot = SLOTS_PER_EPOCH - 1;
1188        let (
1189            RewardBank {
1190                bank: mut previous_bank,
1191                ..
1192            },
1193            bank_forks,
1194        ) = create_default_reward_bank(100, starting_slot - 1);
1195
1196        // simulate block progress
1197        for slot in starting_slot..=(2 * SLOTS_PER_EPOCH) + 2 {
1198            let pre_cap = previous_bank.capitalization();
1199            let curr_bank = Bank::new_from_parent_with_bank_forks(
1200                bank_forks.as_ref(),
1201                previous_bank.clone(),
1202                SlotLeader::default(),
1203                slot,
1204            );
1205            let post_cap = curr_bank.capitalization();
1206
1207            if slot % SLOTS_PER_EPOCH == 0 {
1208                // This is the first block of the epoch. Reward computation should happen in this block.
1209                // assert reward compute status activated at epoch boundary
1210                assert_matches!(
1211                    curr_bank.get_reward_interval(),
1212                    RewardInterval::InsideInterval
1213                );
1214
1215                assert!(curr_bank.is_calculated());
1216
1217                // after reward calculation, the cache should be filled.
1218                assert!(
1219                    curr_bank
1220                        .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
1221                        .is_some()
1222                );
1223                assert_eq!(post_cap, pre_cap);
1224
1225                // Make a root the bank, which is the first bank in the epoch.
1226                // This will clear the cache.
1227                let _ = bank_forks.write().unwrap().set_root(slot, None, None);
1228                assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
1229            } else if slot == SLOTS_PER_EPOCH + 1 {
1230                // 1. when curr_slot == SLOTS_PER_EPOCH + 1, the 2nd block of
1231                // epoch 1, reward distribution should happen in this block.
1232                // however, all stake rewards are paid at this block therefore
1233                // reward_status should have transitioned to inactive. The cap
1234                // should increase accordingly.
1235                assert_matches!(
1236                    curr_bank.get_reward_interval(),
1237                    RewardInterval::OutsideInterval
1238                );
1239                let account = curr_bank
1240                    .get_account(&solana_sysvar::epoch_rewards::id())
1241                    .unwrap();
1242                let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1243                    from_account(&account).unwrap();
1244                assert_eq!(post_cap, pre_cap + epoch_rewards.distributed_rewards);
1245            } else {
1246                // 2. when curr_slot == SLOTS_PER_EPOCH + 2, the 3rd block of
1247                // epoch 1 (or any other slot). reward distribution should have
1248                // already completed. Therefore, reward_status should stay
1249                // inactive and cap should stay the same.
1250                assert_matches!(
1251                    curr_bank.get_reward_interval(),
1252                    RewardInterval::OutsideInterval
1253                );
1254
1255                // slot is not in rewards, cap should not change
1256                assert_eq!(post_cap, pre_cap);
1257            }
1258            // EpochRewards sysvar is created in the first block of epoch 1.
1259            // Ensure the sysvar persists thereafter.
1260            if slot >= SLOTS_PER_EPOCH {
1261                let epoch_rewards_lamports =
1262                    curr_bank.get_balance(&solana_sysvar::epoch_rewards::id());
1263                assert!(epoch_rewards_lamports > 0);
1264            }
1265            previous_bank = curr_bank;
1266        }
1267    }
1268
1269    /// Test rewards computation and partitioned rewards distribution at the
1270    /// epoch boundary (multiple reward distribution blocks)
1271    #[test]
1272    fn test_rewards_computation_and_partitioned_distribution_multi_blocks() {
1273        agave_logger::setup();
1274
1275        let starting_slot = SLOTS_PER_EPOCH - 1;
1276        let (
1277            RewardBank {
1278                bank: mut previous_bank,
1279                ..
1280            },
1281            bank_forks,
1282        ) = create_reward_bank(100, 50, starting_slot - 1);
1283        let mut starting_hash = None;
1284        let mut reward_distribution_completion_slot = None;
1285
1286        // simulate block progress
1287        for slot in starting_slot..=SLOTS_PER_EPOCH.saturating_mul(2) {
1288            let pre_cap = previous_bank.capitalization();
1289
1290            let pre_sysvar_account = previous_bank
1291                .get_account(&solana_sysvar::epoch_rewards::id())
1292                .unwrap_or_default();
1293            let pre_epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1294                from_account(&pre_sysvar_account).unwrap_or_default();
1295            let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
1296            let curr_bank = Bank::new_from_parent_with_bank_forks(
1297                bank_forks.as_ref(),
1298                previous_bank.clone(),
1299                SlotLeader::default(),
1300                slot,
1301            );
1302            let post_cap = curr_bank.capitalization();
1303
1304            if slot < SLOTS_PER_EPOCH {
1305                // Verify we haven't hit the rewards interval yet.
1306                assert_matches!(
1307                    curr_bank.get_reward_interval(),
1308                    RewardInterval::OutsideInterval
1309                );
1310                assert_eq!(post_cap, pre_cap);
1311            } else if slot == SLOTS_PER_EPOCH {
1312                // This is the first block of epoch 1. Reward computation should happen in this block.
1313                // assert reward compute status activated at epoch boundary
1314                assert_matches!(
1315                    curr_bank.get_reward_interval(),
1316                    RewardInterval::InsideInterval
1317                );
1318
1319                // calculation block, state should be calculated.
1320                assert!(curr_bank.is_calculated());
1321
1322                // after reward calculation, the cache should be filled.
1323                assert!(
1324                    curr_bank
1325                        .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
1326                        .is_some()
1327                );
1328                assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
1329                starting_hash = Some(curr_bank.parent_hash);
1330
1331                // Grab the number of slots to complete rewards payout from the
1332                // epoch rewards sysvar.
1333                let account = curr_bank
1334                    .get_account(&solana_sysvar::epoch_rewards::id())
1335                    .unwrap();
1336                let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1337                    from_account(&account).unwrap();
1338                reward_distribution_completion_slot =
1339                    Some(SLOTS_PER_EPOCH + epoch_rewards.num_partitions);
1340            } else if slot
1341                < reward_distribution_completion_slot
1342                    .expect("epoch boundary must set completion slot")
1343            {
1344                // Reward distribution should be active in this range.
1345                assert_matches!(
1346                    curr_bank.get_reward_interval(),
1347                    RewardInterval::InsideInterval
1348                );
1349                assert!(curr_bank.is_partitioned());
1350
1351                let account = curr_bank
1352                    .get_account(&solana_sysvar::epoch_rewards::id())
1353                    .unwrap();
1354                let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1355                    from_account(&account).unwrap();
1356                assert_eq!(
1357                    post_cap,
1358                    pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
1359                );
1360
1361                if slot == SLOTS_PER_EPOCH + 1 {
1362                    // The first block of the epoch has not rooted yet, so the
1363                    // cache should still have the results.
1364                    assert!(
1365                        curr_bank
1366                            .get_epoch_rewards_from_cache(&starting_hash.unwrap())
1367                            .is_some()
1368                    );
1369                    assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
1370
1371                    // Now make a root the first bank in the epoch.
1372                    // This should clear the cache.
1373                    let _ = bank_forks.write().unwrap().set_root(slot - 1, None, None);
1374                    assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
1375                }
1376            } else if slot
1377                == reward_distribution_completion_slot
1378                    .expect("epoch boundary must set completion slot")
1379            {
1380                // All stake rewards should have been paid by now. Therefore,
1381                // reward_status should have transitioned to inactive. The cap
1382                // should increase accordingly.
1383                assert_matches!(
1384                    curr_bank.get_reward_interval(),
1385                    RewardInterval::OutsideInterval
1386                );
1387
1388                let account = curr_bank
1389                    .get_account(&solana_sysvar::epoch_rewards::id())
1390                    .unwrap();
1391                let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1392                    from_account(&account).unwrap();
1393                assert_eq!(
1394                    post_cap,
1395                    pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
1396                );
1397            } else {
1398                // First slot after rewards payout. Verify we are outside the
1399                // interval and capitalization doesn't change this slot.
1400                assert_matches!(
1401                    curr_bank.get_reward_interval(),
1402                    RewardInterval::OutsideInterval
1403                );
1404                assert_eq!(post_cap, pre_cap);
1405                break;
1406            }
1407            previous_bank = curr_bank;
1408        }
1409    }
1410
1411    /// Test that lamports can be sent to stake accounts regardless of rewards period.
1412    #[test]
1413    fn test_rewards_period_system_transfer() {
1414        let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
1415        let validator_keypairs = vec![&validator_vote_keypairs];
1416        let GenesisConfigInfo {
1417            mut genesis_config,
1418            mint_keypair,
1419            ..
1420        } = create_genesis_config_with_vote_accounts(
1421            1_000_000_000,
1422            &validator_keypairs,
1423            vec![1_000_000_000; 1],
1424        );
1425
1426        // Add stake account to try to mutate
1427        let vote_key = validator_keypairs[0].vote_keypair.pubkey();
1428        let vote_account = genesis_config
1429            .accounts
1430            .iter()
1431            .find(|(address, _)| **address == vote_key)
1432            .map(|(_, account)| account)
1433            .unwrap()
1434            .clone();
1435
1436        let new_stake_signer = Keypair::new();
1437        let new_stake_address = new_stake_signer.pubkey();
1438        let new_stake_account = Account::from(stake_utils::create_stake_account(
1439            &new_stake_address,
1440            &vote_key,
1441            &vote_account.into(),
1442            &genesis_config.rent,
1443            2_000_000_000,
1444        ));
1445        genesis_config
1446            .accounts
1447            .extend(vec![(new_stake_address, new_stake_account)]);
1448
1449        let (mut previous_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1450        let num_slots_in_epoch = previous_bank.get_slots_in_epoch(previous_bank.epoch());
1451        assert_eq!(num_slots_in_epoch, 32);
1452
1453        let transfer_amount = 5_000;
1454
1455        for slot in 1..=num_slots_in_epoch + 2 {
1456            let bank = Bank::new_from_parent_with_bank_forks(
1457                bank_forks.as_ref(),
1458                previous_bank.clone(),
1459                SlotLeader::default(),
1460                slot,
1461            );
1462
1463            // Fill bank_forks with banks with votes landing in the next slot
1464            // So that rewards will be paid out at the epoch boundary, i.e. slot = 32
1465            let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash());
1466            let vote = vote_transaction::new_tower_sync_transaction(
1467                tower_sync,
1468                previous_bank.last_blockhash(),
1469                &validator_vote_keypairs.node_keypair,
1470                &validator_vote_keypairs.vote_keypair,
1471                &validator_vote_keypairs.vote_keypair,
1472                None,
1473            );
1474            bank.process_transaction(&vote).unwrap();
1475
1476            // Insert a transfer transaction from the mint to new stake account
1477            let system_tx = system_transaction::transfer(
1478                &mint_keypair,
1479                &new_stake_address,
1480                transfer_amount,
1481                bank.last_blockhash(),
1482            );
1483            let system_result = bank.process_transaction(&system_tx);
1484
1485            // Credits should always succeed
1486            assert!(system_result.is_ok());
1487
1488            // Push a dummy blockhash, so that the latest_blockhash() for the transfer transaction in each
1489            // iteration are different. Otherwise, all those transactions will be the same, and will not be
1490            // executed by the bank except the first one.
1491            bank.register_unique_recent_blockhash_for_test();
1492            previous_bank = bank;
1493        }
1494    }
1495
1496    #[test]
1497    fn test_get_rewards_and_partitions() {
1498        let starting_slot = SLOTS_PER_EPOCH - 1;
1499        let num_rewards = 100;
1500        let stake_account_stores_per_block = 50;
1501        let (RewardBank { bank, .. }, bank_forks) =
1502            create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
1503
1504        // Slot before the epoch boundary contains empty rewards (since fees are
1505        // off), and no partitions because not at the epoch boundary
1506        assert_eq!(
1507            bank.get_rewards_and_num_partitions(),
1508            KeyedRewardsAndNumPartitions {
1509                keyed_rewards: vec![],
1510                num_partitions: None,
1511            }
1512        );
1513
1514        let epoch_boundary_bank = Bank::new_from_parent_with_bank_forks(
1515            bank_forks.as_ref(),
1516            bank,
1517            SlotLeader::default(),
1518            SLOTS_PER_EPOCH,
1519        );
1520        // Slot at the epoch boundary contains voting rewards only, as well as partition data
1521        let KeyedRewardsAndNumPartitions {
1522            keyed_rewards,
1523            num_partitions,
1524        } = epoch_boundary_bank.get_rewards_and_num_partitions();
1525        for (_pubkey, reward) in keyed_rewards.iter() {
1526            assert_eq!(reward.reward_type, RewardType::Voting);
1527        }
1528        assert_eq!(keyed_rewards.len(), num_rewards);
1529        let expected_num_partitions = (num_rewards as u64)
1530            .div_ceil(epoch_boundary_bank.partitioned_rewards_stake_account_stores_per_block())
1531            .clamp(1, (SLOTS_PER_EPOCH / 10).max(1));
1532        assert_eq!(num_partitions, Some(expected_num_partitions));
1533
1534        let mut total_staking_rewards = 0;
1535        let mut previous_bank = epoch_boundary_bank;
1536        for partition_index in 0..expected_num_partitions {
1537            let partition_bank = Arc::new(Bank::new_from_parent(
1538                previous_bank,
1539                SlotLeader::default(),
1540                SLOTS_PER_EPOCH + partition_index + 1,
1541            ));
1542            // Slot after the epoch boundary contains partitioned staking
1543            // rewards, and no partition metadata because it's not an epoch
1544            // boundary bank.
1545            let KeyedRewardsAndNumPartitions {
1546                keyed_rewards,
1547                num_partitions,
1548            } = partition_bank.get_rewards_and_num_partitions();
1549            for (_pubkey, reward) in keyed_rewards.iter() {
1550                assert_eq!(reward.reward_type, RewardType::Staking);
1551            }
1552            total_staking_rewards += keyed_rewards.len();
1553            assert_eq!(num_partitions, None);
1554            previous_bank = partition_bank;
1555        }
1556
1557        // All rewards are recorded
1558        assert_eq!(total_staking_rewards, num_rewards);
1559        let bank = Bank::new_from_parent(
1560            previous_bank,
1561            SlotLeader::default(),
1562            SLOTS_PER_EPOCH + expected_num_partitions + 1,
1563        );
1564        // Next slot contains empty rewards (since fees are off), and no
1565        // partitions because not at the epoch boundary
1566        assert_eq!(
1567            bank.get_rewards_and_num_partitions(),
1568            KeyedRewardsAndNumPartitions {
1569                keyed_rewards: vec![],
1570                num_partitions: None,
1571            }
1572        );
1573    }
1574
1575    #[test]
1576    fn test_rewards_and_partitions_should_record() {
1577        let reward = RewardInfo {
1578            reward_type: RewardType::Voting,
1579            lamports: 55,
1580            post_balance: 5555,
1581            commission_bps: Some(500),
1582        };
1583
1584        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1585            keyed_rewards: vec![],
1586            num_partitions: None,
1587        };
1588        assert!(!rewards_and_partitions.should_record());
1589
1590        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1591            keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1592            num_partitions: None,
1593        };
1594        assert!(rewards_and_partitions.should_record());
1595
1596        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1597            keyed_rewards: vec![],
1598            num_partitions: Some(42),
1599        };
1600        assert!(rewards_and_partitions.should_record());
1601
1602        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1603            keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1604            num_partitions: Some(42),
1605        };
1606        assert!(rewards_and_partitions.should_record());
1607    }
1608
1609    #[test]
1610    fn test_reward_epoch_delegated_stakes_excludes_vat() {
1611        let (mut genesis_config, _mint_keypair) = create_genesis_config(500);
1612        activate_all_features_alpenglow(&mut genesis_config);
1613        let bank = Bank::new_for_tests(&genesis_config);
1614        let first_normal_slot = bank.epoch_schedule().first_normal_slot;
1615        let slots_per_epoch = bank.epoch_schedule().slots_per_epoch;
1616
1617        let stake_amount = 10_000;
1618        {
1619            let stakes = bank.stakes_cache.stakes();
1620            assert!(stakes.vote_accounts().as_ref().is_empty());
1621            assert!(stakes.stake_delegations().is_empty());
1622        }
1623
1624        let ((non_vat_vote_address, non_vat_vote_account), (stake_address, stake_account)) =
1625            crate::stakes::tests::create_staked_node_accounts(
1626                stake_amount,
1627                &bank.rent_collector.rent,
1628            );
1629
1630        bank.store_account(&non_vat_vote_address, &non_vat_vote_account);
1631        bank.store_account(&stake_address, &stake_account);
1632
1633        let ((vat_vote_address, mut vat_vote_account), (stake_address, stake_account)) =
1634            crate::stakes::tests::create_staked_node_accounts(
1635                stake_amount,
1636                &bank.rent_collector.rent,
1637            );
1638        vat_vote_account.set_lamports(bank.vat_to_burn_per_epoch() * 5);
1639
1640        bank.store_account(&vat_vote_address, &vat_vote_account);
1641        bank.store_account(&stake_address, &stake_account);
1642
1643        // Advance to first normal slot
1644        let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
1645        let bank = Bank::new_from_parent_with_bank_forks(
1646            &bank_forks,
1647            bank,
1648            SlotLeader::default(),
1649            first_normal_slot,
1650        );
1651        drop(bank_forks); // so that `Arc::into_inner` succeeds
1652        let mut bank = Arc::into_inner(bank).unwrap();
1653
1654        let (
1655            (unstaked_vat_vote_address, mut unstaked_vat_vote_account),
1656            (stake_address, mut stake_account),
1657        ) = crate::stakes::tests::create_staked_node_accounts(
1658            stake_amount,
1659            &bank.rent_collector.rent,
1660        );
1661        unstaked_vat_vote_account.set_lamports(bank.vat_to_burn_per_epoch() * 5);
1662        {
1663            // Will activate in next epoch, so validator still has 0 stake in
1664            // rewarded epoch
1665            let state: StakeStateV2 = stake_account.state().unwrap();
1666            let meta = state.meta().unwrap();
1667            let mut stake = state.stake().unwrap();
1668            stake.delegation.activation_epoch = bank.epoch();
1669
1670            stake_account
1671                .set_state(&StakeStateV2::Stake(meta, stake, StakeFlags::empty()))
1672                .expect("set_state");
1673        }
1674        bank.store_account(&unstaked_vat_vote_address, &unstaked_vat_vote_account);
1675        bank.store_account(&stake_address, &stake_account);
1676
1677        {
1678            let stakes = bank.stakes_cache.stakes();
1679            assert_eq!(stakes.vote_accounts().len(), 3);
1680            assert_eq!(stakes.stake_delegations().len(), 3);
1681        }
1682
1683        // Mimic some of the early work in `Bank::new_from_parent`
1684        bank.slot = bank.slot() + slots_per_epoch;
1685        bank.epoch += 1;
1686
1687        // Simulate the steps in `compute_new_epoch_caches_and_rewards`
1688        let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
1689        let mut rewards_metrics = RewardsMetrics::default();
1690        let NewEpochBundle {
1691            stake_history: _,
1692            unfiltered_distribution_vote_accounts,
1693            delegated_stakes: _,
1694            reward_epoch_delegated_stakes,
1695            inert_stake_delegations: _,
1696            filtered_distribution_vote_accounts,
1697            rewards_calculation: _,
1698            calculate_activated_stake_time_us: _,
1699            update_rewards_with_thread_pool_time_us: _,
1700        } = bank.compute_new_epoch_caches_and_rewards(
1701            &thread_pool,
1702            bank.epoch() - 1,
1703            null_tracer(),
1704            &mut rewards_metrics,
1705        );
1706        unfiltered_distribution_vote_accounts
1707            .get(&non_vat_vote_address)
1708            .unwrap();
1709        unfiltered_distribution_vote_accounts
1710            .get(&vat_vote_address)
1711            .unwrap();
1712        unfiltered_distribution_vote_accounts
1713            .get(&unstaked_vat_vote_address)
1714            .unwrap();
1715
1716        assert!(
1717            filtered_distribution_vote_accounts
1718                .get(&non_vat_vote_address)
1719                .is_none()
1720        );
1721        filtered_distribution_vote_accounts
1722            .get(&unstaked_vat_vote_address)
1723            .unwrap();
1724        filtered_distribution_vote_accounts
1725            .get(&vat_vote_address)
1726            .unwrap();
1727
1728        // Filtered out during `RewardEpochDelegatedStakes::set`
1729        assert!(
1730            !reward_epoch_delegated_stakes
1731                .delegated_stakes
1732                .contains_key(&non_vat_vote_address)
1733        );
1734        assert!(
1735            reward_epoch_delegated_stakes
1736                .delegated_stakes
1737                .contains_key(&vat_vote_address)
1738        );
1739
1740        // Even though the validator isn't staked in the rewarded epoch, it still
1741        // has an entry in the map because there is an activating stake account
1742        // delegated to it
1743        assert_eq!(
1744            *reward_epoch_delegated_stakes
1745                .delegated_stakes
1746                .get(&unstaked_vat_vote_address)
1747                .unwrap(),
1748            0
1749        );
1750
1751        // actually advance to the next epoch, see that everything lines up
1752        bank.slot = bank.slot() - slots_per_epoch;
1753        bank.epoch -= 1;
1754        let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
1755        let bank = Bank::new_from_parent_with_bank_forks(
1756            &bank_forks,
1757            bank,
1758            SlotLeader::default(),
1759            first_normal_slot + slots_per_epoch,
1760        );
1761        let fetched_reward_epoch_delegated_stakes = RewardEpochDelegatedStakes::get(&bank).unwrap();
1762        assert_eq!(
1763            fetched_reward_epoch_delegated_stakes,
1764            reward_epoch_delegated_stakes
1765        );
1766    }
1767
1768    fn epoch_boundary_accounts_for_test(
1769        account_with_rewards_addresses: &[Pubkey],
1770        swept_vote_account_addresses: &[Pubkey],
1771    ) -> EpochBoundaryAccounts {
1772        let reward_info = RewardInfo {
1773            reward_type: RewardType::Staking,
1774            lamports: 0,
1775            post_balance: 0,
1776            commission_bps: None,
1777        };
1778        EpochBoundaryAccounts {
1779            accounts_with_rewards: account_with_rewards_addresses
1780                .iter()
1781                .map(|address| (*address, reward_info, AccountSharedData::default()))
1782                .collect(),
1783            swept_vote_accounts: swept_vote_account_addresses
1784                .iter()
1785                .map(|address| (*address, AccountSharedData::default()))
1786                .collect(),
1787            ..Default::default()
1788        }
1789    }
1790
1791    #[test]
1792    fn test_epoch_boundary_accounts_storable_get() {
1793        let first_len = 10;
1794        let account_with_rewards_addresses = std::iter::repeat_with(Pubkey::new_unique)
1795            .take(first_len)
1796            .collect::<Vec<_>>();
1797        let second_len = 2;
1798        let swept_vote_account_addresses = std::iter::repeat_with(Pubkey::new_unique)
1799            .take(second_len)
1800            .collect::<Vec<_>>();
1801
1802        let accounts = epoch_boundary_accounts_for_test(
1803            &account_with_rewards_addresses,
1804            &swept_vote_account_addresses,
1805        );
1806        let storable_accounts = EpochBoundaryAccountsStorable {
1807            slot: 0,
1808            epoch_boundary_accounts: &accounts,
1809        };
1810        assert_eq!(
1811            *storable_accounts.get_unchecked(0).0,
1812            account_with_rewards_addresses[0]
1813        );
1814        assert_eq!(
1815            *storable_accounts.get_unchecked(first_len - 1).0,
1816            account_with_rewards_addresses[first_len - 1]
1817        );
1818        assert_eq!(
1819            *storable_accounts.get_unchecked(first_len).0,
1820            swept_vote_account_addresses[0]
1821        );
1822        assert_eq!(
1823            *storable_accounts
1824                .get_unchecked(first_len + second_len - 1)
1825                .0,
1826            swept_vote_account_addresses[second_len - 1]
1827        );
1828        assert_eq!(
1829            *storable_accounts
1830                .get_unchecked(first_len + second_len - 1)
1831                .0,
1832            swept_vote_account_addresses[second_len - 1]
1833        );
1834        assert_eq!(storable_accounts.len(), first_len + second_len);
1835    }
1836
1837    #[test]
1838    #[should_panic(expected = "index out of bounds: the len is 2 but the index is 2")]
1839    fn test_epoch_boundary_accounts_storable_get_past_range() {
1840        let first_len = 10;
1841        let account_with_rewards_addresses = std::iter::repeat_with(Pubkey::new_unique)
1842            .take(first_len)
1843            .collect::<Vec<_>>();
1844        let second_len = 2;
1845        let swept_vote_account_addresses = std::iter::repeat_with(Pubkey::new_unique)
1846            .take(second_len)
1847            .collect::<Vec<_>>();
1848
1849        let accounts = epoch_boundary_accounts_for_test(
1850            &account_with_rewards_addresses,
1851            &swept_vote_account_addresses,
1852        );
1853        let storable_accounts = EpochBoundaryAccountsStorable {
1854            slot: 0,
1855            epoch_boundary_accounts: &accounts,
1856        };
1857        let _ = storable_accounts.get_unchecked(first_len + second_len);
1858    }
1859
1860    #[test]
1861    fn test_block_rewards_computation_and_partitioned_distribution() {
1862        agave_logger::setup();
1863
1864        let stake_lamports = 1_000_000_000;
1865        let vote_lamports = 10_000_000_000; // more than enough for VAT
1866        let pending_delegator_rewards = 1_000_000;
1867        let stores_per_block = 2; // get through all of them in two blocks
1868        let (
1869            RewardBank {
1870                bank: mut previous_bank,
1871                voters,
1872                stakers,
1873            },
1874            bank_forks,
1875            cases,
1876        ) = create_reward_bank_for_block_revenue_sharing(
1877            stake_lamports,
1878            vote_lamports,
1879            pending_delegator_rewards,
1880            stores_per_block,
1881        );
1882
1883        // Right before the epoch rollover
1884        let starting_slot = previous_bank.slot();
1885        let slots_per_epoch = previous_bank.epoch_schedule().slots_per_epoch;
1886        assert_eq!(starting_slot + 1, slots_per_epoch);
1887
1888        // only the activating stake case doesn't get swept
1889        let pre_unswept = previous_bank.get_balance(&cases.activating);
1890
1891        let completion_slot = slots_per_epoch + 3;
1892        for slot in slots_per_epoch..=completion_slot {
1893            let pre_cap = previous_bank.capitalization();
1894            let pre_sysvar_account = previous_bank
1895                .get_account(&epoch_rewards::id())
1896                .unwrap_or_default();
1897            let pre_epoch_rewards: EpochRewards =
1898                from_account(&pre_sysvar_account).unwrap_or_default();
1899            let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
1900
1901            let curr_bank = Bank::new_from_parent_with_bank_forks(
1902                bank_forks.as_ref(),
1903                previous_bank.clone(),
1904                SlotLeader::default(),
1905                slot,
1906            );
1907            let post_cap = curr_bank.capitalization();
1908            let rent = &curr_bank.rent_collector.rent;
1909
1910            if slot == slots_per_epoch {
1911                // This is the first block of the epoch. Reward computation should happen in this block.
1912                // assert reward compute status activated at epoch boundary
1913                assert_matches!(
1914                    curr_bank.get_reward_interval(),
1915                    RewardInterval::InsideInterval
1916                );
1917                assert!(curr_bank.is_calculated());
1918
1919                // All vote accounts had field reset
1920                for vote_address in voters.iter() {
1921                    let vote_account = curr_bank.get_account_with_fixed_root(vote_address).unwrap();
1922                    let vote_state_versions: VoteStateVersions = vote_account.state().unwrap();
1923                    let VoteStateVersions::V4(vote_state) = vote_state_versions else {
1924                        panic!("unexpected version");
1925                    };
1926                    assert_eq!(vote_state.pending_delegator_rewards, 0);
1927                }
1928
1929                // Epoch rewards sysvar has pending delegator rewards from *all*
1930                // vote accounts, even those that will be burned at the end
1931                let epoch_rewards_sysvar = curr_bank.get_account(&epoch_rewards::id()).unwrap();
1932                let block_rewards_swept = pending_delegator_rewards * cases.swept().len() as u64;
1933                assert_eq!(
1934                    epoch_rewards_sysvar.lamports(),
1935                    block_rewards_swept + rent.minimum_balance(epoch_rewards_sysvar.data().len())
1936                );
1937                assert_eq!(
1938                    pre_unswept - curr_bank.vat_to_burn_per_epoch(),
1939                    curr_bank.get_balance(&cases.activating)
1940                );
1941
1942                // Make a root the bank, which is the first bank in the epoch.
1943                // This will clear the cache.
1944                let _ = bank_forks.write().unwrap().set_root(slot, None, None);
1945                assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
1946            } else if slot == slots_per_epoch + 1 {
1947                // Reward distribution should be active in this range.
1948                assert_matches!(
1949                    curr_bank.get_reward_interval(),
1950                    RewardInterval::InsideInterval
1951                );
1952                assert!(curr_bank.is_partitioned());
1953
1954                let account = curr_bank.get_account(&epoch_rewards::id()).unwrap();
1955                let epoch_rewards: EpochRewards = from_account(&account).unwrap();
1956                assert_eq!(
1957                    post_cap,
1958                    pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
1959                );
1960
1961                // Extra lamports haven't been burned yet
1962                assert!(account.lamports() > rent.minimum_balance(account.data().len()));
1963            } else if slot == slots_per_epoch + 2 {
1964                // Reward distribution should complete in this block.
1965                assert_matches!(
1966                    curr_bank.get_reward_interval(),
1967                    RewardInterval::OutsideInterval
1968                );
1969                let account = curr_bank.get_account(&epoch_rewards::id()).unwrap();
1970                let epoch_rewards: EpochRewards = from_account(&account).unwrap();
1971                let burned = pending_delegator_rewards * cases.burned().len() as u64;
1972                assert_eq!(
1973                    post_cap,
1974                    pre_cap + epoch_rewards.distributed_rewards - burned
1975                );
1976
1977                for stake_address in stakers.iter() {
1978                    let stake_account = curr_bank
1979                        .get_account_with_fixed_root(stake_address)
1980                        .unwrap();
1981                    let stake_state: StakeStateV2 = stake_account.state().unwrap();
1982                    let delegation = stake_state.delegation().unwrap();
1983                    // Check that stake accounts expected to receive a distribution, do.
1984                    if cases.distributed().contains(&delegation.voter_pubkey) {
1985                        assert_eq!(
1986                            stake_account.lamports(),
1987                            delegation.stake
1988                                + rent.minimum_balance(StakeStateV2::size_of())
1989                                + pending_delegator_rewards
1990                        );
1991                    } else {
1992                        assert_eq!(
1993                            stake_account.lamports(),
1994                            delegation.stake + rent.minimum_balance(StakeStateV2::size_of())
1995                        );
1996                    }
1997                }
1998            } else if slot == completion_slot {
1999                // outside the interval, nothing changes
2000                assert_matches!(
2001                    curr_bank.get_reward_interval(),
2002                    RewardInterval::OutsideInterval
2003                );
2004
2005                // slot is not in rewards, cap should not change
2006                assert_eq!(post_cap, pre_cap);
2007
2008                // epoch rewards burned down to the rent exempt minimum
2009                let account = curr_bank.get_account(&epoch_rewards::id()).unwrap();
2010                assert_eq!(
2011                    account.lamports(),
2012                    rent.minimum_balance(account.data().len())
2013                );
2014            }
2015            previous_bank = curr_bank;
2016        }
2017    }
2018}