1mod calculation;
2mod distribution;
3mod epoch_rewards_hasher;
4mod sysvar;
5
6use {
7 super::Bank,
8 crate::{
9 inflation_rewards::points::PointValue, stake_account::StakeAccount,
10 stake_history::StakeHistory,
11 },
12 solana_account::{AccountSharedData, ReadableAccount},
13 solana_accounts_db::{
14 partitioned_rewards::PartitionedEpochRewardsConfig,
15 stake_rewards::StakeReward,
16 storable_accounts::{AccountForStorage, StorableAccounts},
17 },
18 solana_clock::Slot,
19 solana_pubkey::Pubkey,
20 solana_reward_info::RewardInfo,
21 solana_stake_interface::state::{Delegation, Stake},
22 solana_vote::vote_account::VoteAccounts,
23 std::sync::Arc,
24};
25
26const REWARD_CALCULATION_NUM_BLOCKS: u64 = 1;
29
30#[derive(Debug, Clone, PartialEq)]
31pub(crate) struct PartitionedStakeReward {
32 pub stake_pubkey: Pubkey,
34 pub stake: Stake,
36 pub stake_reward: u64,
38 pub commission: u8,
40}
41
42type PartitionedStakeRewards = Vec<PartitionedStakeReward>;
43
44#[derive(Debug, Clone, PartialEq)]
45pub(crate) struct StartBlockHeightAndRewards {
46 pub(crate) distribution_starting_block_height: u64,
48 pub(crate) all_stake_rewards: Arc<Vec<PartitionedStakeReward>>,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53pub(crate) struct StartBlockHeightAndPartitionedRewards {
54 pub(crate) distribution_starting_block_height: u64,
56
57 pub(crate) all_stake_rewards: Arc<Vec<PartitionedStakeReward>>,
59
60 pub(crate) partition_indices: Vec<Vec<usize>>,
64}
65
66#[derive(Debug, Clone, PartialEq, Default)]
68pub(crate) enum EpochRewardStatus {
69 Active(EpochRewardPhase),
74 #[default]
76 Inactive,
77}
78
79#[derive(Debug, Clone, PartialEq)]
80pub(crate) enum EpochRewardPhase {
81 Calculation(StartBlockHeightAndRewards),
82 Distribution(StartBlockHeightAndPartitionedRewards),
83}
84
85#[derive(Debug, Default)]
86pub(super) struct VoteRewardsAccounts {
87 pub(super) accounts_with_rewards: Vec<(Pubkey, RewardInfo, AccountSharedData)>,
89 pub(super) total_vote_rewards_lamports: u64,
91}
92
93pub(super) struct VoteRewardsAccountsStorable<'a> {
95 pub slot: Slot,
96 pub vote_rewards_accounts: &'a VoteRewardsAccounts,
97}
98
99impl<'a> StorableAccounts<'a> for VoteRewardsAccountsStorable<'a> {
100 fn account<Ret>(
101 &self,
102 index: usize,
103 mut callback: impl for<'local> FnMut(AccountForStorage<'local>) -> Ret,
104 ) -> Ret {
105 let (pubkey, _, account) = &self.vote_rewards_accounts.accounts_with_rewards[index];
106 callback((pubkey, account).into())
107 }
108
109 fn is_zero_lamport(&self, index: usize) -> bool {
110 self.vote_rewards_accounts.accounts_with_rewards[index]
111 .2
112 .lamports()
113 == 0
114 }
115
116 fn data_len(&self, index: usize) -> usize {
117 self.vote_rewards_accounts.accounts_with_rewards[index]
118 .2
119 .data()
120 .len()
121 }
122
123 fn pubkey(&self, index: usize) -> &Pubkey {
124 &self.vote_rewards_accounts.accounts_with_rewards[index].0
125 }
126
127 fn slot(&self, _index: usize) -> Slot {
128 self.target_slot()
129 }
130
131 fn target_slot(&self) -> Slot {
132 self.slot
133 }
134
135 fn len(&self) -> usize {
136 self.vote_rewards_accounts.accounts_with_rewards.len()
137 }
138}
139
140#[derive(Debug, Default)]
141pub(super) struct StakeRewardCalculation {
143 stake_rewards: Arc<PartitionedStakeRewards>,
145 total_stake_rewards_lamports: u64,
147}
148
149#[derive(Debug)]
150struct CalculateValidatorRewardsResult {
151 vote_rewards_accounts: VoteRewardsAccounts,
152 stake_reward_calculation: StakeRewardCalculation,
153 point_value: PointValue,
154}
155
156impl Default for CalculateValidatorRewardsResult {
157 fn default() -> Self {
158 Self {
159 vote_rewards_accounts: VoteRewardsAccounts::default(),
160 stake_reward_calculation: StakeRewardCalculation::default(),
161 point_value: PointValue {
162 points: 0,
163 rewards: 0,
164 },
165 }
166 }
167}
168
169pub(super) struct EpochRewardCalculateParamInfo<'a> {
171 pub(super) stake_history: StakeHistory,
172 pub(super) stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)>,
173 pub(super) cached_vote_accounts: &'a VoteAccounts,
174}
175
176#[derive(Debug)]
180pub(super) struct PartitionedRewardsCalculation {
181 pub(super) vote_account_rewards: VoteRewardsAccounts,
182 pub(super) stake_rewards: StakeRewardCalculation,
183 pub(super) validator_rate: f64,
184 pub(super) foundation_rate: f64,
185 pub(super) prev_epoch_duration_in_years: f64,
186 pub(super) capitalization: u64,
187 point_value: PointValue,
188}
189
190pub(super) struct CalculateRewardsAndDistributeVoteRewardsResult {
191 pub(super) distributed_rewards: u64,
193 pub(super) point_value: PointValue,
198 pub(super) stake_rewards: Arc<Vec<PartitionedStakeReward>>,
200}
201
202pub(crate) type StakeRewards = Vec<StakeReward>;
203
204#[derive(Debug, PartialEq)]
205pub struct KeyedRewardsAndNumPartitions {
206 pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
207 pub num_partitions: Option<u64>,
208}
209
210impl KeyedRewardsAndNumPartitions {
211 pub fn should_record(&self) -> bool {
212 !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
213 }
214}
215
216impl Bank {
217 pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
218 let keyed_rewards = self.rewards.read().unwrap().clone();
219 let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
220 let epoch_schedule = self.epoch_schedule();
223 let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
224 let is_first_block_in_epoch = self.epoch() > parent_epoch;
225
226 let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
227 .then_some(epoch_rewards_sysvar.num_partitions);
228 KeyedRewardsAndNumPartitions {
229 keyed_rewards,
230 num_partitions,
231 }
232 }
233
234 pub(crate) fn set_epoch_reward_status_calculation(
235 &mut self,
236 distribution_starting_block_height: u64,
237 stake_rewards: Arc<Vec<PartitionedStakeReward>>,
238 ) {
239 self.epoch_reward_status =
240 EpochRewardStatus::Active(EpochRewardPhase::Calculation(StartBlockHeightAndRewards {
241 distribution_starting_block_height,
242 all_stake_rewards: stake_rewards,
243 }));
244 }
245
246 pub(crate) fn set_epoch_reward_status_distribution(
247 &mut self,
248 distribution_starting_block_height: u64,
249 all_stake_rewards: Arc<Vec<PartitionedStakeReward>>,
250 partition_indices: Vec<Vec<usize>>,
251 ) {
252 self.epoch_reward_status = EpochRewardStatus::Active(EpochRewardPhase::Distribution(
253 StartBlockHeightAndPartitionedRewards {
254 distribution_starting_block_height,
255 all_stake_rewards,
256 partition_indices,
257 },
258 ));
259 }
260
261 pub(super) fn partitioned_epoch_rewards_config(&self) -> &PartitionedEpochRewardsConfig {
262 &self
263 .rc
264 .accounts
265 .accounts_db
266 .partitioned_epoch_rewards_config
267 }
268
269 pub(super) fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 {
271 self.partitioned_epoch_rewards_config()
272 .stake_account_stores_per_block
273 }
274
275 pub(super) fn get_reward_distribution_num_blocks(
277 &self,
278 rewards: &PartitionedStakeRewards,
279 ) -> u64 {
280 let total_stake_accounts = rewards.len();
281 if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() {
282 1
283 } else {
284 const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10;
285 let num_chunks = total_stake_accounts
286 .div_ceil(self.partitioned_rewards_stake_account_stores_per_block() as usize)
287 as u64;
288
289 num_chunks.clamp(
291 1,
292 (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1),
293 )
294 }
295 }
296
297 pub fn force_reward_interval_end_for_tests(&mut self) {
299 self.epoch_reward_status = EpochRewardStatus::Inactive;
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use {
306 super::*,
307 crate::{
308 bank::tests::{create_genesis_config, new_bank_from_parent_with_bank_forks},
309 bank_forks::BankForks,
310 genesis_utils::{
311 create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs,
312 },
313 runtime_config::RuntimeConfig,
314 },
315 assert_matches::assert_matches,
316 solana_account::{state_traits::StateMut, Account},
317 solana_accounts_db::accounts_db::{AccountsDbConfig, ACCOUNTS_DB_CONFIG_FOR_TESTING},
318 solana_epoch_schedule::EpochSchedule,
319 solana_hash::Hash,
320 solana_keypair::Keypair,
321 solana_native_token::LAMPORTS_PER_SOL,
322 solana_reward_info::RewardType,
323 solana_signer::Signer,
324 solana_stake_interface::{error::StakeError, state::StakeStateV2},
325 solana_system_transaction as system_transaction,
326 solana_transaction::Transaction,
327 solana_vote::vote_transaction,
328 solana_vote_interface::state::{VoteStateVersions, MAX_LOCKOUT_HISTORY},
329 solana_vote_program::vote_state::{self, TowerSync},
330 std::sync::{Arc, RwLock},
331 };
332
333 impl PartitionedStakeReward {
334 fn maybe_from(stake_reward: &StakeReward) -> Option<Self> {
335 if let Ok(StakeStateV2::Stake(_meta, stake, _flags)) =
336 stake_reward.stake_account.state()
337 {
338 Some(Self {
339 stake_pubkey: stake_reward.stake_pubkey,
340 stake,
341 stake_reward: stake_reward.stake_reward_info.lamports as u64,
342 commission: stake_reward.stake_reward_info.commission.unwrap(),
343 })
344 } else {
345 None
346 }
347 }
348
349 pub fn new_random() -> Self {
350 Self::maybe_from(&StakeReward::new_random()).unwrap()
351 }
352 }
353
354 pub fn build_partitioned_stake_rewards(
355 stake_rewards: &[PartitionedStakeReward],
356 partition_indices: &[Vec<usize>],
357 ) -> Vec<Vec<PartitionedStakeReward>> {
358 partition_indices
359 .iter()
360 .map(|partition_index| {
361 partition_index
364 .iter()
365 .map(|&index| stake_rewards[index].clone())
366 .collect::<Vec<_>>()
367 })
368 .collect::<Vec<_>>()
369 }
370
371 pub fn convert_rewards(
372 stake_rewards: impl IntoIterator<Item = StakeReward>,
373 ) -> PartitionedStakeRewards {
374 stake_rewards
375 .into_iter()
376 .map(|stake_reward| PartitionedStakeReward::maybe_from(&stake_reward).unwrap())
377 .collect()
378 }
379
380 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
381 enum RewardInterval {
382 InsideInterval,
384 OutsideInterval,
386 }
387
388 impl Bank {
389 fn get_reward_interval(&self) -> RewardInterval {
391 if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) {
392 RewardInterval::InsideInterval
393 } else {
394 RewardInterval::OutsideInterval
395 }
396 }
397
398 fn is_calculated(&self) -> bool {
399 matches!(
400 self.epoch_reward_status,
401 EpochRewardStatus::Active(EpochRewardPhase::Calculation(_))
402 )
403 }
404
405 fn is_partitioned(&self) -> bool {
406 matches!(
407 self.epoch_reward_status,
408 EpochRewardStatus::Active(EpochRewardPhase::Distribution(_))
409 )
410 }
411
412 fn get_epoch_rewards_from_cache(
413 &self,
414 parent_hash: &Hash,
415 ) -> Option<Arc<PartitionedRewardsCalculation>> {
416 self.epoch_rewards_calculation_cache
417 .lock()
418 .unwrap()
419 .get(parent_hash)
420 .cloned()
421 }
422
423 fn get_epoch_rewards_cache_len(&self) -> usize {
424 self.epoch_rewards_calculation_cache.lock().unwrap().len()
425 }
426 }
427
428 pub(super) const SLOTS_PER_EPOCH: u64 = 32;
429
430 pub(super) struct RewardBank {
431 pub(super) bank: Arc<Bank>,
432 pub(super) voters: Vec<Pubkey>,
433 pub(super) stakers: Vec<Pubkey>,
434 }
435
436 pub(super) fn create_default_reward_bank(
438 expected_num_delegations: usize,
439 advance_num_slots: u64,
440 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
441 create_reward_bank(
442 expected_num_delegations,
443 PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
444 advance_num_slots,
445 )
446 }
447
448 pub(super) fn create_reward_bank(
449 expected_num_delegations: usize,
450 stake_account_stores_per_block: u64,
451 advance_num_slots: u64,
452 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
453 create_reward_bank_with_specific_stakes(
454 vec![2_000_000_000; expected_num_delegations],
455 stake_account_stores_per_block,
456 advance_num_slots,
457 )
458 }
459
460 pub(super) fn create_reward_bank_with_specific_stakes(
461 stakes: Vec<u64>,
462 stake_account_stores_per_block: u64,
463 advance_num_slots: u64,
464 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
465 let validator_keypairs = (0..stakes.len())
466 .map(|_| ValidatorVoteKeypairs::new_rand())
467 .collect::<Vec<_>>();
468
469 let GenesisConfigInfo {
470 mut genesis_config, ..
471 } = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stakes);
472 genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
473
474 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
475 accounts_db_config.partitioned_epoch_rewards_config =
476 PartitionedEpochRewardsConfig::new_for_test(stake_account_stores_per_block);
477
478 let bank = Bank::new_with_paths(
479 &genesis_config,
480 Arc::new(RuntimeConfig::default()),
481 Vec::new(),
482 None,
483 None,
484 false,
485 Some(accounts_db_config),
486 None,
487 Some(Pubkey::new_unique()),
488 Arc::default(),
489 None,
490 None,
491 );
492
493 for validator_vote_keypairs in &validator_keypairs {
496 let vote_id = validator_vote_keypairs.vote_keypair.pubkey();
497 let mut vote_account = bank.get_account(&vote_id).unwrap();
498 let mut vote_state = Some(vote_state::from(&vote_account).unwrap());
500 for i in 0..MAX_LOCKOUT_HISTORY + 42 {
501 if let Some(v) = vote_state.as_mut() {
502 vote_state::process_slot_vote_unchecked(v, i as u64)
503 }
504 let versioned = VoteStateVersions::V3(Box::new(vote_state.take().unwrap()));
505 vote_state::to(&versioned, &mut vote_account).unwrap();
506 match versioned {
507 VoteStateVersions::V3(v) => {
508 vote_state = Some(*v);
509 }
510 _ => panic!("Has to be of type Current"),
511 };
512 }
513 bank.store_account_and_update_capitalization(&vote_id, &vote_account);
514 }
515
516 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
519 let bank = new_bank_from_parent_with_bank_forks(
520 &bank_forks,
521 bank,
522 &Pubkey::default(),
523 advance_num_slots,
524 );
525
526 (
527 RewardBank {
528 bank,
529 voters: validator_keypairs
530 .iter()
531 .map(|k| k.vote_keypair.pubkey())
532 .collect(),
533 stakers: validator_keypairs
534 .iter()
535 .map(|k| k.stake_keypair.pubkey())
536 .collect(),
537 },
538 bank_forks,
539 )
540 }
541
542 #[test]
543 fn test_force_reward_interval_end() {
544 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
545 let mut bank = Bank::new_for_tests(&genesis_config);
546
547 let expected_num = 100;
548
549 let stake_rewards = (0..expected_num)
550 .map(|_| PartitionedStakeReward::new_random())
551 .collect::<Vec<_>>();
552
553 let partition_indices = vec![(0..expected_num).collect()];
554
555 bank.set_epoch_reward_status_distribution(
556 bank.block_height() + REWARD_CALCULATION_NUM_BLOCKS,
557 Arc::new(stake_rewards),
558 partition_indices,
559 );
560 assert!(bank.get_reward_interval() == RewardInterval::InsideInterval);
561
562 bank.force_reward_interval_end_for_tests();
563 assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval);
564 }
565
566 #[test]
569 fn test_get_reward_distribution_num_blocks_cap() {
570 let (mut genesis_config, _mint_keypair) =
571 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
572 genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false);
573
574 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
576 accounts_db_config.partitioned_epoch_rewards_config =
577 PartitionedEpochRewardsConfig::new_for_test(10);
578
579 let bank = Bank::new_with_paths(
580 &genesis_config,
581 Arc::new(RuntimeConfig::default()),
582 Vec::new(),
583 None,
584 None,
585 false,
586 Some(accounts_db_config),
587 None,
588 Some(Pubkey::new_unique()),
589 Arc::default(),
590 None,
591 None,
592 );
593
594 let stake_account_stores_per_block =
595 bank.partitioned_rewards_stake_account_stores_per_block();
596 assert_eq!(stake_account_stores_per_block, 10);
597
598 let check_num_reward_distribution_blocks =
599 |num_stakes: u64, expected_num_reward_distribution_blocks: u64| {
600 let stake_rewards = (0..num_stakes)
602 .map(|_| PartitionedStakeReward::new_random())
603 .collect::<Vec<_>>();
604
605 assert_eq!(
606 bank.get_reward_distribution_num_blocks(&stake_rewards),
607 expected_num_reward_distribution_blocks
608 );
609 };
610
611 for test_record in [
612 (0, 1),
614 (1, 1),
615 (stake_account_stores_per_block, 1),
616 (2 * stake_account_stores_per_block - 1, 2),
617 (2 * stake_account_stores_per_block, 2),
618 (3 * stake_account_stores_per_block - 1, 3),
619 (3 * stake_account_stores_per_block, 3),
620 (4 * stake_account_stores_per_block, 3), (5 * stake_account_stores_per_block, 3), ] {
623 check_num_reward_distribution_blocks(test_record.0, test_record.1);
624 }
625 }
626
627 #[test]
629 fn test_get_reward_distribution_num_blocks_normal() {
630 solana_logger::setup();
631 let (mut genesis_config, _mint_keypair) =
632 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
633 genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
634
635 let bank = Bank::new_for_tests(&genesis_config);
636
637 let expected_num = 8192;
639 let stake_rewards = (0..expected_num)
640 .map(|_| PartitionedStakeReward::new_random())
641 .collect::<Vec<_>>();
642
643 assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2);
644 }
645
646 #[test]
649 fn test_get_reward_distribution_num_blocks_warmup() {
650 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
651
652 let bank = Bank::new_for_tests(&genesis_config);
653 let rewards = vec![];
654 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
655 }
656
657 #[test]
658 fn test_rewards_computation_and_partitioned_distribution_one_block() {
659 solana_logger::setup();
660
661 let starting_slot = SLOTS_PER_EPOCH - 1;
662 let (
663 RewardBank {
664 bank: mut previous_bank,
665 ..
666 },
667 bank_forks,
668 ) = create_default_reward_bank(100, starting_slot - 1);
669
670 for slot in starting_slot..=(2 * SLOTS_PER_EPOCH) + 2 {
672 let pre_cap = previous_bank.capitalization();
673 let curr_bank = new_bank_from_parent_with_bank_forks(
674 bank_forks.as_ref(),
675 previous_bank.clone(),
676 &Pubkey::default(),
677 slot,
678 );
679 let post_cap = curr_bank.capitalization();
680
681 if slot % SLOTS_PER_EPOCH == 0 {
682 assert_matches!(
685 curr_bank.get_reward_interval(),
686 RewardInterval::InsideInterval
687 );
688
689 assert!(curr_bank.is_calculated());
690
691 assert!(curr_bank
693 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
694 .is_some());
695 assert_eq!(post_cap, pre_cap);
696
697 let _ = bank_forks.write().unwrap().set_root(slot, None, None);
700 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
701 } else if slot == SLOTS_PER_EPOCH + 1 {
702 assert_matches!(
708 curr_bank.get_reward_interval(),
709 RewardInterval::OutsideInterval
710 );
711 let account = curr_bank
712 .get_account(&solana_sysvar::epoch_rewards::id())
713 .unwrap();
714 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
715 solana_account::from_account(&account).unwrap();
716 assert_eq!(post_cap, pre_cap + epoch_rewards.distributed_rewards);
717 } else {
718 assert_matches!(
723 curr_bank.get_reward_interval(),
724 RewardInterval::OutsideInterval
725 );
726
727 assert_eq!(post_cap, pre_cap);
729 }
730 if slot >= SLOTS_PER_EPOCH {
733 let epoch_rewards_lamports =
734 curr_bank.get_balance(&solana_sysvar::epoch_rewards::id());
735 assert!(epoch_rewards_lamports > 0);
736 }
737 previous_bank = curr_bank;
738 }
739 }
740
741 #[test]
743 fn test_rewards_computation_and_partitioned_distribution_two_blocks() {
744 solana_logger::setup();
745
746 let starting_slot = SLOTS_PER_EPOCH - 1;
747 let (
748 RewardBank {
749 bank: mut previous_bank,
750 ..
751 },
752 bank_forks,
753 ) = create_reward_bank(100, 50, starting_slot - 1);
754 let mut starting_hash = None;
755
756 for slot in starting_slot..=SLOTS_PER_EPOCH + 3 {
758 let pre_cap = previous_bank.capitalization();
759
760 let pre_sysvar_account = previous_bank
761 .get_account(&solana_sysvar::epoch_rewards::id())
762 .unwrap_or_default();
763 let pre_epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
764 solana_account::from_account(&pre_sysvar_account).unwrap_or_default();
765 let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
766 let curr_bank = new_bank_from_parent_with_bank_forks(
767 bank_forks.as_ref(),
768 previous_bank.clone(),
769 &Pubkey::default(),
770 slot,
771 );
772 let post_cap = curr_bank.capitalization();
773
774 if slot == SLOTS_PER_EPOCH {
775 assert_matches!(
778 curr_bank.get_reward_interval(),
779 RewardInterval::InsideInterval
780 );
781
782 assert!(curr_bank.is_calculated());
784
785 assert!(curr_bank
787 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
788 .is_some());
789 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
790 starting_hash = Some(curr_bank.parent_hash);
791 } else if slot == SLOTS_PER_EPOCH + 1 {
792 assert_matches!(
796 curr_bank.get_reward_interval(),
797 RewardInterval::InsideInterval
798 );
799
800 assert!(curr_bank
803 .get_epoch_rewards_from_cache(&starting_hash.unwrap())
804 .is_some());
805 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
806
807 assert!(curr_bank.is_partitioned());
809
810 let account = curr_bank
811 .get_account(&solana_sysvar::epoch_rewards::id())
812 .unwrap();
813 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
814 solana_account::from_account(&account).unwrap();
815 assert_eq!(
816 post_cap,
817 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
818 );
819
820 let _ = bank_forks.write().unwrap().set_root(slot - 1, None, None);
823 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
824 } else if slot == SLOTS_PER_EPOCH + 2 {
825 assert_matches!(
831 curr_bank.get_reward_interval(),
832 RewardInterval::OutsideInterval
833 );
834
835 let account = curr_bank
836 .get_account(&solana_sysvar::epoch_rewards::id())
837 .unwrap();
838 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
839 solana_account::from_account(&account).unwrap();
840 assert_eq!(
841 post_cap,
842 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
843 );
844 } else {
845 assert_matches!(
850 curr_bank.get_reward_interval(),
851 RewardInterval::OutsideInterval
852 );
853
854 assert_eq!(post_cap, pre_cap);
856 }
857 previous_bank = curr_bank;
858 }
859 }
860
861 #[test]
865 fn test_program_execution_restricted_for_stake_account_in_reward_period() {
866 use solana_transaction_error::TransactionError::InstructionError;
867
868 let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
869 let validator_keypairs = vec![&validator_vote_keypairs];
870 let GenesisConfigInfo {
871 mut genesis_config,
872 mint_keypair,
873 ..
874 } = create_genesis_config_with_vote_accounts(
875 1_000_000_000,
876 &validator_keypairs,
877 vec![1_000_000_000; 1],
878 );
879
880 let vote_key = validator_keypairs[0].vote_keypair.pubkey();
882 let vote_account = genesis_config
883 .accounts
884 .iter()
885 .find(|(&address, _)| address == vote_key)
886 .map(|(_, account)| account)
887 .unwrap()
888 .clone();
889
890 let new_stake_signer = Keypair::new();
891 let new_stake_address = new_stake_signer.pubkey();
892 let new_stake_account = Account::from(solana_stake_program::stake_state::create_account(
893 &new_stake_address,
894 &vote_key,
895 &vote_account.into(),
896 &genesis_config.rent,
897 2_000_000_000,
898 ));
899 genesis_config
900 .accounts
901 .extend(vec![(new_stake_address, new_stake_account)]);
902
903 let (mut previous_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
904 let num_slots_in_epoch = previous_bank.get_slots_in_epoch(previous_bank.epoch());
905 assert_eq!(num_slots_in_epoch, 32);
906
907 let transfer_amount = 5_000;
908
909 for slot in 1..=num_slots_in_epoch + 2 {
910 let bank = new_bank_from_parent_with_bank_forks(
911 bank_forks.as_ref(),
912 previous_bank.clone(),
913 &Pubkey::default(),
914 slot,
915 );
916
917 let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash());
920 let vote = vote_transaction::new_tower_sync_transaction(
921 tower_sync,
922 previous_bank.last_blockhash(),
923 &validator_vote_keypairs.node_keypair,
924 &validator_vote_keypairs.vote_keypair,
925 &validator_vote_keypairs.vote_keypair,
926 None,
927 );
928 bank.process_transaction(&vote).unwrap();
929
930 let system_tx = system_transaction::transfer(
932 &mint_keypair,
933 &new_stake_address,
934 transfer_amount,
935 bank.last_blockhash(),
936 );
937 let system_result = bank.process_transaction(&system_tx);
938
939 assert!(system_result.is_ok());
941
942 let stake_ix = solana_stake_interface::instruction::withdraw(
944 &new_stake_address,
945 &new_stake_address,
946 &mint_keypair.pubkey(),
947 transfer_amount,
948 None,
949 );
950 let stake_tx = Transaction::new_signed_with_payer(
951 &[stake_ix],
952 Some(&mint_keypair.pubkey()),
953 &[&mint_keypair, &new_stake_signer],
954 bank.last_blockhash(),
955 );
956 let stake_result = bank.process_transaction(&stake_tx);
957
958 if slot == num_slots_in_epoch {
959 assert_eq!(
963 stake_result,
964 Err(InstructionError(0, StakeError::EpochRewardsActive.into()))
965 );
966 } else {
967 assert!(stake_result.is_ok());
970 }
971
972 bank.register_unique_recent_blockhash_for_test();
976 previous_bank = bank;
977 }
978 }
979
980 #[test]
981 fn test_get_rewards_and_partitions() {
982 let starting_slot = SLOTS_PER_EPOCH - 1;
983 let num_rewards = 100;
984 let stake_account_stores_per_block = 50;
985 let (RewardBank { bank, .. }, _) =
986 create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
987
988 assert_eq!(
991 bank.get_rewards_and_num_partitions(),
992 KeyedRewardsAndNumPartitions {
993 keyed_rewards: vec![],
994 num_partitions: None,
995 }
996 );
997
998 let epoch_boundary_bank = Arc::new(Bank::new_from_parent(
999 bank,
1000 &Pubkey::default(),
1001 SLOTS_PER_EPOCH,
1002 ));
1003 let KeyedRewardsAndNumPartitions {
1005 keyed_rewards,
1006 num_partitions,
1007 } = epoch_boundary_bank.get_rewards_and_num_partitions();
1008 for (_pubkey, reward) in keyed_rewards.iter() {
1009 assert_eq!(reward.reward_type, RewardType::Voting);
1010 }
1011 assert_eq!(keyed_rewards.len(), num_rewards);
1012 assert_eq!(
1013 num_partitions,
1014 Some(num_rewards as u64 / stake_account_stores_per_block)
1015 );
1016
1017 let mut total_staking_rewards = 0;
1018
1019 let partition0_bank = Arc::new(Bank::new_from_parent(
1020 epoch_boundary_bank,
1021 &Pubkey::default(),
1022 SLOTS_PER_EPOCH + 1,
1023 ));
1024 let KeyedRewardsAndNumPartitions {
1027 keyed_rewards,
1028 num_partitions,
1029 } = partition0_bank.get_rewards_and_num_partitions();
1030 for (_pubkey, reward) in keyed_rewards.iter() {
1031 assert_eq!(reward.reward_type, RewardType::Staking);
1032 }
1033 total_staking_rewards += keyed_rewards.len();
1034 assert_eq!(num_partitions, None);
1035
1036 let partition1_bank = Arc::new(Bank::new_from_parent(
1037 partition0_bank,
1038 &Pubkey::default(),
1039 SLOTS_PER_EPOCH + 2,
1040 ));
1041 let KeyedRewardsAndNumPartitions {
1044 keyed_rewards,
1045 num_partitions,
1046 } = partition1_bank.get_rewards_and_num_partitions();
1047 for (_pubkey, reward) in keyed_rewards.iter() {
1048 assert_eq!(reward.reward_type, RewardType::Staking);
1049 }
1050 total_staking_rewards += keyed_rewards.len();
1051 assert_eq!(num_partitions, None);
1052
1053 assert_eq!(total_staking_rewards, num_rewards);
1055
1056 let bank = Bank::new_from_parent(partition1_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 3);
1057 assert_eq!(
1060 bank.get_rewards_and_num_partitions(),
1061 KeyedRewardsAndNumPartitions {
1062 keyed_rewards: vec![],
1063 num_partitions: None,
1064 }
1065 );
1066 }
1067
1068 #[test]
1069 fn test_rewards_and_partitions_should_record() {
1070 let reward = RewardInfo {
1071 reward_type: RewardType::Voting,
1072 lamports: 55,
1073 post_balance: 5555,
1074 commission: Some(5),
1075 };
1076
1077 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1078 keyed_rewards: vec![],
1079 num_partitions: None,
1080 };
1081 assert!(!rewards_and_partitions.should_record());
1082
1083 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1084 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1085 num_partitions: None,
1086 };
1087 assert!(rewards_and_partitions.should_record());
1088
1089 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1090 keyed_rewards: vec![],
1091 num_partitions: Some(42),
1092 };
1093 assert!(rewards_and_partitions.should_record());
1094
1095 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1096 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1097 num_partitions: Some(42),
1098 };
1099 assert!(rewards_and_partitions.should_record());
1100 }
1101}