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,
13 solana_accounts_db::{
14 partitioned_rewards::PartitionedEpochRewardsConfig, stake_rewards::StakeReward,
15 },
16 solana_pubkey::Pubkey,
17 solana_reward_info::RewardInfo,
18 solana_stake_interface::state::{Delegation, Stake},
19 solana_vote::vote_account::VoteAccounts,
20 std::sync::Arc,
21};
22
23const REWARD_CALCULATION_NUM_BLOCKS: u64 = 1;
26
27#[derive(Debug, Clone, PartialEq)]
28pub(crate) struct PartitionedStakeReward {
29 pub stake_pubkey: Pubkey,
31 pub stake: Stake,
33 pub stake_reward: u64,
35 pub commission: u8,
37}
38
39type PartitionedStakeRewards = Vec<PartitionedStakeReward>;
40
41#[derive(Debug, Clone, PartialEq)]
42pub(crate) struct StartBlockHeightAndRewards {
43 pub(crate) distribution_starting_block_height: u64,
45 pub(crate) all_stake_rewards: Arc<Vec<PartitionedStakeReward>>,
47}
48
49#[derive(Debug, Clone, PartialEq)]
50pub(crate) struct StartBlockHeightAndPartitionedRewards {
51 pub(crate) distribution_starting_block_height: u64,
53
54 pub(crate) all_stake_rewards: Arc<Vec<PartitionedStakeReward>>,
56
57 pub(crate) partition_indices: Vec<Vec<usize>>,
61}
62
63#[derive(Debug, Clone, PartialEq, Default)]
65pub(crate) enum EpochRewardStatus {
66 Active(EpochRewardPhase),
71 #[default]
73 Inactive,
74}
75
76#[derive(Debug, Clone, PartialEq)]
77pub(crate) enum EpochRewardPhase {
78 Calculation(StartBlockHeightAndRewards),
79 Distribution(StartBlockHeightAndPartitionedRewards),
80}
81
82#[derive(Debug, Default)]
83pub(super) struct VoteRewardsAccounts {
84 pub(super) rewards: Vec<(Pubkey, RewardInfo)>,
87 pub(super) accounts_to_store: Vec<(Pubkey, AccountSharedData)>,
89 pub(super) total_vote_rewards_lamports: u64,
91}
92
93#[derive(Debug, Default)]
94pub(super) struct StakeRewardCalculation {
96 stake_rewards: Arc<PartitionedStakeRewards>,
98 total_stake_rewards_lamports: u64,
100}
101
102#[derive(Debug)]
103struct CalculateValidatorRewardsResult {
104 vote_rewards_accounts: VoteRewardsAccounts,
105 stake_reward_calculation: StakeRewardCalculation,
106 point_value: PointValue,
107}
108
109impl Default for CalculateValidatorRewardsResult {
110 fn default() -> Self {
111 Self {
112 vote_rewards_accounts: VoteRewardsAccounts::default(),
113 stake_reward_calculation: StakeRewardCalculation::default(),
114 point_value: PointValue {
115 points: 0,
116 rewards: 0,
117 },
118 }
119 }
120}
121
122pub(super) struct EpochRewardCalculateParamInfo<'a> {
124 pub(super) stake_history: StakeHistory,
125 pub(super) stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)>,
126 pub(super) cached_vote_accounts: &'a VoteAccounts,
127}
128
129#[derive(Debug)]
133pub(super) struct PartitionedRewardsCalculation {
134 pub(super) vote_account_rewards: VoteRewardsAccounts,
135 pub(super) stake_rewards: StakeRewardCalculation,
136 pub(super) validator_rate: f64,
137 pub(super) foundation_rate: f64,
138 pub(super) prev_epoch_duration_in_years: f64,
139 pub(super) capitalization: u64,
140 point_value: PointValue,
141}
142
143pub(super) struct CalculateRewardsAndDistributeVoteRewardsResult {
144 pub(super) distributed_rewards: u64,
146 pub(super) point_value: PointValue,
151 pub(super) stake_rewards: Arc<Vec<PartitionedStakeReward>>,
153}
154
155pub(crate) type StakeRewards = Vec<StakeReward>;
156
157#[derive(Debug, PartialEq)]
158pub struct KeyedRewardsAndNumPartitions {
159 pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
160 pub num_partitions: Option<u64>,
161}
162
163impl KeyedRewardsAndNumPartitions {
164 pub fn should_record(&self) -> bool {
165 !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
166 }
167}
168
169impl Bank {
170 pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
171 let keyed_rewards = self.rewards.read().unwrap().clone();
172 let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
173 let epoch_schedule = self.epoch_schedule();
176 let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
177 let is_first_block_in_epoch = self.epoch() > parent_epoch;
178
179 let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
180 .then_some(epoch_rewards_sysvar.num_partitions);
181 KeyedRewardsAndNumPartitions {
182 keyed_rewards,
183 num_partitions,
184 }
185 }
186
187 pub(crate) fn set_epoch_reward_status_calculation(
188 &mut self,
189 distribution_starting_block_height: u64,
190 stake_rewards: Arc<Vec<PartitionedStakeReward>>,
191 ) {
192 self.epoch_reward_status =
193 EpochRewardStatus::Active(EpochRewardPhase::Calculation(StartBlockHeightAndRewards {
194 distribution_starting_block_height,
195 all_stake_rewards: stake_rewards,
196 }));
197 }
198
199 pub(crate) fn set_epoch_reward_status_distribution(
200 &mut self,
201 distribution_starting_block_height: u64,
202 all_stake_rewards: Arc<Vec<PartitionedStakeReward>>,
203 partition_indices: Vec<Vec<usize>>,
204 ) {
205 self.epoch_reward_status = EpochRewardStatus::Active(EpochRewardPhase::Distribution(
206 StartBlockHeightAndPartitionedRewards {
207 distribution_starting_block_height,
208 all_stake_rewards,
209 partition_indices,
210 },
211 ));
212 }
213
214 pub(super) fn partitioned_epoch_rewards_config(&self) -> &PartitionedEpochRewardsConfig {
215 &self
216 .rc
217 .accounts
218 .accounts_db
219 .partitioned_epoch_rewards_config
220 }
221
222 pub(super) fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 {
224 self.partitioned_epoch_rewards_config()
225 .stake_account_stores_per_block
226 }
227
228 pub(super) fn get_reward_distribution_num_blocks(
230 &self,
231 rewards: &PartitionedStakeRewards,
232 ) -> u64 {
233 let total_stake_accounts = rewards.len();
234 if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() {
235 1
236 } else {
237 const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10;
238 let num_chunks = solana_accounts_db::accounts_hash::AccountsHasher::div_ceil(
239 total_stake_accounts,
240 self.partitioned_rewards_stake_account_stores_per_block() as usize,
241 ) as u64;
242
243 num_chunks.clamp(
245 1,
246 (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1),
247 )
248 }
249 }
250
251 pub fn force_reward_interval_end_for_tests(&mut self) {
253 self.epoch_reward_status = EpochRewardStatus::Inactive;
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use {
260 super::*,
261 crate::{
262 bank::tests::{create_genesis_config, new_bank_from_parent_with_bank_forks},
263 bank_forks::BankForks,
264 genesis_utils::{
265 create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs,
266 },
267 runtime_config::RuntimeConfig,
268 },
269 assert_matches::assert_matches,
270 solana_account::{state_traits::StateMut, Account},
271 solana_accounts_db::accounts_db::{AccountsDbConfig, ACCOUNTS_DB_CONFIG_FOR_TESTING},
272 solana_epoch_schedule::EpochSchedule,
273 solana_hash::Hash,
274 solana_keypair::Keypair,
275 solana_native_token::LAMPORTS_PER_SOL,
276 solana_reward_info::RewardType,
277 solana_signer::Signer,
278 solana_stake_interface::{error::StakeError, state::StakeStateV2},
279 solana_system_transaction as system_transaction,
280 solana_transaction::Transaction,
281 solana_vote::vote_transaction,
282 solana_vote_interface::state::{VoteStateVersions, MAX_LOCKOUT_HISTORY},
283 solana_vote_program::vote_state::{self, TowerSync},
284 std::sync::{Arc, RwLock},
285 };
286
287 impl PartitionedStakeReward {
288 fn maybe_from(stake_reward: &StakeReward) -> Option<Self> {
289 if let Ok(StakeStateV2::Stake(_meta, stake, _flags)) =
290 stake_reward.stake_account.state()
291 {
292 Some(Self {
293 stake_pubkey: stake_reward.stake_pubkey,
294 stake,
295 stake_reward: stake_reward.stake_reward_info.lamports as u64,
296 commission: stake_reward.stake_reward_info.commission.unwrap(),
297 })
298 } else {
299 None
300 }
301 }
302
303 pub fn new_random() -> Self {
304 Self::maybe_from(&StakeReward::new_random()).unwrap()
305 }
306 }
307
308 pub fn build_partitioned_stake_rewards(
309 stake_rewards: &[PartitionedStakeReward],
310 partition_indices: &[Vec<usize>],
311 ) -> Vec<Vec<PartitionedStakeReward>> {
312 partition_indices
313 .iter()
314 .map(|partition_index| {
315 partition_index
318 .iter()
319 .map(|&index| stake_rewards[index].clone())
320 .collect::<Vec<_>>()
321 })
322 .collect::<Vec<_>>()
323 }
324
325 pub fn convert_rewards(
326 stake_rewards: impl IntoIterator<Item = StakeReward>,
327 ) -> PartitionedStakeRewards {
328 stake_rewards
329 .into_iter()
330 .map(|stake_reward| PartitionedStakeReward::maybe_from(&stake_reward).unwrap())
331 .collect()
332 }
333
334 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
335 enum RewardInterval {
336 InsideInterval,
338 OutsideInterval,
340 }
341
342 impl Bank {
343 fn get_reward_interval(&self) -> RewardInterval {
345 if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) {
346 RewardInterval::InsideInterval
347 } else {
348 RewardInterval::OutsideInterval
349 }
350 }
351
352 fn is_calculated(&self) -> bool {
353 matches!(
354 self.epoch_reward_status,
355 EpochRewardStatus::Active(EpochRewardPhase::Calculation(_))
356 )
357 }
358
359 fn is_partitioned(&self) -> bool {
360 matches!(
361 self.epoch_reward_status,
362 EpochRewardStatus::Active(EpochRewardPhase::Distribution(_))
363 )
364 }
365
366 fn get_epoch_rewards_from_cache(
367 &self,
368 parent_hash: &Hash,
369 ) -> Option<Arc<PartitionedRewardsCalculation>> {
370 self.epoch_rewards_calculation_cache
371 .lock()
372 .unwrap()
373 .get(parent_hash)
374 .cloned()
375 }
376
377 fn get_epoch_rewards_cache_len(&self) -> usize {
378 self.epoch_rewards_calculation_cache.lock().unwrap().len()
379 }
380 }
381
382 pub(super) const SLOTS_PER_EPOCH: u64 = 32;
383
384 pub(super) struct RewardBank {
385 pub(super) bank: Arc<Bank>,
386 pub(super) voters: Vec<Pubkey>,
387 pub(super) stakers: Vec<Pubkey>,
388 }
389
390 pub(super) fn create_default_reward_bank(
392 expected_num_delegations: usize,
393 advance_num_slots: u64,
394 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
395 create_reward_bank(
396 expected_num_delegations,
397 PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
398 advance_num_slots,
399 )
400 }
401
402 pub(super) fn create_reward_bank(
403 expected_num_delegations: usize,
404 stake_account_stores_per_block: u64,
405 advance_num_slots: u64,
406 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
407 create_reward_bank_with_specific_stakes(
408 vec![2_000_000_000; expected_num_delegations],
409 stake_account_stores_per_block,
410 advance_num_slots,
411 )
412 }
413
414 pub(super) fn create_reward_bank_with_specific_stakes(
415 stakes: Vec<u64>,
416 stake_account_stores_per_block: u64,
417 advance_num_slots: u64,
418 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
419 let validator_keypairs = (0..stakes.len())
420 .map(|_| ValidatorVoteKeypairs::new_rand())
421 .collect::<Vec<_>>();
422
423 let GenesisConfigInfo {
424 mut genesis_config, ..
425 } = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stakes);
426 genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
427
428 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
429 accounts_db_config.partitioned_epoch_rewards_config =
430 PartitionedEpochRewardsConfig::new_for_test(stake_account_stores_per_block);
431
432 let bank = Bank::new_with_paths(
433 &genesis_config,
434 Arc::new(RuntimeConfig::default()),
435 Vec::new(),
436 None,
437 None,
438 false,
439 Some(accounts_db_config),
440 None,
441 Some(Pubkey::new_unique()),
442 Arc::default(),
443 None,
444 None,
445 );
446
447 for validator_vote_keypairs in &validator_keypairs {
450 let vote_id = validator_vote_keypairs.vote_keypair.pubkey();
451 let mut vote_account = bank.get_account(&vote_id).unwrap();
452 let mut vote_state = Some(vote_state::from(&vote_account).unwrap());
454 for i in 0..MAX_LOCKOUT_HISTORY + 42 {
455 if let Some(v) = vote_state.as_mut() {
456 vote_state::process_slot_vote_unchecked(v, i as u64)
457 }
458 let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap()));
459 vote_state::to(&versioned, &mut vote_account).unwrap();
460 match versioned {
461 VoteStateVersions::Current(v) => {
462 vote_state = Some(*v);
463 }
464 _ => panic!("Has to be of type Current"),
465 };
466 }
467 bank.store_account_and_update_capitalization(&vote_id, &vote_account);
468 }
469
470 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
473 let bank = new_bank_from_parent_with_bank_forks(
474 &bank_forks,
475 bank,
476 &Pubkey::default(),
477 advance_num_slots,
478 );
479
480 (
481 RewardBank {
482 bank,
483 voters: validator_keypairs
484 .iter()
485 .map(|k| k.vote_keypair.pubkey())
486 .collect(),
487 stakers: validator_keypairs
488 .iter()
489 .map(|k| k.stake_keypair.pubkey())
490 .collect(),
491 },
492 bank_forks,
493 )
494 }
495
496 #[test]
497 fn test_force_reward_interval_end() {
498 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
499 let mut bank = Bank::new_for_tests(&genesis_config);
500
501 let expected_num = 100;
502
503 let stake_rewards = (0..expected_num)
504 .map(|_| PartitionedStakeReward::new_random())
505 .collect::<Vec<_>>();
506
507 let partition_indices = vec![(0..expected_num).collect()];
508
509 bank.set_epoch_reward_status_distribution(
510 bank.block_height() + REWARD_CALCULATION_NUM_BLOCKS,
511 Arc::new(stake_rewards),
512 partition_indices,
513 );
514 assert!(bank.get_reward_interval() == RewardInterval::InsideInterval);
515
516 bank.force_reward_interval_end_for_tests();
517 assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval);
518 }
519
520 #[test]
523 fn test_get_reward_distribution_num_blocks_cap() {
524 let (mut genesis_config, _mint_keypair) =
525 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
526 genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false);
527
528 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
530 accounts_db_config.partitioned_epoch_rewards_config =
531 PartitionedEpochRewardsConfig::new_for_test(10);
532
533 let bank = Bank::new_with_paths(
534 &genesis_config,
535 Arc::new(RuntimeConfig::default()),
536 Vec::new(),
537 None,
538 None,
539 false,
540 Some(accounts_db_config),
541 None,
542 Some(Pubkey::new_unique()),
543 Arc::default(),
544 None,
545 None,
546 );
547
548 let stake_account_stores_per_block =
549 bank.partitioned_rewards_stake_account_stores_per_block();
550 assert_eq!(stake_account_stores_per_block, 10);
551
552 let check_num_reward_distribution_blocks =
553 |num_stakes: u64, expected_num_reward_distribution_blocks: u64| {
554 let stake_rewards = (0..num_stakes)
556 .map(|_| PartitionedStakeReward::new_random())
557 .collect::<Vec<_>>();
558
559 assert_eq!(
560 bank.get_reward_distribution_num_blocks(&stake_rewards),
561 expected_num_reward_distribution_blocks
562 );
563 };
564
565 for test_record in [
566 (0, 1),
568 (1, 1),
569 (stake_account_stores_per_block, 1),
570 (2 * stake_account_stores_per_block - 1, 2),
571 (2 * stake_account_stores_per_block, 2),
572 (3 * stake_account_stores_per_block - 1, 3),
573 (3 * stake_account_stores_per_block, 3),
574 (4 * stake_account_stores_per_block, 3), (5 * stake_account_stores_per_block, 3), ] {
577 check_num_reward_distribution_blocks(test_record.0, test_record.1);
578 }
579 }
580
581 #[test]
583 fn test_get_reward_distribution_num_blocks_normal() {
584 solana_logger::setup();
585 let (mut genesis_config, _mint_keypair) =
586 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
587 genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
588
589 let bank = Bank::new_for_tests(&genesis_config);
590
591 let expected_num = 8192;
593 let stake_rewards = (0..expected_num)
594 .map(|_| PartitionedStakeReward::new_random())
595 .collect::<Vec<_>>();
596
597 assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2);
598 }
599
600 #[test]
603 fn test_get_reward_distribution_num_blocks_warmup() {
604 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
605
606 let bank = Bank::new_for_tests(&genesis_config);
607 let rewards = vec![];
608 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
609 }
610
611 #[test]
612 fn test_rewards_computation_and_partitioned_distribution_one_block() {
613 solana_logger::setup();
614
615 let starting_slot = SLOTS_PER_EPOCH - 1;
616 let (
617 RewardBank {
618 bank: mut previous_bank,
619 ..
620 },
621 bank_forks,
622 ) = create_default_reward_bank(100, starting_slot - 1);
623
624 for slot in starting_slot..=(2 * SLOTS_PER_EPOCH) + 2 {
626 let pre_cap = previous_bank.capitalization();
627 let curr_bank = new_bank_from_parent_with_bank_forks(
628 bank_forks.as_ref(),
629 previous_bank.clone(),
630 &Pubkey::default(),
631 slot,
632 );
633 let post_cap = curr_bank.capitalization();
634
635 if slot % SLOTS_PER_EPOCH == 0 {
636 assert_matches!(
639 curr_bank.get_reward_interval(),
640 RewardInterval::InsideInterval
641 );
642
643 assert!(curr_bank.is_calculated());
644
645 assert!(curr_bank
647 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
648 .is_some());
649 assert_eq!(post_cap, pre_cap);
650
651 let _ = bank_forks.write().unwrap().set_root(slot, None, None);
654 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
655 } else if slot == SLOTS_PER_EPOCH + 1 {
656 assert_matches!(
662 curr_bank.get_reward_interval(),
663 RewardInterval::OutsideInterval
664 );
665 let account = curr_bank
666 .get_account(&solana_sysvar::epoch_rewards::id())
667 .unwrap();
668 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
669 solana_account::from_account(&account).unwrap();
670 assert_eq!(post_cap, pre_cap + epoch_rewards.distributed_rewards);
671 } else {
672 assert_matches!(
677 curr_bank.get_reward_interval(),
678 RewardInterval::OutsideInterval
679 );
680
681 assert_eq!(post_cap, pre_cap);
683 }
684 if slot >= SLOTS_PER_EPOCH {
687 let epoch_rewards_lamports =
688 curr_bank.get_balance(&solana_sysvar::epoch_rewards::id());
689 assert!(epoch_rewards_lamports > 0);
690 }
691 previous_bank = curr_bank;
692 }
693 }
694
695 #[test]
697 fn test_rewards_computation_and_partitioned_distribution_two_blocks() {
698 solana_logger::setup();
699
700 let starting_slot = SLOTS_PER_EPOCH - 1;
701 let (
702 RewardBank {
703 bank: mut previous_bank,
704 ..
705 },
706 bank_forks,
707 ) = create_reward_bank(100, 50, starting_slot - 1);
708 let mut starting_hash = None;
709
710 for slot in starting_slot..=SLOTS_PER_EPOCH + 3 {
712 let pre_cap = previous_bank.capitalization();
713
714 let pre_sysvar_account = previous_bank
715 .get_account(&solana_sysvar::epoch_rewards::id())
716 .unwrap_or_default();
717 let pre_epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
718 solana_account::from_account(&pre_sysvar_account).unwrap_or_default();
719 let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
720 let curr_bank = new_bank_from_parent_with_bank_forks(
721 bank_forks.as_ref(),
722 previous_bank.clone(),
723 &Pubkey::default(),
724 slot,
725 );
726 let post_cap = curr_bank.capitalization();
727
728 if slot == SLOTS_PER_EPOCH {
729 assert_matches!(
732 curr_bank.get_reward_interval(),
733 RewardInterval::InsideInterval
734 );
735
736 assert!(curr_bank.is_calculated());
738
739 assert!(curr_bank
741 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
742 .is_some());
743 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
744 starting_hash = Some(curr_bank.parent_hash);
745 } else if slot == SLOTS_PER_EPOCH + 1 {
746 assert_matches!(
750 curr_bank.get_reward_interval(),
751 RewardInterval::InsideInterval
752 );
753
754 assert!(curr_bank
757 .get_epoch_rewards_from_cache(&starting_hash.unwrap())
758 .is_some());
759 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
760
761 assert!(curr_bank.is_partitioned());
763
764 let account = curr_bank
765 .get_account(&solana_sysvar::epoch_rewards::id())
766 .unwrap();
767 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
768 solana_account::from_account(&account).unwrap();
769 assert_eq!(
770 post_cap,
771 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
772 );
773
774 let _ = bank_forks.write().unwrap().set_root(slot - 1, None, None);
777 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
778 } else if slot == SLOTS_PER_EPOCH + 2 {
779 assert_matches!(
785 curr_bank.get_reward_interval(),
786 RewardInterval::OutsideInterval
787 );
788
789 let account = curr_bank
790 .get_account(&solana_sysvar::epoch_rewards::id())
791 .unwrap();
792 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
793 solana_account::from_account(&account).unwrap();
794 assert_eq!(
795 post_cap,
796 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
797 );
798 } else {
799 assert_matches!(
804 curr_bank.get_reward_interval(),
805 RewardInterval::OutsideInterval
806 );
807
808 assert_eq!(post_cap, pre_cap);
810 }
811 previous_bank = curr_bank;
812 }
813 }
814
815 #[test]
819 fn test_program_execution_restricted_for_stake_account_in_reward_period() {
820 use solana_transaction_error::TransactionError::InstructionError;
821
822 let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
823 let validator_keypairs = vec![&validator_vote_keypairs];
824 let GenesisConfigInfo {
825 mut genesis_config,
826 mint_keypair,
827 ..
828 } = create_genesis_config_with_vote_accounts(
829 1_000_000_000,
830 &validator_keypairs,
831 vec![1_000_000_000; 1],
832 );
833
834 let vote_key = validator_keypairs[0].vote_keypair.pubkey();
836 let vote_account = genesis_config
837 .accounts
838 .iter()
839 .find(|(&address, _)| address == vote_key)
840 .map(|(_, account)| account)
841 .unwrap()
842 .clone();
843
844 let new_stake_signer = Keypair::new();
845 let new_stake_address = new_stake_signer.pubkey();
846 let new_stake_account = Account::from(solana_stake_program::stake_state::create_account(
847 &new_stake_address,
848 &vote_key,
849 &vote_account.into(),
850 &genesis_config.rent,
851 2_000_000_000,
852 ));
853 genesis_config
854 .accounts
855 .extend(vec![(new_stake_address, new_stake_account)]);
856
857 let (mut previous_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
858 let num_slots_in_epoch = previous_bank.get_slots_in_epoch(previous_bank.epoch());
859 assert_eq!(num_slots_in_epoch, 32);
860
861 let transfer_amount = 5_000;
862
863 for slot in 1..=num_slots_in_epoch + 2 {
864 let bank = new_bank_from_parent_with_bank_forks(
865 bank_forks.as_ref(),
866 previous_bank.clone(),
867 &Pubkey::default(),
868 slot,
869 );
870
871 let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash());
874 let vote = vote_transaction::new_tower_sync_transaction(
875 tower_sync,
876 previous_bank.last_blockhash(),
877 &validator_vote_keypairs.node_keypair,
878 &validator_vote_keypairs.vote_keypair,
879 &validator_vote_keypairs.vote_keypair,
880 None,
881 );
882 bank.process_transaction(&vote).unwrap();
883
884 let system_tx = system_transaction::transfer(
886 &mint_keypair,
887 &new_stake_address,
888 transfer_amount,
889 bank.last_blockhash(),
890 );
891 let system_result = bank.process_transaction(&system_tx);
892
893 assert!(system_result.is_ok());
895
896 let stake_ix = solana_stake_interface::instruction::withdraw(
898 &new_stake_address,
899 &new_stake_address,
900 &mint_keypair.pubkey(),
901 transfer_amount,
902 None,
903 );
904 let stake_tx = Transaction::new_signed_with_payer(
905 &[stake_ix],
906 Some(&mint_keypair.pubkey()),
907 &[&mint_keypair, &new_stake_signer],
908 bank.last_blockhash(),
909 );
910 let stake_result = bank.process_transaction(&stake_tx);
911
912 if slot == num_slots_in_epoch {
913 assert_eq!(
917 stake_result,
918 Err(InstructionError(0, StakeError::EpochRewardsActive.into()))
919 );
920 } else {
921 assert!(stake_result.is_ok());
924 }
925
926 bank.register_unique_recent_blockhash_for_test();
930 previous_bank = bank;
931 }
932 }
933
934 #[test]
935 fn test_get_rewards_and_partitions() {
936 let starting_slot = SLOTS_PER_EPOCH - 1;
937 let num_rewards = 100;
938 let stake_account_stores_per_block = 50;
939 let (RewardBank { bank, .. }, _) =
940 create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
941
942 assert_eq!(
945 bank.get_rewards_and_num_partitions(),
946 KeyedRewardsAndNumPartitions {
947 keyed_rewards: vec![],
948 num_partitions: None,
949 }
950 );
951
952 let epoch_boundary_bank = Arc::new(Bank::new_from_parent(
953 bank,
954 &Pubkey::default(),
955 SLOTS_PER_EPOCH,
956 ));
957 let KeyedRewardsAndNumPartitions {
959 keyed_rewards,
960 num_partitions,
961 } = epoch_boundary_bank.get_rewards_and_num_partitions();
962 for (_pubkey, reward) in keyed_rewards.iter() {
963 assert_eq!(reward.reward_type, RewardType::Voting);
964 }
965 assert_eq!(keyed_rewards.len(), num_rewards);
966 assert_eq!(
967 num_partitions,
968 Some(num_rewards as u64 / stake_account_stores_per_block)
969 );
970
971 let mut total_staking_rewards = 0;
972
973 let partition0_bank = Arc::new(Bank::new_from_parent(
974 epoch_boundary_bank,
975 &Pubkey::default(),
976 SLOTS_PER_EPOCH + 1,
977 ));
978 let KeyedRewardsAndNumPartitions {
981 keyed_rewards,
982 num_partitions,
983 } = partition0_bank.get_rewards_and_num_partitions();
984 for (_pubkey, reward) in keyed_rewards.iter() {
985 assert_eq!(reward.reward_type, RewardType::Staking);
986 }
987 total_staking_rewards += keyed_rewards.len();
988 assert_eq!(num_partitions, None);
989
990 let partition1_bank = Arc::new(Bank::new_from_parent(
991 partition0_bank,
992 &Pubkey::default(),
993 SLOTS_PER_EPOCH + 2,
994 ));
995 let KeyedRewardsAndNumPartitions {
998 keyed_rewards,
999 num_partitions,
1000 } = partition1_bank.get_rewards_and_num_partitions();
1001 for (_pubkey, reward) in keyed_rewards.iter() {
1002 assert_eq!(reward.reward_type, RewardType::Staking);
1003 }
1004 total_staking_rewards += keyed_rewards.len();
1005 assert_eq!(num_partitions, None);
1006
1007 assert_eq!(total_staking_rewards, num_rewards);
1009
1010 let bank = Bank::new_from_parent(partition1_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 3);
1011 assert_eq!(
1014 bank.get_rewards_and_num_partitions(),
1015 KeyedRewardsAndNumPartitions {
1016 keyed_rewards: vec![],
1017 num_partitions: None,
1018 }
1019 );
1020 }
1021
1022 #[test]
1023 fn test_rewards_and_partitions_should_record() {
1024 let reward = RewardInfo {
1025 reward_type: RewardType::Voting,
1026 lamports: 55,
1027 post_balance: 5555,
1028 commission: Some(5),
1029 };
1030
1031 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1032 keyed_rewards: vec![],
1033 num_partitions: None,
1034 };
1035 assert!(!rewards_and_partitions.should_record());
1036
1037 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1038 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1039 num_partitions: None,
1040 };
1041 assert!(rewards_and_partitions.should_record());
1042
1043 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1044 keyed_rewards: vec![],
1045 num_partitions: Some(42),
1046 };
1047 assert!(rewards_and_partitions.should_record());
1048
1049 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1050 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1051 num_partitions: Some(42),
1052 };
1053 assert!(rewards_and_partitions.should_record());
1054 }
1055}