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