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
24const REWARD_CALCULATION_NUM_BLOCKS: u64 = 1;
27
28#[derive(Debug, Clone, PartialEq)]
30pub(crate) struct PartitionedStakeReward {
31 pub stake_pubkey: Pubkey,
33 pub inflation: InflationReward,
35 pub block_reward: u64,
37}
38
39#[derive(Debug, Clone, PartialEq)]
41pub(crate) struct InflationReward {
42 pub stake: Stake,
44 pub stake_reward: u64,
46 pub commission_bps: Option<u16>,
52}
53
54#[derive(Debug, Default, PartialEq)]
56pub(crate) struct PartitionedStakeRewards {
57 rewards: Vec<Option<PartitionedStakeReward>>,
59 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 pub(crate) fn num_rewards(&self) -> usize {
74 self.num_rewards
75 }
76
77 pub(crate) fn total_len(&self) -> usize {
79 self.rewards.len()
80 }
81
82 pub(crate) fn get(&self, index: usize) -> Option<&Option<PartitionedStakeReward>> {
83 self.rewards.get(index)
84 }
85
86 pub(crate) fn enumerated_rewards_iter(
87 &self,
88 ) -> impl Iterator<Item = (usize, &PartitionedStakeReward)> {
89 self.rewards
90 .iter()
91 .enumerate()
92 .filter_map(|(index, reward)| reward.as_ref().map(|reward| (index, reward)))
93 }
94
95 fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit<Option<PartitionedStakeReward>>] {
96 self.rewards.spare_capacity_mut()
97 }
98
99 unsafe fn assume_init(&mut self, num_stake_rewards: usize, 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 pub(crate) distribution_starting_block_height: u64,
142 pub(crate) all_stake_rewards: Arc<PartitionedStakeRewards>,
144}
145
146#[derive(Debug, Clone, PartialEq)]
147pub(crate) struct StartBlockHeightAndPartitionedRewards {
148 pub(crate) distribution_starting_block_height: u64,
150
151 pub(crate) all_stake_rewards: Arc<PartitionedStakeRewards>,
153
154 pub(crate) partition_indices: Vec<Vec<usize>>,
158}
159
160#[derive(Debug, Clone, PartialEq, Default)]
162pub(crate) enum EpochRewardStatus {
163 Active(EpochRewardPhase),
168 #[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 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#[derive(Debug, Default)]
195pub(super) struct RewardCommissionLamportAmounts {
196 pub(super) distributed_lamports: u64,
202 pub(super) distributed_to_incinerator_lamports: u64,
208 pub(super) burned_lamports: u64,
210}
211
212#[derive(Debug, Default)]
213pub(super) struct RewardCommissionAccounts {
214 pub(super) accounts_with_rewards: Vec<(Pubkey, RewardInfo, AccountSharedData)>,
216 pub(super) amounts: RewardCommissionLamportAmounts,
218}
219
220pub(super) struct RewardCommissionAccountsStorable<'a> {
222 pub slot: Slot,
223 pub reward_commission_accounts: &'a RewardCommissionAccounts,
224}
225
226impl<'a> StorableAccounts<'a> for RewardCommissionAccountsStorable<'a> {
227 fn account<Ret>(
228 &self,
229 index: usize,
230 mut callback: impl for<'local> FnMut(AccountForStorage<'local>) -> Ret,
231 ) -> Ret {
232 let (pubkey, _, account) = &self.reward_commission_accounts.accounts_with_rewards[index];
233 callback((pubkey, account).into())
234 }
235
236 fn account_for_geyser<Ret>(
237 &self,
238 index: usize,
239 mut callback: impl for<'local> FnMut(&'local Pubkey, &'local AccountSharedData) -> Ret,
240 ) -> Ret {
241 let (pubkey, _, account) = &self.reward_commission_accounts.accounts_with_rewards[index];
242 callback(pubkey, account)
243 }
244
245 fn is_zero_lamport(&self, index: usize) -> bool {
246 self.reward_commission_accounts.accounts_with_rewards[index]
247 .2
248 .lamports()
249 == 0
250 }
251
252 fn data_len(&self, index: usize) -> usize {
253 self.reward_commission_accounts.accounts_with_rewards[index]
254 .2
255 .data()
256 .len()
257 }
258
259 fn pubkey(&self, index: usize) -> &Pubkey {
260 &self.reward_commission_accounts.accounts_with_rewards[index].0
261 }
262
263 fn slot(&self, _index: usize) -> Slot {
264 self.target_slot()
265 }
266
267 fn target_slot(&self) -> Slot {
268 self.slot
269 }
270
271 fn len(&self) -> usize {
272 self.reward_commission_accounts.accounts_with_rewards.len()
273 }
274}
275
276#[derive(Debug, Default)]
277pub(super) struct StakeRewardCalculation {
279 stake_rewards: Arc<PartitionedStakeRewards>,
281 total_stake_rewards_lamports: u64,
283}
284
285#[derive(Debug)]
286struct CalculateValidatorRewardsResult {
287 reward_commissions: RewardCommissions,
288 stake_reward_calculation: StakeRewardCalculation,
289 point_value: PointValue,
290}
291
292impl Default for CalculateValidatorRewardsResult {
293 fn default() -> Self {
294 Self {
295 reward_commissions: RewardCommissions::default(),
296 stake_reward_calculation: StakeRewardCalculation::default(),
297 point_value: PointValue {
298 points: 0,
299 rewards: 0,
300 },
301 }
302 }
303}
304
305pub(super) struct CachedVoteAccounts<'a> {
306 pub(super) snapshot_epoch_vote_accounts: Option<&'a VoteAccounts>,
312 pub(super) rewarded_epoch_vote_accounts: Option<&'a VoteAccounts>,
316 pub(super) distribution_epoch_vote_accounts: &'a VoteAccounts,
319}
320
321pub(super) struct EpochRewardCalculateParamInfo<'a> {
323 pub(super) stake_history: StakeHistory,
324 pub(super) stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)>,
325 pub(super) cached_vote_accounts: CachedVoteAccounts<'a>,
326}
327
328#[derive(Debug)]
332pub(super) struct PartitionedRewardsCalculation {
333 reward_commissions: RewardCommissions,
334 stake_rewards: StakeRewardCalculation,
335 capitalization: u64,
336 point_value: PointValue,
337 num_filtered_vote_accounts: usize,
342}
343
344pub(crate) type StakeRewards = Vec<StakeReward>;
345
346#[derive(Debug, PartialEq)]
347pub struct KeyedRewardsAndNumPartitions {
348 pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
349 pub num_partitions: Option<u64>,
350}
351
352impl KeyedRewardsAndNumPartitions {
353 pub fn should_record(&self) -> bool {
354 !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
355 }
356}
357
358impl Bank {
359 pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
360 let keyed_rewards = self.rewards.read().unwrap().clone();
361 let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
362 let epoch_schedule = self.epoch_schedule();
365 let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
366 let is_first_block_in_epoch = self.epoch() > parent_epoch;
367
368 let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
369 .then_some(epoch_rewards_sysvar.num_partitions);
370 KeyedRewardsAndNumPartitions {
371 keyed_rewards,
372 num_partitions,
373 }
374 }
375
376 pub(crate) fn set_epoch_reward_status_calculation(
377 &mut self,
378 distribution_starting_block_height: u64,
379 stake_rewards: Arc<PartitionedStakeRewards>,
380 ) {
381 self.epoch_reward_status =
382 EpochRewardStatus::Active(EpochRewardPhase::Calculation(StartBlockHeightAndRewards {
383 distribution_starting_block_height,
384 all_stake_rewards: stake_rewards,
385 }));
386 }
387
388 pub(crate) fn set_epoch_reward_status_distribution(
389 &mut self,
390 distribution_starting_block_height: u64,
391 all_stake_rewards: Arc<PartitionedStakeRewards>,
392 partition_indices: Vec<Vec<usize>>,
393 ) {
394 self.epoch_reward_status = EpochRewardStatus::Active(EpochRewardPhase::Distribution(
395 StartBlockHeightAndPartitionedRewards {
396 distribution_starting_block_height,
397 all_stake_rewards,
398 partition_indices,
399 },
400 ));
401 }
402
403 pub(super) fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 {
405 self.partitioned_rewards_stake_account_stores_per_block
406 }
407
408 pub(super) fn get_reward_distribution_num_blocks(
410 &self,
411 rewards: &PartitionedStakeRewards,
412 ) -> u64 {
413 let total_stake_accounts = rewards.num_rewards();
414 if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() {
415 1
416 } else {
417 const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10;
418 let num_chunks = total_stake_accounts
419 .div_ceil(self.partitioned_rewards_stake_account_stores_per_block() as usize)
420 as u64;
421
422 num_chunks.clamp(
424 1,
425 (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1),
426 )
427 }
428 }
429
430 pub fn force_reward_interval_end_for_tests(&mut self) {
432 self.epoch_reward_status = EpochRewardStatus::Inactive;
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use {
439 super::*,
440 crate::{
441 bank::{SlotLeader, tests::create_genesis_config},
442 bank_forks::BankForks,
443 genesis_utils::{
444 GenesisConfigInfo, ValidatorVoteKeypairs, create_genesis_config_with_vote_accounts,
445 deactivate_features,
446 },
447 runtime_config::RuntimeConfig,
448 stake_utils,
449 sysvar_account::from_account,
450 },
451 assert_matches::assert_matches,
452 rand::Rng,
453 solana_account::{Account, state_traits::StateMut},
454 solana_accounts_db::{
455 accounts_db::{ACCOUNTS_DB_CONFIG_FOR_TESTING, AccountsDbConfig},
456 partitioned_rewards::PartitionedEpochRewardsConfig,
457 },
458 solana_epoch_schedule::EpochSchedule,
459 solana_hash::Hash,
460 solana_keypair::Keypair,
461 solana_native_token::LAMPORTS_PER_SOL,
462 solana_reward_info::RewardType,
463 solana_signer::Signer,
464 solana_system_transaction as system_transaction,
465 solana_vote::vote_transaction,
466 solana_vote_interface::state::{MAX_LOCKOUT_HISTORY, VoteStateV4, VoteStateVersions},
467 solana_vote_program::vote_state::{self, TowerSync, handler::VoteStateHandler},
468 std::sync::{Arc, RwLock},
469 };
470
471 impl PartitionedStakeReward {
472 pub fn new_random() -> Self {
473 let mut rng = rand::rng();
474 let stake_reward = rng.random_range(1..200);
475 Self {
476 stake_pubkey: Pubkey::new_unique(),
477 inflation: InflationReward {
478 stake: Stake {
479 delegation: Delegation {
480 voter_pubkey: Pubkey::new_unique(),
481 stake: rng.random_range(1..200) + stake_reward,
482 activation_epoch: 0,
483 deactivation_epoch: u64::MAX,
484 ..Default::default()
485 },
486 credits_observed: rng.random_range(1..200),
487 },
488 stake_reward,
489 commission_bps: None,
490 },
491 block_reward: rng.random_range(0..10_000_000_000),
492 }
493 }
494
495 pub fn new_with_lamport_amounts(stake_reward: u64, block_reward: u64, stake: u64) -> Self {
496 Self {
497 stake_pubkey: Pubkey::new_unique(),
498 inflation: InflationReward {
499 stake: Stake {
500 delegation: Delegation {
501 voter_pubkey: Pubkey::new_unique(),
502 stake: stake + stake_reward,
503 activation_epoch: 0,
504 deactivation_epoch: u64::MAX,
505 ..Default::default()
506 },
507 credits_observed: 0,
508 },
509 stake_reward,
510 commission_bps: None,
511 },
512 block_reward,
513 }
514 }
515 }
516
517 pub fn build_partitioned_stake_rewards(
518 stake_rewards: &PartitionedStakeRewards,
519 partition_indices: &[Vec<usize>],
520 ) -> Vec<PartitionedStakeRewards> {
521 partition_indices
522 .iter()
523 .map(|partition_index| {
524 partition_index
527 .iter()
528 .map(|&index| stake_rewards.get(index).unwrap().clone())
529 .collect::<PartitionedStakeRewards>()
530 })
531 .collect::<Vec<_>>()
532 }
533
534 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
535 enum RewardInterval {
536 InsideInterval,
538 OutsideInterval,
540 }
541
542 impl Bank {
543 fn get_reward_interval(&self) -> RewardInterval {
545 if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) {
546 RewardInterval::InsideInterval
547 } else {
548 RewardInterval::OutsideInterval
549 }
550 }
551
552 fn is_calculated(&self) -> bool {
553 matches!(
554 self.epoch_reward_status,
555 EpochRewardStatus::Active(EpochRewardPhase::Calculation(_))
556 )
557 }
558
559 fn is_partitioned(&self) -> bool {
560 matches!(
561 self.epoch_reward_status,
562 EpochRewardStatus::Active(EpochRewardPhase::Distribution(_))
563 )
564 }
565
566 fn get_epoch_rewards_from_cache(
567 &self,
568 parent_hash: &Hash,
569 ) -> Option<Arc<PartitionedRewardsCalculation>> {
570 self.epoch_rewards_calculation_cache
571 .lock()
572 .unwrap()
573 .get(parent_hash)
574 .cloned()
575 }
576
577 fn get_epoch_rewards_cache_len(&self) -> usize {
578 self.epoch_rewards_calculation_cache.lock().unwrap().len()
579 }
580 }
581
582 pub(super) const SLOTS_PER_EPOCH: u64 = 32;
583
584 pub(super) struct RewardBank {
585 pub(super) bank: Arc<Bank>,
586 pub(super) voters: Vec<Pubkey>,
587 pub(super) stakers: Vec<Pubkey>,
588 }
589
590 pub(super) fn create_default_reward_bank(
592 expected_num_delegations: usize,
593 advance_num_slots: u64,
594 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
595 create_reward_bank(
596 expected_num_delegations,
597 PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
598 advance_num_slots,
599 )
600 }
601
602 pub(super) fn create_reward_bank(
603 expected_num_delegations: usize,
604 stake_account_stores_per_block: u64,
605 advance_num_slots: u64,
606 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
607 create_reward_bank_with_specific_stakes(
608 vec![2_000_000_000; expected_num_delegations],
609 stake_account_stores_per_block,
610 advance_num_slots,
611 )
612 }
613
614 pub(super) fn create_reward_bank_with_specific_stakes(
615 stakes: Vec<u64>,
616 stake_account_stores_per_block: u64,
617 advance_num_slots: u64,
618 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
619 let features_to_deactivate = crate::slot_params::slot_time_feature_ids().to_vec();
622 let validator_keypairs = (0..stakes.len())
623 .map(|_| ValidatorVoteKeypairs::new_rand())
624 .collect::<Vec<_>>();
625
626 let GenesisConfigInfo {
627 mut genesis_config, ..
628 } = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stakes);
629 genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
630 deactivate_features(&mut genesis_config, &features_to_deactivate);
631
632 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING;
633 accounts_db_config.partitioned_epoch_rewards_config =
634 PartitionedEpochRewardsConfig::new_for_test(stake_account_stores_per_block);
635
636 let bank = Bank::new_from_genesis(
637 &genesis_config,
638 Arc::new(RuntimeConfig::default()),
639 Vec::new(),
640 None,
641 accounts_db_config,
642 None,
643 None,
644 Arc::default(),
645 None,
646 None,
647 );
648
649 populate_vote_accounts_with_votes(
652 &bank,
653 validator_keypairs.iter().map(|k| k.vote_keypair.pubkey()),
654 0,
655 );
656
657 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
660 let bank = Bank::new_from_parent_with_bank_forks(
661 &bank_forks,
662 bank,
663 SlotLeader::default(),
664 advance_num_slots,
665 );
666
667 (
668 RewardBank {
669 bank,
670 voters: validator_keypairs
671 .iter()
672 .map(|k| k.vote_keypair.pubkey())
673 .collect(),
674 stakers: validator_keypairs
675 .iter()
676 .map(|k| k.stake_keypair.pubkey())
677 .collect(),
678 },
679 bank_forks,
680 )
681 }
682
683 pub(super) fn populate_vote_accounts_with_votes(
684 bank: &Bank,
685 vote_pubkeys: impl IntoIterator<Item = Pubkey>,
686 commission: u8,
687 ) {
688 for vote_pubkey in vote_pubkeys {
689 let mut vote_account = bank
690 .get_account(&vote_pubkey)
691 .unwrap_or_else(|| panic!("missing vote account {vote_pubkey:?}"));
692 let mut vote_state = VoteStateHandler::new_v4(
693 VoteStateV4::deserialize(vote_account.data(), &vote_pubkey).unwrap(),
694 );
695 vote_state.set_commission(commission);
696 for i in 0..MAX_LOCKOUT_HISTORY + 42 {
697 vote_state::process_slot_vote_unchecked(&mut vote_state, i as u64);
698 let versioned = VoteStateVersions::V4(Box::new(vote_state.as_ref_v4().clone()));
699 vote_account.set_state(&versioned).unwrap();
700 }
701 bank.store_account_and_update_capitalization(&vote_pubkey, &vote_account);
702 }
703 }
704
705 #[test]
706 fn test_force_reward_interval_end() {
707 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
708 let mut bank = Bank::new_for_tests(&genesis_config);
709
710 let expected_num = 100;
711
712 let stake_rewards = (0..expected_num)
713 .map(|_| Some(PartitionedStakeReward::new_random()))
714 .collect::<PartitionedStakeRewards>();
715
716 let partition_indices = vec![(0..expected_num).collect()];
717
718 bank.set_epoch_reward_status_distribution(
719 bank.block_height() + REWARD_CALCULATION_NUM_BLOCKS,
720 Arc::new(stake_rewards),
721 partition_indices,
722 );
723 assert!(bank.get_reward_interval() == RewardInterval::InsideInterval);
724
725 bank.force_reward_interval_end_for_tests();
726 assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval);
727 }
728
729 #[test]
732 fn test_get_reward_distribution_num_blocks_cap() {
733 let (mut genesis_config, _mint_keypair) =
734 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
735 genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false);
736
737 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING;
739 accounts_db_config.partitioned_epoch_rewards_config =
740 PartitionedEpochRewardsConfig::new_for_test(10);
741
742 let bank = Bank::new_from_genesis(
743 &genesis_config,
744 Arc::new(RuntimeConfig::default()),
745 Vec::new(),
746 None,
747 accounts_db_config,
748 None,
749 Some(SlotLeader::new_unique()),
750 Arc::default(),
751 None,
752 None,
753 );
754
755 let stake_account_stores_per_block =
756 bank.partitioned_rewards_stake_account_stores_per_block();
757 assert_eq!(stake_account_stores_per_block, 10);
758
759 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
760 let bank =
761 Bank::new_from_parent_with_bank_forks(&bank_forks, bank, SlotLeader::default(), 1);
762 assert_eq!(
763 bank.partitioned_rewards_stake_account_stores_per_block(),
764 stake_account_stores_per_block
765 );
766
767 let check_num_reward_distribution_blocks =
768 |num_stakes: u64, expected_num_reward_distribution_blocks: u64| {
769 let stake_rewards = (0..num_stakes)
771 .map(|_| Some(PartitionedStakeReward::new_random()))
772 .collect::<PartitionedStakeRewards>();
773
774 assert_eq!(
775 bank.get_reward_distribution_num_blocks(&stake_rewards),
776 expected_num_reward_distribution_blocks
777 );
778 };
779
780 for test_record in [
781 (0, 1),
783 (1, 1),
784 (stake_account_stores_per_block, 1),
785 (2 * stake_account_stores_per_block - 1, 2),
786 (2 * stake_account_stores_per_block, 2),
787 (3 * stake_account_stores_per_block - 1, 3),
788 (3 * stake_account_stores_per_block, 3),
789 (4 * stake_account_stores_per_block, 3), (5 * stake_account_stores_per_block, 3), ] {
792 check_num_reward_distribution_blocks(test_record.0, test_record.1);
793 }
794 }
795
796 #[test]
798 fn test_get_reward_distribution_num_blocks_normal() {
799 agave_logger::setup();
800 let (mut genesis_config, _mint_keypair) =
801 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
802 genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
803
804 let bank = Bank::new_for_tests(&genesis_config);
805
806 let expected_num = 8192;
808 let stake_rewards = (0..expected_num)
809 .map(|_| Some(PartitionedStakeReward::new_random()))
810 .collect::<PartitionedStakeRewards>();
811
812 assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2);
813 }
814
815 #[test]
818 fn test_get_reward_distribution_num_blocks_warmup() {
819 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
820
821 let bank = Bank::new_for_tests(&genesis_config);
822 let rewards = PartitionedStakeRewards::default();
823 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
824 }
825
826 #[test]
832 fn test_get_reward_distribution_num_blocks_none() {
833 let rewards_all = 8192;
834 let expected_rewards_some = 6144;
835
836 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
837 let bank = Bank::new_for_tests(&genesis_config);
838
839 let rewards = (0..rewards_all)
840 .map(|i| {
841 if i % 4 == 0 {
842 None
843 } else {
844 Some(PartitionedStakeReward::new_random())
845 }
846 })
847 .collect::<PartitionedStakeRewards>();
848 assert_eq!(rewards.rewards.len(), rewards_all);
849 assert_eq!(rewards.num_rewards(), expected_rewards_some);
850
851 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
852 }
853
854 #[test]
855 fn test_rewards_computation_and_partitioned_distribution_one_block() {
856 agave_logger::setup();
857
858 let starting_slot = SLOTS_PER_EPOCH - 1;
859 let (
860 RewardBank {
861 bank: mut previous_bank,
862 ..
863 },
864 bank_forks,
865 ) = create_default_reward_bank(100, starting_slot - 1);
866
867 for slot in starting_slot..=(2 * SLOTS_PER_EPOCH) + 2 {
869 let pre_cap = previous_bank.capitalization();
870 let curr_bank = Bank::new_from_parent_with_bank_forks(
871 bank_forks.as_ref(),
872 previous_bank.clone(),
873 SlotLeader::default(),
874 slot,
875 );
876 let post_cap = curr_bank.capitalization();
877
878 if slot % SLOTS_PER_EPOCH == 0 {
879 assert_matches!(
882 curr_bank.get_reward_interval(),
883 RewardInterval::InsideInterval
884 );
885
886 assert!(curr_bank.is_calculated());
887
888 assert!(
890 curr_bank
891 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
892 .is_some()
893 );
894 assert_eq!(post_cap, pre_cap);
895
896 let _ = bank_forks.write().unwrap().set_root(slot, None, None);
899 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
900 } else if slot == SLOTS_PER_EPOCH + 1 {
901 assert_matches!(
907 curr_bank.get_reward_interval(),
908 RewardInterval::OutsideInterval
909 );
910 let account = curr_bank
911 .get_account(&solana_sysvar::epoch_rewards::id())
912 .unwrap();
913 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
914 from_account(&account).unwrap();
915 assert_eq!(post_cap, pre_cap + epoch_rewards.distributed_rewards);
916 } else {
917 assert_matches!(
922 curr_bank.get_reward_interval(),
923 RewardInterval::OutsideInterval
924 );
925
926 assert_eq!(post_cap, pre_cap);
928 }
929 if slot >= SLOTS_PER_EPOCH {
932 let epoch_rewards_lamports =
933 curr_bank.get_balance(&solana_sysvar::epoch_rewards::id());
934 assert!(epoch_rewards_lamports > 0);
935 }
936 previous_bank = curr_bank;
937 }
938 }
939
940 #[test]
943 fn test_rewards_computation_and_partitioned_distribution_multi_blocks() {
944 agave_logger::setup();
945
946 let starting_slot = SLOTS_PER_EPOCH - 1;
947 let (
948 RewardBank {
949 bank: mut previous_bank,
950 ..
951 },
952 bank_forks,
953 ) = create_reward_bank(100, 50, starting_slot - 1);
954 let mut starting_hash = None;
955 let mut reward_distribution_completion_slot = None;
956
957 for slot in starting_slot..=SLOTS_PER_EPOCH.saturating_mul(2) {
959 let pre_cap = previous_bank.capitalization();
960
961 let pre_sysvar_account = previous_bank
962 .get_account(&solana_sysvar::epoch_rewards::id())
963 .unwrap_or_default();
964 let pre_epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
965 from_account(&pre_sysvar_account).unwrap_or_default();
966 let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
967 let curr_bank = Bank::new_from_parent_with_bank_forks(
968 bank_forks.as_ref(),
969 previous_bank.clone(),
970 SlotLeader::default(),
971 slot,
972 );
973 let post_cap = curr_bank.capitalization();
974
975 if slot < SLOTS_PER_EPOCH {
976 assert_matches!(
978 curr_bank.get_reward_interval(),
979 RewardInterval::OutsideInterval
980 );
981 assert_eq!(post_cap, pre_cap);
982 } else if slot == SLOTS_PER_EPOCH {
983 assert_matches!(
986 curr_bank.get_reward_interval(),
987 RewardInterval::InsideInterval
988 );
989
990 assert!(curr_bank.is_calculated());
992
993 assert!(
995 curr_bank
996 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
997 .is_some()
998 );
999 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
1000 starting_hash = Some(curr_bank.parent_hash);
1001
1002 let account = curr_bank
1005 .get_account(&solana_sysvar::epoch_rewards::id())
1006 .unwrap();
1007 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1008 from_account(&account).unwrap();
1009 reward_distribution_completion_slot =
1010 Some(SLOTS_PER_EPOCH + epoch_rewards.num_partitions);
1011 } else if slot
1012 < reward_distribution_completion_slot
1013 .expect("epoch boundary must set completion slot")
1014 {
1015 assert_matches!(
1017 curr_bank.get_reward_interval(),
1018 RewardInterval::InsideInterval
1019 );
1020 assert!(curr_bank.is_partitioned());
1021
1022 let account = curr_bank
1023 .get_account(&solana_sysvar::epoch_rewards::id())
1024 .unwrap();
1025 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1026 from_account(&account).unwrap();
1027 assert_eq!(
1028 post_cap,
1029 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
1030 );
1031
1032 if slot == SLOTS_PER_EPOCH + 1 {
1033 assert!(
1036 curr_bank
1037 .get_epoch_rewards_from_cache(&starting_hash.unwrap())
1038 .is_some()
1039 );
1040 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
1041
1042 let _ = bank_forks.write().unwrap().set_root(slot - 1, None, None);
1045 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
1046 }
1047 } else if slot
1048 == reward_distribution_completion_slot
1049 .expect("epoch boundary must set completion slot")
1050 {
1051 assert_matches!(
1055 curr_bank.get_reward_interval(),
1056 RewardInterval::OutsideInterval
1057 );
1058
1059 let account = curr_bank
1060 .get_account(&solana_sysvar::epoch_rewards::id())
1061 .unwrap();
1062 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1063 from_account(&account).unwrap();
1064 assert_eq!(
1065 post_cap,
1066 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
1067 );
1068 } else {
1069 assert_matches!(
1072 curr_bank.get_reward_interval(),
1073 RewardInterval::OutsideInterval
1074 );
1075 assert_eq!(post_cap, pre_cap);
1076 break;
1077 }
1078 previous_bank = curr_bank;
1079 }
1080 }
1081
1082 #[test]
1084 fn test_rewards_period_system_transfer() {
1085 let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
1086 let validator_keypairs = vec![&validator_vote_keypairs];
1087 let GenesisConfigInfo {
1088 mut genesis_config,
1089 mint_keypair,
1090 ..
1091 } = create_genesis_config_with_vote_accounts(
1092 1_000_000_000,
1093 &validator_keypairs,
1094 vec![1_000_000_000; 1],
1095 );
1096
1097 let vote_key = validator_keypairs[0].vote_keypair.pubkey();
1099 let vote_account = genesis_config
1100 .accounts
1101 .iter()
1102 .find(|(address, _)| **address == vote_key)
1103 .map(|(_, account)| account)
1104 .unwrap()
1105 .clone();
1106
1107 let new_stake_signer = Keypair::new();
1108 let new_stake_address = new_stake_signer.pubkey();
1109 let new_stake_account = Account::from(stake_utils::create_stake_account(
1110 &new_stake_address,
1111 &vote_key,
1112 &vote_account.into(),
1113 &genesis_config.rent,
1114 2_000_000_000,
1115 ));
1116 genesis_config
1117 .accounts
1118 .extend(vec![(new_stake_address, new_stake_account)]);
1119
1120 let (mut previous_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1121 let num_slots_in_epoch = previous_bank.get_slots_in_epoch(previous_bank.epoch());
1122 assert_eq!(num_slots_in_epoch, 32);
1123
1124 let transfer_amount = 5_000;
1125
1126 for slot in 1..=num_slots_in_epoch + 2 {
1127 let bank = Bank::new_from_parent_with_bank_forks(
1128 bank_forks.as_ref(),
1129 previous_bank.clone(),
1130 SlotLeader::default(),
1131 slot,
1132 );
1133
1134 let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash());
1137 let vote = vote_transaction::new_tower_sync_transaction(
1138 tower_sync,
1139 previous_bank.last_blockhash(),
1140 &validator_vote_keypairs.node_keypair,
1141 &validator_vote_keypairs.vote_keypair,
1142 &validator_vote_keypairs.vote_keypair,
1143 None,
1144 );
1145 bank.process_transaction(&vote).unwrap();
1146
1147 let system_tx = system_transaction::transfer(
1149 &mint_keypair,
1150 &new_stake_address,
1151 transfer_amount,
1152 bank.last_blockhash(),
1153 );
1154 let system_result = bank.process_transaction(&system_tx);
1155
1156 assert!(system_result.is_ok());
1158
1159 bank.register_unique_recent_blockhash_for_test();
1163 previous_bank = bank;
1164 }
1165 }
1166
1167 #[test]
1168 fn test_get_rewards_and_partitions() {
1169 let starting_slot = SLOTS_PER_EPOCH - 1;
1170 let num_rewards = 100;
1171 let stake_account_stores_per_block = 50;
1172 let (RewardBank { bank, .. }, bank_forks) =
1173 create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
1174
1175 assert_eq!(
1178 bank.get_rewards_and_num_partitions(),
1179 KeyedRewardsAndNumPartitions {
1180 keyed_rewards: vec![],
1181 num_partitions: None,
1182 }
1183 );
1184
1185 let epoch_boundary_bank = Bank::new_from_parent_with_bank_forks(
1186 bank_forks.as_ref(),
1187 bank,
1188 SlotLeader::default(),
1189 SLOTS_PER_EPOCH,
1190 );
1191 let KeyedRewardsAndNumPartitions {
1193 keyed_rewards,
1194 num_partitions,
1195 } = epoch_boundary_bank.get_rewards_and_num_partitions();
1196 for (_pubkey, reward) in keyed_rewards.iter() {
1197 assert_eq!(reward.reward_type, RewardType::Voting);
1198 }
1199 assert_eq!(keyed_rewards.len(), num_rewards);
1200 let expected_num_partitions = (num_rewards as u64)
1201 .div_ceil(epoch_boundary_bank.partitioned_rewards_stake_account_stores_per_block())
1202 .clamp(1, (SLOTS_PER_EPOCH / 10).max(1));
1203 assert_eq!(num_partitions, Some(expected_num_partitions));
1204
1205 let mut total_staking_rewards = 0;
1206 let mut previous_bank = epoch_boundary_bank;
1207 for partition_index in 0..expected_num_partitions {
1208 let partition_bank = Arc::new(Bank::new_from_parent(
1209 previous_bank,
1210 SlotLeader::default(),
1211 SLOTS_PER_EPOCH + partition_index + 1,
1212 ));
1213 let KeyedRewardsAndNumPartitions {
1217 keyed_rewards,
1218 num_partitions,
1219 } = partition_bank.get_rewards_and_num_partitions();
1220 for (_pubkey, reward) in keyed_rewards.iter() {
1221 assert_eq!(reward.reward_type, RewardType::Staking);
1222 }
1223 total_staking_rewards += keyed_rewards.len();
1224 assert_eq!(num_partitions, None);
1225 previous_bank = partition_bank;
1226 }
1227
1228 assert_eq!(total_staking_rewards, num_rewards);
1230 let bank = Bank::new_from_parent(
1231 previous_bank,
1232 SlotLeader::default(),
1233 SLOTS_PER_EPOCH + expected_num_partitions + 1,
1234 );
1235 assert_eq!(
1238 bank.get_rewards_and_num_partitions(),
1239 KeyedRewardsAndNumPartitions {
1240 keyed_rewards: vec![],
1241 num_partitions: None,
1242 }
1243 );
1244 }
1245
1246 #[test]
1247 fn test_rewards_and_partitions_should_record() {
1248 let reward = RewardInfo {
1249 reward_type: RewardType::Voting,
1250 lamports: 55,
1251 post_balance: 5555,
1252 commission_bps: Some(500),
1253 };
1254
1255 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1256 keyed_rewards: vec![],
1257 num_partitions: None,
1258 };
1259 assert!(!rewards_and_partitions.should_record());
1260
1261 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1262 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1263 num_partitions: None,
1264 };
1265 assert!(rewards_and_partitions.should_record());
1266
1267 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1268 keyed_rewards: vec![],
1269 num_partitions: Some(42),
1270 };
1271 assert!(rewards_and_partitions.should_record());
1272
1273 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1274 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1275 num_partitions: Some(42),
1276 };
1277 assert!(rewards_and_partitions.should_record());
1278 }
1279}