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) {
100 unsafe {
101 self.rewards.set_len(self.rewards.capacity());
102 }
103 self.num_rewards = num_stake_rewards;
104 }
105}
106
107#[cfg(test)]
108impl FromIterator<Option<PartitionedStakeReward>> for PartitionedStakeRewards {
109 fn from_iter<T: IntoIterator<Item = Option<PartitionedStakeReward>>>(iter: T) -> Self {
110 let mut len_some: usize = 0;
111 let rewards = Vec::from_iter(iter.into_iter().inspect(|reward| {
112 if reward.is_some() {
113 len_some = len_some.saturating_add(1);
114 }
115 }));
116 Self {
117 rewards,
118 num_rewards: len_some,
119 }
120 }
121}
122
123#[cfg(test)]
124impl FromIterator<PartitionedStakeReward> for PartitionedStakeRewards {
125 fn from_iter<T: IntoIterator<Item = PartitionedStakeReward>>(iter: T) -> Self {
126 let rewards = Vec::from_iter(iter.into_iter().map(Some));
127 let num_rewards = rewards.len();
128 Self {
129 rewards,
130 num_rewards,
131 }
132 }
133}
134
135#[derive(Debug, Clone, PartialEq)]
136pub(crate) struct StartBlockHeightAndRewards {
137 pub(crate) distribution_starting_block_height: u64,
139 pub(crate) all_stake_rewards: Arc<PartitionedStakeRewards>,
141}
142
143#[derive(Debug, Clone, PartialEq)]
144pub(crate) struct StartBlockHeightAndPartitionedRewards {
145 pub(crate) distribution_starting_block_height: u64,
147
148 pub(crate) all_stake_rewards: Arc<PartitionedStakeRewards>,
150
151 pub(crate) partition_indices: Vec<Vec<usize>>,
155}
156
157#[derive(Debug, Clone, PartialEq, Default)]
159pub(crate) enum EpochRewardStatus {
160 Active(EpochRewardPhase),
165 #[default]
167 Inactive,
168}
169
170#[derive(Debug, Clone, PartialEq)]
171pub(crate) enum EpochRewardPhase {
172 Calculation(StartBlockHeightAndRewards),
173 Distribution(StartBlockHeightAndPartitionedRewards),
174}
175
176#[derive(Debug)]
177#[cfg_attr(test, derive(Clone))]
178pub(super) struct RewardCommission {
179 pub(super) commission_bps: Option<u16>,
182 pub(super) commission_lamports: u64,
183 pub(super) burned_lamports: u64,
184 pub(super) is_vote_account: bool,
185}
186
187pub(super) type RewardCommissions = HashMap<Pubkey, RewardCommission, PubkeyHasherBuilder>;
188
189#[derive(Debug, Default)]
192pub(super) struct RewardCommissionLamportAmounts {
193 pub(super) distributed_lamports: u64,
199 pub(super) distributed_to_incinerator_lamports: u64,
205 pub(super) burned_lamports: u64,
207}
208
209#[derive(Debug, Default)]
210pub(super) struct RewardCommissionAccounts {
211 pub(super) accounts_with_rewards: Vec<(Pubkey, RewardInfo, AccountSharedData)>,
213 pub(super) amounts: RewardCommissionLamportAmounts,
215}
216
217pub(super) struct RewardCommissionAccountsStorable<'a> {
219 pub slot: Slot,
220 pub reward_commission_accounts: &'a RewardCommissionAccounts,
221}
222
223impl<'a> StorableAccounts<'a> for RewardCommissionAccountsStorable<'a> {
224 fn account<Ret>(
225 &self,
226 index: usize,
227 mut callback: impl for<'local> FnMut(AccountForStorage<'local>) -> Ret,
228 ) -> Ret {
229 let (pubkey, _, account) = &self.reward_commission_accounts.accounts_with_rewards[index];
230 callback((pubkey, account).into())
231 }
232
233 fn account_for_geyser<Ret>(
234 &self,
235 index: usize,
236 mut callback: impl for<'local> FnMut(&'local Pubkey, &'local AccountSharedData) -> Ret,
237 ) -> Ret {
238 let (pubkey, _, account) = &self.reward_commission_accounts.accounts_with_rewards[index];
239 callback(pubkey, account)
240 }
241
242 fn is_zero_lamport(&self, index: usize) -> bool {
243 self.reward_commission_accounts.accounts_with_rewards[index]
244 .2
245 .lamports()
246 == 0
247 }
248
249 fn data_len(&self, index: usize) -> usize {
250 self.reward_commission_accounts.accounts_with_rewards[index]
251 .2
252 .data()
253 .len()
254 }
255
256 fn pubkey(&self, index: usize) -> &Pubkey {
257 &self.reward_commission_accounts.accounts_with_rewards[index].0
258 }
259
260 fn slot(&self, _index: usize) -> Slot {
261 self.target_slot()
262 }
263
264 fn target_slot(&self) -> Slot {
265 self.slot
266 }
267
268 fn len(&self) -> usize {
269 self.reward_commission_accounts.accounts_with_rewards.len()
270 }
271}
272
273#[derive(Debug, Default)]
274pub(super) struct StakeRewardCalculation {
276 stake_rewards: Arc<PartitionedStakeRewards>,
278 total_stake_rewards_lamports: u64,
280}
281
282#[derive(Debug)]
283struct CalculateValidatorRewardsResult {
284 reward_commissions: RewardCommissions,
285 stake_reward_calculation: StakeRewardCalculation,
286 point_value: PointValue,
287}
288
289impl Default for CalculateValidatorRewardsResult {
290 fn default() -> Self {
291 Self {
292 reward_commissions: RewardCommissions::default(),
293 stake_reward_calculation: StakeRewardCalculation::default(),
294 point_value: PointValue {
295 points: 0,
296 rewards: 0,
297 },
298 }
299 }
300}
301
302pub(super) struct CachedVoteAccounts<'a> {
303 pub(super) snapshot_epoch_vote_accounts: Option<&'a VoteAccounts>,
309 pub(super) rewarded_epoch_vote_accounts: Option<&'a VoteAccounts>,
313 pub(super) distribution_epoch_vote_accounts: &'a VoteAccounts,
316}
317
318pub(super) struct EpochRewardCalculateParamInfo<'a> {
320 pub(super) stake_history: StakeHistory,
321 pub(super) stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)>,
322 pub(super) cached_vote_accounts: CachedVoteAccounts<'a>,
323}
324
325#[derive(Debug)]
329pub(super) struct PartitionedRewardsCalculation {
330 reward_commissions: RewardCommissions,
331 stake_rewards: StakeRewardCalculation,
332 capitalization: u64,
333 point_value: PointValue,
334 num_filtered_vote_accounts: usize,
339}
340
341pub(crate) type StakeRewards = Vec<StakeReward>;
342
343#[derive(Debug, PartialEq)]
344pub struct KeyedRewardsAndNumPartitions {
345 pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
346 pub num_partitions: Option<u64>,
347}
348
349impl KeyedRewardsAndNumPartitions {
350 pub fn should_record(&self) -> bool {
351 !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
352 }
353}
354
355impl Bank {
356 pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
357 let keyed_rewards = self.rewards.read().unwrap().clone();
358 let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
359 let epoch_schedule = self.epoch_schedule();
362 let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
363 let is_first_block_in_epoch = self.epoch() > parent_epoch;
364
365 let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
366 .then_some(epoch_rewards_sysvar.num_partitions);
367 KeyedRewardsAndNumPartitions {
368 keyed_rewards,
369 num_partitions,
370 }
371 }
372
373 pub(crate) fn set_epoch_reward_status_calculation(
374 &mut self,
375 distribution_starting_block_height: u64,
376 stake_rewards: Arc<PartitionedStakeRewards>,
377 ) {
378 self.epoch_reward_status =
379 EpochRewardStatus::Active(EpochRewardPhase::Calculation(StartBlockHeightAndRewards {
380 distribution_starting_block_height,
381 all_stake_rewards: stake_rewards,
382 }));
383 }
384
385 pub(crate) fn set_epoch_reward_status_distribution(
386 &mut self,
387 distribution_starting_block_height: u64,
388 all_stake_rewards: Arc<PartitionedStakeRewards>,
389 partition_indices: Vec<Vec<usize>>,
390 ) {
391 self.epoch_reward_status = EpochRewardStatus::Active(EpochRewardPhase::Distribution(
392 StartBlockHeightAndPartitionedRewards {
393 distribution_starting_block_height,
394 all_stake_rewards,
395 partition_indices,
396 },
397 ));
398 }
399
400 pub(super) fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 {
402 self.partitioned_rewards_stake_account_stores_per_block
403 }
404
405 pub(super) fn get_reward_distribution_num_blocks(
407 &self,
408 rewards: &PartitionedStakeRewards,
409 ) -> u64 {
410 let total_stake_accounts = rewards.num_rewards();
411 if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() {
412 1
413 } else {
414 const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10;
415 let num_chunks = total_stake_accounts
416 .div_ceil(self.partitioned_rewards_stake_account_stores_per_block() as usize)
417 as u64;
418
419 num_chunks.clamp(
421 1,
422 (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1),
423 )
424 }
425 }
426
427 pub fn force_reward_interval_end_for_tests(&mut self) {
429 self.epoch_reward_status = EpochRewardStatus::Inactive;
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use {
436 super::*,
437 crate::{
438 bank::{SlotLeader, tests::create_genesis_config},
439 bank_forks::BankForks,
440 genesis_utils::{
441 GenesisConfigInfo, ValidatorVoteKeypairs, create_genesis_config_with_vote_accounts,
442 deactivate_features,
443 },
444 runtime_config::RuntimeConfig,
445 stake_utils,
446 },
447 assert_matches::assert_matches,
448 rand::Rng,
449 solana_account::{Account, state_traits::StateMut},
450 solana_accounts_db::{
451 accounts_db::{ACCOUNTS_DB_CONFIG_FOR_TESTING, AccountsDbConfig},
452 partitioned_rewards::PartitionedEpochRewardsConfig,
453 },
454 solana_epoch_schedule::EpochSchedule,
455 solana_hash::Hash,
456 solana_keypair::Keypair,
457 solana_native_token::LAMPORTS_PER_SOL,
458 solana_reward_info::RewardType,
459 solana_signer::Signer,
460 solana_system_transaction as system_transaction,
461 solana_vote::vote_transaction,
462 solana_vote_interface::state::{MAX_LOCKOUT_HISTORY, VoteStateV4, VoteStateVersions},
463 solana_vote_program::vote_state::{self, TowerSync, handler::VoteStateHandler},
464 std::sync::{Arc, RwLock},
465 };
466
467 impl PartitionedStakeReward {
468 pub fn new_random() -> Self {
469 let mut rng = rand::rng();
470 let stake_reward = rng.random_range(1..200);
471 Self {
472 stake_pubkey: Pubkey::new_unique(),
473 inflation: InflationReward {
474 stake: Stake {
475 delegation: Delegation {
476 voter_pubkey: Pubkey::new_unique(),
477 stake: rng.random_range(1..200) + stake_reward,
478 activation_epoch: 0,
479 deactivation_epoch: u64::MAX,
480 ..Default::default()
481 },
482 credits_observed: rng.random_range(1..200),
483 },
484 stake_reward,
485 commission_bps: None,
486 },
487 block_reward: rng.random_range(0..10_000_000_000),
488 }
489 }
490
491 pub fn new_with_lamport_amounts(stake_reward: u64, block_reward: u64, stake: u64) -> Self {
492 Self {
493 stake_pubkey: Pubkey::new_unique(),
494 inflation: InflationReward {
495 stake: Stake {
496 delegation: Delegation {
497 voter_pubkey: Pubkey::new_unique(),
498 stake: stake + stake_reward,
499 activation_epoch: 0,
500 deactivation_epoch: u64::MAX,
501 ..Default::default()
502 },
503 credits_observed: 0,
504 },
505 stake_reward,
506 commission_bps: None,
507 },
508 block_reward,
509 }
510 }
511 }
512
513 pub fn build_partitioned_stake_rewards(
514 stake_rewards: &PartitionedStakeRewards,
515 partition_indices: &[Vec<usize>],
516 ) -> Vec<PartitionedStakeRewards> {
517 partition_indices
518 .iter()
519 .map(|partition_index| {
520 partition_index
523 .iter()
524 .map(|&index| stake_rewards.get(index).unwrap().clone())
525 .collect::<PartitionedStakeRewards>()
526 })
527 .collect::<Vec<_>>()
528 }
529
530 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
531 enum RewardInterval {
532 InsideInterval,
534 OutsideInterval,
536 }
537
538 impl Bank {
539 fn get_reward_interval(&self) -> RewardInterval {
541 if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) {
542 RewardInterval::InsideInterval
543 } else {
544 RewardInterval::OutsideInterval
545 }
546 }
547
548 fn is_calculated(&self) -> bool {
549 matches!(
550 self.epoch_reward_status,
551 EpochRewardStatus::Active(EpochRewardPhase::Calculation(_))
552 )
553 }
554
555 fn is_partitioned(&self) -> bool {
556 matches!(
557 self.epoch_reward_status,
558 EpochRewardStatus::Active(EpochRewardPhase::Distribution(_))
559 )
560 }
561
562 fn get_epoch_rewards_from_cache(
563 &self,
564 parent_hash: &Hash,
565 ) -> Option<Arc<PartitionedRewardsCalculation>> {
566 self.epoch_rewards_calculation_cache
567 .lock()
568 .unwrap()
569 .get(parent_hash)
570 .cloned()
571 }
572
573 fn get_epoch_rewards_cache_len(&self) -> usize {
574 self.epoch_rewards_calculation_cache.lock().unwrap().len()
575 }
576 }
577
578 pub(super) const SLOTS_PER_EPOCH: u64 = 32;
579
580 pub(super) struct RewardBank {
581 pub(super) bank: Arc<Bank>,
582 pub(super) voters: Vec<Pubkey>,
583 pub(super) stakers: Vec<Pubkey>,
584 }
585
586 pub(super) fn create_default_reward_bank(
588 expected_num_delegations: usize,
589 advance_num_slots: u64,
590 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
591 create_reward_bank(
592 expected_num_delegations,
593 PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
594 advance_num_slots,
595 )
596 }
597
598 pub(super) fn create_reward_bank(
599 expected_num_delegations: usize,
600 stake_account_stores_per_block: u64,
601 advance_num_slots: u64,
602 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
603 create_reward_bank_with_specific_stakes(
604 vec![2_000_000_000; expected_num_delegations],
605 stake_account_stores_per_block,
606 advance_num_slots,
607 )
608 }
609
610 pub(super) fn create_reward_bank_with_specific_stakes(
611 stakes: Vec<u64>,
612 stake_account_stores_per_block: u64,
613 advance_num_slots: u64,
614 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
615 let features_to_deactivate = crate::slot_params::slot_time_feature_ids().to_vec();
618 let validator_keypairs = (0..stakes.len())
619 .map(|_| ValidatorVoteKeypairs::new_rand())
620 .collect::<Vec<_>>();
621
622 let GenesisConfigInfo {
623 mut genesis_config, ..
624 } = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stakes);
625 genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
626 deactivate_features(&mut genesis_config, &features_to_deactivate);
627
628 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING;
629 accounts_db_config.partitioned_epoch_rewards_config =
630 PartitionedEpochRewardsConfig::new_for_test(stake_account_stores_per_block);
631
632 let bank = Bank::new_from_genesis(
633 &genesis_config,
634 Arc::new(RuntimeConfig::default()),
635 Vec::new(),
636 None,
637 accounts_db_config,
638 None,
639 None,
640 Arc::default(),
641 None,
642 None,
643 );
644
645 populate_vote_accounts_with_votes(
648 &bank,
649 validator_keypairs.iter().map(|k| k.vote_keypair.pubkey()),
650 0,
651 );
652
653 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
656 let bank = Bank::new_from_parent_with_bank_forks(
657 &bank_forks,
658 bank,
659 SlotLeader::default(),
660 advance_num_slots,
661 );
662
663 (
664 RewardBank {
665 bank,
666 voters: validator_keypairs
667 .iter()
668 .map(|k| k.vote_keypair.pubkey())
669 .collect(),
670 stakers: validator_keypairs
671 .iter()
672 .map(|k| k.stake_keypair.pubkey())
673 .collect(),
674 },
675 bank_forks,
676 )
677 }
678
679 pub(super) fn populate_vote_accounts_with_votes(
680 bank: &Bank,
681 vote_pubkeys: impl IntoIterator<Item = Pubkey>,
682 commission: u8,
683 ) {
684 for vote_pubkey in vote_pubkeys {
685 let mut vote_account = bank
686 .get_account(&vote_pubkey)
687 .unwrap_or_else(|| panic!("missing vote account {vote_pubkey:?}"));
688 let mut vote_state = VoteStateHandler::new_v4(
689 VoteStateV4::deserialize(vote_account.data(), &vote_pubkey).unwrap(),
690 );
691 vote_state.set_commission(commission);
692 for i in 0..MAX_LOCKOUT_HISTORY + 42 {
693 vote_state::process_slot_vote_unchecked(&mut vote_state, i as u64);
694 let versioned = VoteStateVersions::V4(Box::new(vote_state.as_ref_v4().clone()));
695 vote_account.set_state(&versioned).unwrap();
696 }
697 bank.store_account_and_update_capitalization(&vote_pubkey, &vote_account);
698 }
699 }
700
701 #[test]
702 fn test_force_reward_interval_end() {
703 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
704 let mut bank = Bank::new_for_tests(&genesis_config);
705
706 let expected_num = 100;
707
708 let stake_rewards = (0..expected_num)
709 .map(|_| Some(PartitionedStakeReward::new_random()))
710 .collect::<PartitionedStakeRewards>();
711
712 let partition_indices = vec![(0..expected_num).collect()];
713
714 bank.set_epoch_reward_status_distribution(
715 bank.block_height() + REWARD_CALCULATION_NUM_BLOCKS,
716 Arc::new(stake_rewards),
717 partition_indices,
718 );
719 assert!(bank.get_reward_interval() == RewardInterval::InsideInterval);
720
721 bank.force_reward_interval_end_for_tests();
722 assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval);
723 }
724
725 #[test]
728 fn test_get_reward_distribution_num_blocks_cap() {
729 let (mut genesis_config, _mint_keypair) =
730 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
731 genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false);
732
733 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING;
735 accounts_db_config.partitioned_epoch_rewards_config =
736 PartitionedEpochRewardsConfig::new_for_test(10);
737
738 let bank = Bank::new_from_genesis(
739 &genesis_config,
740 Arc::new(RuntimeConfig::default()),
741 Vec::new(),
742 None,
743 accounts_db_config,
744 None,
745 Some(SlotLeader::new_unique()),
746 Arc::default(),
747 None,
748 None,
749 );
750
751 let stake_account_stores_per_block =
752 bank.partitioned_rewards_stake_account_stores_per_block();
753 assert_eq!(stake_account_stores_per_block, 10);
754
755 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
756 let bank =
757 Bank::new_from_parent_with_bank_forks(&bank_forks, bank, SlotLeader::default(), 1);
758 assert_eq!(
759 bank.partitioned_rewards_stake_account_stores_per_block(),
760 stake_account_stores_per_block
761 );
762
763 let check_num_reward_distribution_blocks =
764 |num_stakes: u64, expected_num_reward_distribution_blocks: u64| {
765 let stake_rewards = (0..num_stakes)
767 .map(|_| Some(PartitionedStakeReward::new_random()))
768 .collect::<PartitionedStakeRewards>();
769
770 assert_eq!(
771 bank.get_reward_distribution_num_blocks(&stake_rewards),
772 expected_num_reward_distribution_blocks
773 );
774 };
775
776 for test_record in [
777 (0, 1),
779 (1, 1),
780 (stake_account_stores_per_block, 1),
781 (2 * stake_account_stores_per_block - 1, 2),
782 (2 * stake_account_stores_per_block, 2),
783 (3 * stake_account_stores_per_block - 1, 3),
784 (3 * stake_account_stores_per_block, 3),
785 (4 * stake_account_stores_per_block, 3), (5 * stake_account_stores_per_block, 3), ] {
788 check_num_reward_distribution_blocks(test_record.0, test_record.1);
789 }
790 }
791
792 #[test]
794 fn test_get_reward_distribution_num_blocks_normal() {
795 agave_logger::setup();
796 let (mut genesis_config, _mint_keypair) =
797 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
798 genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
799
800 let bank = Bank::new_for_tests(&genesis_config);
801
802 let expected_num = 8192;
804 let stake_rewards = (0..expected_num)
805 .map(|_| Some(PartitionedStakeReward::new_random()))
806 .collect::<PartitionedStakeRewards>();
807
808 assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2);
809 }
810
811 #[test]
814 fn test_get_reward_distribution_num_blocks_warmup() {
815 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
816
817 let bank = Bank::new_for_tests(&genesis_config);
818 let rewards = PartitionedStakeRewards::default();
819 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
820 }
821
822 #[test]
828 fn test_get_reward_distribution_num_blocks_none() {
829 let rewards_all = 8192;
830 let expected_rewards_some = 6144;
831
832 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
833 let bank = Bank::new_for_tests(&genesis_config);
834
835 let rewards = (0..rewards_all)
836 .map(|i| {
837 if i % 4 == 0 {
838 None
839 } else {
840 Some(PartitionedStakeReward::new_random())
841 }
842 })
843 .collect::<PartitionedStakeRewards>();
844 assert_eq!(rewards.rewards.len(), rewards_all);
845 assert_eq!(rewards.num_rewards(), expected_rewards_some);
846
847 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
848 }
849
850 #[test]
851 fn test_rewards_computation_and_partitioned_distribution_one_block() {
852 agave_logger::setup();
853
854 let starting_slot = SLOTS_PER_EPOCH - 1;
855 let (
856 RewardBank {
857 bank: mut previous_bank,
858 ..
859 },
860 bank_forks,
861 ) = create_default_reward_bank(100, starting_slot - 1);
862
863 for slot in starting_slot..=(2 * SLOTS_PER_EPOCH) + 2 {
865 let pre_cap = previous_bank.capitalization();
866 let curr_bank = Bank::new_from_parent_with_bank_forks(
867 bank_forks.as_ref(),
868 previous_bank.clone(),
869 SlotLeader::default(),
870 slot,
871 );
872 let post_cap = curr_bank.capitalization();
873
874 if slot % SLOTS_PER_EPOCH == 0 {
875 assert_matches!(
878 curr_bank.get_reward_interval(),
879 RewardInterval::InsideInterval
880 );
881
882 assert!(curr_bank.is_calculated());
883
884 assert!(
886 curr_bank
887 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
888 .is_some()
889 );
890 assert_eq!(post_cap, pre_cap);
891
892 let _ = bank_forks.write().unwrap().set_root(slot, None, None);
895 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
896 } else if slot == SLOTS_PER_EPOCH + 1 {
897 assert_matches!(
903 curr_bank.get_reward_interval(),
904 RewardInterval::OutsideInterval
905 );
906 let account = curr_bank
907 .get_account(&solana_sysvar::epoch_rewards::id())
908 .unwrap();
909 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
910 solana_account::from_account(&account).unwrap();
911 assert_eq!(post_cap, pre_cap + epoch_rewards.distributed_rewards);
912 } else {
913 assert_matches!(
918 curr_bank.get_reward_interval(),
919 RewardInterval::OutsideInterval
920 );
921
922 assert_eq!(post_cap, pre_cap);
924 }
925 if slot >= SLOTS_PER_EPOCH {
928 let epoch_rewards_lamports =
929 curr_bank.get_balance(&solana_sysvar::epoch_rewards::id());
930 assert!(epoch_rewards_lamports > 0);
931 }
932 previous_bank = curr_bank;
933 }
934 }
935
936 #[test]
939 fn test_rewards_computation_and_partitioned_distribution_multi_blocks() {
940 agave_logger::setup();
941
942 let starting_slot = SLOTS_PER_EPOCH - 1;
943 let (
944 RewardBank {
945 bank: mut previous_bank,
946 ..
947 },
948 bank_forks,
949 ) = create_reward_bank(100, 50, starting_slot - 1);
950 let mut starting_hash = None;
951 let mut reward_distribution_completion_slot = None;
952
953 for slot in starting_slot..=SLOTS_PER_EPOCH.saturating_mul(2) {
955 let pre_cap = previous_bank.capitalization();
956
957 let pre_sysvar_account = previous_bank
958 .get_account(&solana_sysvar::epoch_rewards::id())
959 .unwrap_or_default();
960 let pre_epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
961 solana_account::from_account(&pre_sysvar_account).unwrap_or_default();
962 let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
963 let curr_bank = Bank::new_from_parent_with_bank_forks(
964 bank_forks.as_ref(),
965 previous_bank.clone(),
966 SlotLeader::default(),
967 slot,
968 );
969 let post_cap = curr_bank.capitalization();
970
971 if slot < SLOTS_PER_EPOCH {
972 assert_matches!(
974 curr_bank.get_reward_interval(),
975 RewardInterval::OutsideInterval
976 );
977 assert_eq!(post_cap, pre_cap);
978 } else if slot == SLOTS_PER_EPOCH {
979 assert_matches!(
982 curr_bank.get_reward_interval(),
983 RewardInterval::InsideInterval
984 );
985
986 assert!(curr_bank.is_calculated());
988
989 assert!(
991 curr_bank
992 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
993 .is_some()
994 );
995 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
996 starting_hash = Some(curr_bank.parent_hash);
997
998 let account = curr_bank
1001 .get_account(&solana_sysvar::epoch_rewards::id())
1002 .unwrap();
1003 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1004 solana_account::from_account(&account).unwrap();
1005 reward_distribution_completion_slot =
1006 Some(SLOTS_PER_EPOCH + epoch_rewards.num_partitions);
1007 } else if slot
1008 < reward_distribution_completion_slot
1009 .expect("epoch boundary must set completion slot")
1010 {
1011 assert_matches!(
1013 curr_bank.get_reward_interval(),
1014 RewardInterval::InsideInterval
1015 );
1016 assert!(curr_bank.is_partitioned());
1017
1018 let account = curr_bank
1019 .get_account(&solana_sysvar::epoch_rewards::id())
1020 .unwrap();
1021 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1022 solana_account::from_account(&account).unwrap();
1023 assert_eq!(
1024 post_cap,
1025 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
1026 );
1027
1028 if slot == SLOTS_PER_EPOCH + 1 {
1029 assert!(
1032 curr_bank
1033 .get_epoch_rewards_from_cache(&starting_hash.unwrap())
1034 .is_some()
1035 );
1036 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
1037
1038 let _ = bank_forks.write().unwrap().set_root(slot - 1, None, None);
1041 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
1042 }
1043 } else if slot
1044 == reward_distribution_completion_slot
1045 .expect("epoch boundary must set completion slot")
1046 {
1047 assert_matches!(
1051 curr_bank.get_reward_interval(),
1052 RewardInterval::OutsideInterval
1053 );
1054
1055 let account = curr_bank
1056 .get_account(&solana_sysvar::epoch_rewards::id())
1057 .unwrap();
1058 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
1059 solana_account::from_account(&account).unwrap();
1060 assert_eq!(
1061 post_cap,
1062 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
1063 );
1064 } else {
1065 assert_matches!(
1068 curr_bank.get_reward_interval(),
1069 RewardInterval::OutsideInterval
1070 );
1071 assert_eq!(post_cap, pre_cap);
1072 break;
1073 }
1074 previous_bank = curr_bank;
1075 }
1076 }
1077
1078 #[test]
1080 fn test_rewards_period_system_transfer() {
1081 let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
1082 let validator_keypairs = vec![&validator_vote_keypairs];
1083 let GenesisConfigInfo {
1084 mut genesis_config,
1085 mint_keypair,
1086 ..
1087 } = create_genesis_config_with_vote_accounts(
1088 1_000_000_000,
1089 &validator_keypairs,
1090 vec![1_000_000_000; 1],
1091 );
1092
1093 let vote_key = validator_keypairs[0].vote_keypair.pubkey();
1095 let vote_account = genesis_config
1096 .accounts
1097 .iter()
1098 .find(|(address, _)| **address == vote_key)
1099 .map(|(_, account)| account)
1100 .unwrap()
1101 .clone();
1102
1103 let new_stake_signer = Keypair::new();
1104 let new_stake_address = new_stake_signer.pubkey();
1105 let new_stake_account = Account::from(stake_utils::create_stake_account(
1106 &new_stake_address,
1107 &vote_key,
1108 &vote_account.into(),
1109 &genesis_config.rent,
1110 2_000_000_000,
1111 ));
1112 genesis_config
1113 .accounts
1114 .extend(vec![(new_stake_address, new_stake_account)]);
1115
1116 let (mut previous_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1117 let num_slots_in_epoch = previous_bank.get_slots_in_epoch(previous_bank.epoch());
1118 assert_eq!(num_slots_in_epoch, 32);
1119
1120 let transfer_amount = 5_000;
1121
1122 for slot in 1..=num_slots_in_epoch + 2 {
1123 let bank = Bank::new_from_parent_with_bank_forks(
1124 bank_forks.as_ref(),
1125 previous_bank.clone(),
1126 SlotLeader::default(),
1127 slot,
1128 );
1129
1130 let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash());
1133 let vote = vote_transaction::new_tower_sync_transaction(
1134 tower_sync,
1135 previous_bank.last_blockhash(),
1136 &validator_vote_keypairs.node_keypair,
1137 &validator_vote_keypairs.vote_keypair,
1138 &validator_vote_keypairs.vote_keypair,
1139 None,
1140 );
1141 bank.process_transaction(&vote).unwrap();
1142
1143 let system_tx = system_transaction::transfer(
1145 &mint_keypair,
1146 &new_stake_address,
1147 transfer_amount,
1148 bank.last_blockhash(),
1149 );
1150 let system_result = bank.process_transaction(&system_tx);
1151
1152 assert!(system_result.is_ok());
1154
1155 bank.register_unique_recent_blockhash_for_test();
1159 previous_bank = bank;
1160 }
1161 }
1162
1163 #[test]
1164 fn test_get_rewards_and_partitions() {
1165 let starting_slot = SLOTS_PER_EPOCH - 1;
1166 let num_rewards = 100;
1167 let stake_account_stores_per_block = 50;
1168 let (RewardBank { bank, .. }, bank_forks) =
1169 create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
1170
1171 assert_eq!(
1174 bank.get_rewards_and_num_partitions(),
1175 KeyedRewardsAndNumPartitions {
1176 keyed_rewards: vec![],
1177 num_partitions: None,
1178 }
1179 );
1180
1181 let epoch_boundary_bank = Bank::new_from_parent_with_bank_forks(
1182 bank_forks.as_ref(),
1183 bank,
1184 SlotLeader::default(),
1185 SLOTS_PER_EPOCH,
1186 );
1187 let KeyedRewardsAndNumPartitions {
1189 keyed_rewards,
1190 num_partitions,
1191 } = epoch_boundary_bank.get_rewards_and_num_partitions();
1192 for (_pubkey, reward) in keyed_rewards.iter() {
1193 assert_eq!(reward.reward_type, RewardType::Voting);
1194 }
1195 assert_eq!(keyed_rewards.len(), num_rewards);
1196 let expected_num_partitions = (num_rewards as u64)
1197 .div_ceil(epoch_boundary_bank.partitioned_rewards_stake_account_stores_per_block())
1198 .clamp(1, (SLOTS_PER_EPOCH / 10).max(1));
1199 assert_eq!(num_partitions, Some(expected_num_partitions));
1200
1201 let mut total_staking_rewards = 0;
1202 let mut previous_bank = epoch_boundary_bank;
1203 for partition_index in 0..expected_num_partitions {
1204 let partition_bank = Arc::new(Bank::new_from_parent(
1205 previous_bank,
1206 SlotLeader::default(),
1207 SLOTS_PER_EPOCH + partition_index + 1,
1208 ));
1209 let KeyedRewardsAndNumPartitions {
1213 keyed_rewards,
1214 num_partitions,
1215 } = partition_bank.get_rewards_and_num_partitions();
1216 for (_pubkey, reward) in keyed_rewards.iter() {
1217 assert_eq!(reward.reward_type, RewardType::Staking);
1218 }
1219 total_staking_rewards += keyed_rewards.len();
1220 assert_eq!(num_partitions, None);
1221 previous_bank = partition_bank;
1222 }
1223
1224 assert_eq!(total_staking_rewards, num_rewards);
1226 let bank = Bank::new_from_parent(
1227 previous_bank,
1228 SlotLeader::default(),
1229 SLOTS_PER_EPOCH + expected_num_partitions + 1,
1230 );
1231 assert_eq!(
1234 bank.get_rewards_and_num_partitions(),
1235 KeyedRewardsAndNumPartitions {
1236 keyed_rewards: vec![],
1237 num_partitions: None,
1238 }
1239 );
1240 }
1241
1242 #[test]
1243 fn test_rewards_and_partitions_should_record() {
1244 let reward = RewardInfo {
1245 reward_type: RewardType::Voting,
1246 lamports: 55,
1247 post_balance: 5555,
1248 commission_bps: Some(500),
1249 };
1250
1251 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1252 keyed_rewards: vec![],
1253 num_partitions: None,
1254 };
1255 assert!(!rewards_and_partitions.should_record());
1256
1257 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1258 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1259 num_partitions: None,
1260 };
1261 assert!(rewards_and_partitions.should_record());
1262
1263 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1264 keyed_rewards: vec![],
1265 num_partitions: Some(42),
1266 };
1267 assert!(rewards_and_partitions.should_record());
1268
1269 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1270 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1271 num_partitions: Some(42),
1272 };
1273 assert!(rewards_and_partitions.should_record());
1274 }
1275}