1mod calculation;
2mod distribution;
3mod epoch_rewards_hasher;
4mod sysvar;
5
6use {
7 super::Bank,
8 crate::{stake_account::StakeAccount, stake_history::StakeHistory},
9 solana_accounts_db::{
10 partitioned_rewards::PartitionedEpochRewardsConfig, stake_rewards::StakeReward,
11 },
12 solana_sdk::{
13 account::AccountSharedData,
14 pubkey::Pubkey,
15 reward_info::RewardInfo,
16 stake::state::{Delegation, Stake},
17 },
18 solana_stake_program::points::PointValue,
19 solana_vote::vote_account::VoteAccounts,
20 std::sync::Arc,
21};
22
23const REWARD_CALCULATION_NUM_BLOCKS: u64 = 1;
26
27#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
28pub(crate) struct PartitionedStakeReward {
29 pub stake_pubkey: Pubkey,
31 pub stake: Stake,
33 pub stake_reward_info: RewardInfo,
37}
38
39type PartitionedStakeRewards = Vec<PartitionedStakeReward>;
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub(crate) struct StartBlockHeightAndRewards {
43 pub(crate) distribution_starting_block_height: u64,
45 pub(crate) stake_rewards_by_partition: Arc<Vec<PartitionedStakeRewards>>,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
51pub(crate) enum EpochRewardStatus {
52 Active(StartBlockHeightAndRewards),
57 #[default]
59 Inactive,
60}
61
62#[derive(Debug, Default)]
63pub(super) struct VoteRewardsAccounts {
64 pub(super) rewards: Vec<(Pubkey, RewardInfo)>,
67 pub(super) accounts_to_store: Vec<Option<AccountSharedData>>,
71}
72
73#[derive(Debug, Default)]
74struct StakeRewardCalculation {
76 stake_rewards: PartitionedStakeRewards,
78 total_stake_rewards_lamports: u64,
80}
81
82#[derive(Debug)]
83struct CalculateValidatorRewardsResult {
84 vote_rewards_accounts: VoteRewardsAccounts,
85 stake_reward_calculation: StakeRewardCalculation,
86 point_value: PointValue,
87}
88
89impl Default for CalculateValidatorRewardsResult {
90 fn default() -> Self {
91 Self {
92 vote_rewards_accounts: VoteRewardsAccounts::default(),
93 stake_reward_calculation: StakeRewardCalculation::default(),
94 point_value: PointValue {
95 points: 0,
96 rewards: 0,
97 },
98 }
99 }
100}
101
102pub(super) struct EpochRewardCalculateParamInfo<'a> {
104 pub(super) stake_history: StakeHistory,
105 pub(super) stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)>,
106 pub(super) cached_vote_accounts: &'a VoteAccounts,
107}
108
109pub(super) struct PartitionedRewardsCalculation {
113 pub(super) vote_account_rewards: VoteRewardsAccounts,
114 pub(super) stake_rewards_by_partition: StakeRewardCalculationPartitioned,
115 pub(super) old_vote_balance_and_staked: u64,
116 pub(super) validator_rate: f64,
117 pub(super) foundation_rate: f64,
118 pub(super) prev_epoch_duration_in_years: f64,
119 pub(super) capitalization: u64,
120 point_value: PointValue,
121}
122
123pub(super) struct StakeRewardCalculationPartitioned {
125 pub(super) stake_rewards_by_partition: Vec<PartitionedStakeRewards>,
127 pub(super) total_stake_rewards_lamports: u64,
129}
130
131pub(super) struct CalculateRewardsAndDistributeVoteRewardsResult {
132 pub(super) distributed_rewards: u64,
134 pub(super) point_value: PointValue,
139 pub(super) stake_rewards_by_partition: Vec<PartitionedStakeRewards>,
141}
142
143pub(crate) type StakeRewards = Vec<StakeReward>;
144
145#[derive(Debug, PartialEq)]
146pub struct KeyedRewardsAndNumPartitions {
147 pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
148 pub num_partitions: Option<u64>,
149}
150
151impl KeyedRewardsAndNumPartitions {
152 pub fn should_record(&self) -> bool {
153 !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
154 }
155}
156
157impl Bank {
158 pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
159 let keyed_rewards = self.rewards.read().unwrap().clone();
160 let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
161 let epoch_schedule = self.epoch_schedule();
164 let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
165 let is_first_block_in_epoch = self.epoch() > parent_epoch;
166
167 let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
168 .then_some(epoch_rewards_sysvar.num_partitions);
169 KeyedRewardsAndNumPartitions {
170 keyed_rewards,
171 num_partitions,
172 }
173 }
174
175 pub(crate) fn set_epoch_reward_status_active(
176 &mut self,
177 distribution_starting_block_height: u64,
178 stake_rewards_by_partition: Vec<PartitionedStakeRewards>,
179 ) {
180 self.epoch_reward_status = EpochRewardStatus::Active(StartBlockHeightAndRewards {
181 distribution_starting_block_height,
182 stake_rewards_by_partition: Arc::new(stake_rewards_by_partition),
183 });
184 }
185
186 pub(super) fn partitioned_epoch_rewards_config(&self) -> &PartitionedEpochRewardsConfig {
187 &self
188 .rc
189 .accounts
190 .accounts_db
191 .partitioned_epoch_rewards_config
192 }
193
194 pub(super) fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 {
196 self.partitioned_epoch_rewards_config()
197 .stake_account_stores_per_block
198 }
199
200 pub(super) fn get_reward_distribution_num_blocks(
202 &self,
203 rewards: &PartitionedStakeRewards,
204 ) -> u64 {
205 let total_stake_accounts = rewards.len();
206 if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() {
207 1
208 } else {
209 const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10;
210 let num_chunks = solana_accounts_db::accounts_hash::AccountsHasher::div_ceil(
211 total_stake_accounts,
212 self.partitioned_rewards_stake_account_stores_per_block() as usize,
213 ) as u64;
214
215 num_chunks.clamp(
217 1,
218 (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1),
219 )
220 }
221 }
222
223 pub fn force_reward_interval_end_for_tests(&mut self) {
225 self.epoch_reward_status = EpochRewardStatus::Inactive;
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use {
232 super::*,
233 crate::{
234 bank::tests::{create_genesis_config, new_bank_from_parent_with_bank_forks},
235 genesis_utils::{
236 create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs,
237 },
238 runtime_config::RuntimeConfig,
239 },
240 assert_matches::assert_matches,
241 solana_accounts_db::accounts_db::{AccountsDbConfig, ACCOUNTS_DB_CONFIG_FOR_TESTING},
242 solana_sdk::{
243 account::Account,
244 account_utils::StateMut,
245 epoch_schedule::EpochSchedule,
246 native_token::LAMPORTS_PER_SOL,
247 reward_type::RewardType,
248 signature::Signer,
249 signer::keypair::Keypair,
250 stake::{instruction::StakeError, state::StakeStateV2},
251 system_transaction,
252 transaction::Transaction,
253 vote::state::{VoteStateVersions, MAX_LOCKOUT_HISTORY},
254 },
255 solana_vote_program::{
256 vote_state::{self, TowerSync},
257 vote_transaction,
258 },
259 };
260
261 impl PartitionedStakeReward {
262 fn maybe_from(stake_reward: &StakeReward) -> Option<Self> {
263 if let Ok(StakeStateV2::Stake(_meta, stake, _flags)) =
264 stake_reward.stake_account.state()
265 {
266 Some(Self {
267 stake_pubkey: stake_reward.stake_pubkey,
268 stake,
269 stake_reward_info: stake_reward.stake_reward_info,
270 })
271 } else {
272 None
273 }
274 }
275
276 pub fn new_random() -> Self {
277 Self::maybe_from(&StakeReward::new_random()).unwrap()
278 }
279 }
280
281 pub fn convert_rewards(
282 stake_rewards: impl IntoIterator<Item = StakeReward>,
283 ) -> PartitionedStakeRewards {
284 stake_rewards
285 .into_iter()
286 .map(|stake_reward| PartitionedStakeReward::maybe_from(&stake_reward).unwrap())
287 .collect()
288 }
289
290 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
291 enum RewardInterval {
292 InsideInterval,
294 OutsideInterval,
296 }
297
298 impl Bank {
299 fn get_reward_interval(&self) -> RewardInterval {
301 if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) {
302 RewardInterval::InsideInterval
303 } else {
304 RewardInterval::OutsideInterval
305 }
306 }
307 }
308
309 pub(super) const SLOTS_PER_EPOCH: u64 = 32;
310
311 pub(super) struct RewardBank {
312 pub(super) bank: Arc<Bank>,
313 pub(super) voters: Vec<Pubkey>,
314 pub(super) stakers: Vec<Pubkey>,
315 }
316
317 pub(super) fn create_default_reward_bank(
319 expected_num_delegations: usize,
320 advance_num_slots: u64,
321 ) -> RewardBank {
322 create_reward_bank(
323 expected_num_delegations,
324 PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
325 advance_num_slots,
326 )
327 }
328
329 pub(super) fn create_reward_bank(
330 expected_num_delegations: usize,
331 stake_account_stores_per_block: u64,
332 advance_num_slots: u64,
333 ) -> RewardBank {
334 create_reward_bank_with_specific_stakes(
335 vec![2_000_000_000; expected_num_delegations],
336 stake_account_stores_per_block,
337 advance_num_slots,
338 )
339 }
340
341 pub(super) fn create_reward_bank_with_specific_stakes(
342 stakes: Vec<u64>,
343 stake_account_stores_per_block: u64,
344 advance_num_slots: u64,
345 ) -> RewardBank {
346 let validator_keypairs = (0..stakes.len())
347 .map(|_| ValidatorVoteKeypairs::new_rand())
348 .collect::<Vec<_>>();
349
350 let GenesisConfigInfo {
351 mut genesis_config, ..
352 } = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stakes);
353 genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
354
355 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
356 accounts_db_config.partitioned_epoch_rewards_config =
357 PartitionedEpochRewardsConfig::new_for_test(stake_account_stores_per_block);
358
359 let bank = Bank::new_with_paths(
360 &genesis_config,
361 Arc::new(RuntimeConfig::default()),
362 Vec::new(),
363 None,
364 None,
365 false,
366 Some(accounts_db_config),
367 None,
368 Some(Pubkey::new_unique()),
369 Arc::default(),
370 None,
371 None,
372 );
373
374 for validator_vote_keypairs in &validator_keypairs {
377 let vote_id = validator_vote_keypairs.vote_keypair.pubkey();
378 let mut vote_account = bank.get_account(&vote_id).unwrap();
379 let mut vote_state = Some(vote_state::from(&vote_account).unwrap());
381 for i in 0..MAX_LOCKOUT_HISTORY + 42 {
382 if let Some(v) = vote_state.as_mut() {
383 vote_state::process_slot_vote_unchecked(v, i as u64)
384 }
385 let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap()));
386 vote_state::to(&versioned, &mut vote_account).unwrap();
387 match versioned {
388 VoteStateVersions::Current(v) => {
389 vote_state = Some(*v);
390 }
391 _ => panic!("Has to be of type Current"),
392 };
393 }
394 bank.store_account_and_update_capitalization(&vote_id, &vote_account);
395 }
396
397 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
400 let bank = new_bank_from_parent_with_bank_forks(
401 &bank_forks,
402 bank,
403 &Pubkey::default(),
404 advance_num_slots,
405 );
406
407 RewardBank {
408 bank,
409 voters: validator_keypairs
410 .iter()
411 .map(|k| k.vote_keypair.pubkey())
412 .collect(),
413 stakers: validator_keypairs
414 .iter()
415 .map(|k| k.stake_keypair.pubkey())
416 .collect(),
417 }
418 }
419
420 #[test]
421 fn test_force_reward_interval_end() {
422 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
423 let mut bank = Bank::new_for_tests(&genesis_config);
424
425 let expected_num = 100;
426
427 let stake_rewards = (0..expected_num)
428 .map(|_| PartitionedStakeReward::new_random())
429 .collect::<Vec<_>>();
430
431 bank.set_epoch_reward_status_active(
432 bank.block_height() + REWARD_CALCULATION_NUM_BLOCKS,
433 vec![stake_rewards],
434 );
435 assert!(bank.get_reward_interval() == RewardInterval::InsideInterval);
436
437 bank.force_reward_interval_end_for_tests();
438 assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval);
439 }
440
441 #[test]
444 fn test_get_reward_distribution_num_blocks_cap() {
445 let (mut genesis_config, _mint_keypair) =
446 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
447 genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false);
448
449 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
451 accounts_db_config.partitioned_epoch_rewards_config =
452 PartitionedEpochRewardsConfig::new_for_test(10);
453
454 let bank = Bank::new_with_paths(
455 &genesis_config,
456 Arc::new(RuntimeConfig::default()),
457 Vec::new(),
458 None,
459 None,
460 false,
461 Some(accounts_db_config),
462 None,
463 Some(Pubkey::new_unique()),
464 Arc::default(),
465 None,
466 None,
467 );
468
469 let stake_account_stores_per_block =
470 bank.partitioned_rewards_stake_account_stores_per_block();
471 assert_eq!(stake_account_stores_per_block, 10);
472
473 let check_num_reward_distribution_blocks =
474 |num_stakes: u64, expected_num_reward_distribution_blocks: u64| {
475 let stake_rewards = (0..num_stakes)
477 .map(|_| PartitionedStakeReward::new_random())
478 .collect::<Vec<_>>();
479
480 assert_eq!(
481 bank.get_reward_distribution_num_blocks(&stake_rewards),
482 expected_num_reward_distribution_blocks
483 );
484 };
485
486 for test_record in [
487 (0, 1),
489 (1, 1),
490 (stake_account_stores_per_block, 1),
491 (2 * stake_account_stores_per_block - 1, 2),
492 (2 * stake_account_stores_per_block, 2),
493 (3 * stake_account_stores_per_block - 1, 3),
494 (3 * stake_account_stores_per_block, 3),
495 (4 * stake_account_stores_per_block, 3), (5 * stake_account_stores_per_block, 3), ] {
498 check_num_reward_distribution_blocks(test_record.0, test_record.1);
499 }
500 }
501
502 #[test]
504 fn test_get_reward_distribution_num_blocks_normal() {
505 solana_logger::setup();
506 let (mut genesis_config, _mint_keypair) =
507 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
508 genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
509
510 let bank = Bank::new_for_tests(&genesis_config);
511
512 let expected_num = 8192;
514 let stake_rewards = (0..expected_num)
515 .map(|_| PartitionedStakeReward::new_random())
516 .collect::<Vec<_>>();
517
518 assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2);
519 }
520
521 #[test]
524 fn test_get_reward_distribution_num_blocks_warmup() {
525 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
526
527 let bank = Bank::new_for_tests(&genesis_config);
528 let rewards = vec![];
529 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
530 }
531
532 #[test]
533 fn test_rewards_computation_and_partitioned_distribution_one_block() {
534 solana_logger::setup();
535
536 let starting_slot = SLOTS_PER_EPOCH - 1;
537 let RewardBank {
538 bank: mut previous_bank,
539 ..
540 } = create_default_reward_bank(100, starting_slot - 1);
541
542 for slot in starting_slot..=(2 * SLOTS_PER_EPOCH) + 2 {
544 let pre_cap = previous_bank.capitalization();
545 let curr_bank = Bank::new_from_parent(previous_bank, &Pubkey::default(), slot);
546 let post_cap = curr_bank.capitalization();
547
548 if slot % SLOTS_PER_EPOCH == 0 {
549 assert_matches!(
552 curr_bank.get_reward_interval(),
553 RewardInterval::InsideInterval
554 );
555
556 if slot == SLOTS_PER_EPOCH {
557 assert!(post_cap > pre_cap);
559 } else {
560 assert_eq!(post_cap, pre_cap);
561 }
562 } else if slot == SLOTS_PER_EPOCH + 1 {
563 assert_matches!(
569 curr_bank.get_reward_interval(),
570 RewardInterval::OutsideInterval
571 );
572
573 let account = curr_bank
574 .get_account(&solana_sdk::sysvar::epoch_rewards::id())
575 .unwrap();
576 let epoch_rewards: solana_sdk::sysvar::epoch_rewards::EpochRewards =
577 solana_sdk::account::from_account(&account).unwrap();
578 assert_eq!(post_cap, pre_cap + epoch_rewards.distributed_rewards);
579 } else {
580 assert_matches!(
585 curr_bank.get_reward_interval(),
586 RewardInterval::OutsideInterval
587 );
588
589 assert_eq!(post_cap, pre_cap);
591 }
592 if slot >= SLOTS_PER_EPOCH {
595 let epoch_rewards_lamports =
596 curr_bank.get_balance(&solana_sdk::sysvar::epoch_rewards::id());
597 assert!(epoch_rewards_lamports > 0);
598 }
599 previous_bank = Arc::new(curr_bank);
600 }
601 }
602
603 #[test]
605 fn test_rewards_computation_and_partitioned_distribution_two_blocks() {
606 solana_logger::setup();
607
608 let starting_slot = SLOTS_PER_EPOCH - 1;
609 let RewardBank {
610 bank: mut previous_bank,
611 ..
612 } = create_reward_bank(100, 50, starting_slot - 1);
613
614 for slot in starting_slot..=SLOTS_PER_EPOCH + 3 {
616 let pre_cap = previous_bank.capitalization();
617
618 let pre_sysvar_account = previous_bank
619 .get_account(&solana_sdk::sysvar::epoch_rewards::id())
620 .unwrap_or_default();
621 let pre_epoch_rewards: solana_sdk::sysvar::epoch_rewards::EpochRewards =
622 solana_sdk::account::from_account(&pre_sysvar_account).unwrap_or_default();
623 let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
624
625 let curr_bank = Bank::new_from_parent(previous_bank, &Pubkey::default(), slot);
626 let post_cap = curr_bank.capitalization();
627
628 if slot == SLOTS_PER_EPOCH {
629 assert_matches!(
632 curr_bank.get_reward_interval(),
633 RewardInterval::InsideInterval
634 );
635
636 assert!(post_cap > pre_cap);
638 } else if slot == SLOTS_PER_EPOCH + 1 {
639 assert_matches!(
643 curr_bank.get_reward_interval(),
644 RewardInterval::InsideInterval
645 );
646
647 let account = curr_bank
648 .get_account(&solana_sdk::sysvar::epoch_rewards::id())
649 .unwrap();
650 let epoch_rewards: solana_sdk::sysvar::epoch_rewards::EpochRewards =
651 solana_sdk::account::from_account(&account).unwrap();
652 assert_eq!(
653 post_cap,
654 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
655 );
656 } else if slot == SLOTS_PER_EPOCH + 2 {
657 assert_matches!(
663 curr_bank.get_reward_interval(),
664 RewardInterval::OutsideInterval
665 );
666
667 let account = curr_bank
668 .get_account(&solana_sdk::sysvar::epoch_rewards::id())
669 .unwrap();
670 let epoch_rewards: solana_sdk::sysvar::epoch_rewards::EpochRewards =
671 solana_sdk::account::from_account(&account).unwrap();
672 assert_eq!(
673 post_cap,
674 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
675 );
676 } else {
677 assert_matches!(
682 curr_bank.get_reward_interval(),
683 RewardInterval::OutsideInterval
684 );
685
686 assert_eq!(post_cap, pre_cap);
688 }
689 previous_bank = Arc::new(curr_bank);
690 }
691 }
692
693 #[test]
697 fn test_program_execution_restricted_for_stake_account_in_reward_period() {
698 use solana_sdk::transaction::TransactionError::InstructionError;
699
700 let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
701 let validator_keypairs = vec![&validator_vote_keypairs];
702 let GenesisConfigInfo {
703 mut genesis_config,
704 mint_keypair,
705 ..
706 } = create_genesis_config_with_vote_accounts(
707 1_000_000_000,
708 &validator_keypairs,
709 vec![1_000_000_000; 1],
710 );
711
712 let vote_key = validator_keypairs[0].vote_keypair.pubkey();
714 let vote_account = genesis_config
715 .accounts
716 .iter()
717 .find(|(&address, _)| address == vote_key)
718 .map(|(_, account)| account)
719 .unwrap()
720 .clone();
721
722 let new_stake_signer = Keypair::new();
723 let new_stake_address = new_stake_signer.pubkey();
724 let new_stake_account = Account::from(solana_stake_program::stake_state::create_account(
725 &new_stake_address,
726 &vote_key,
727 &vote_account.into(),
728 &genesis_config.rent,
729 2_000_000_000,
730 ));
731 genesis_config
732 .accounts
733 .extend(vec![(new_stake_address, new_stake_account)]);
734
735 let (mut previous_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
736 let num_slots_in_epoch = previous_bank.get_slots_in_epoch(previous_bank.epoch());
737 assert_eq!(num_slots_in_epoch, 32);
738
739 let transfer_amount = 5_000;
740
741 for slot in 1..=num_slots_in_epoch + 2 {
742 let bank = new_bank_from_parent_with_bank_forks(
743 bank_forks.as_ref(),
744 previous_bank.clone(),
745 &Pubkey::default(),
746 slot,
747 );
748
749 let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash());
752 let vote = vote_transaction::new_tower_sync_transaction(
753 tower_sync,
754 previous_bank.last_blockhash(),
755 &validator_vote_keypairs.node_keypair,
756 &validator_vote_keypairs.vote_keypair,
757 &validator_vote_keypairs.vote_keypair,
758 None,
759 );
760 bank.process_transaction(&vote).unwrap();
761
762 let system_tx = system_transaction::transfer(
764 &mint_keypair,
765 &new_stake_address,
766 transfer_amount,
767 bank.last_blockhash(),
768 );
769 let system_result = bank.process_transaction(&system_tx);
770
771 assert!(system_result.is_ok());
773
774 let stake_ix = solana_sdk::stake::instruction::withdraw(
776 &new_stake_address,
777 &new_stake_address,
778 &mint_keypair.pubkey(),
779 transfer_amount,
780 None,
781 );
782 let stake_tx = Transaction::new_signed_with_payer(
783 &[stake_ix],
784 Some(&mint_keypair.pubkey()),
785 &[&mint_keypair, &new_stake_signer],
786 bank.last_blockhash(),
787 );
788 let stake_result = bank.process_transaction(&stake_tx);
789
790 if slot == num_slots_in_epoch {
791 assert_eq!(
795 stake_result,
796 Err(InstructionError(0, StakeError::EpochRewardsActive.into()))
797 );
798 } else {
799 assert!(stake_result.is_ok());
802 }
803
804 bank.register_unique_recent_blockhash_for_test();
808 previous_bank = bank;
809 }
810 }
811
812 #[test]
813 fn test_get_rewards_and_partitions() {
814 let starting_slot = SLOTS_PER_EPOCH - 1;
815 let num_rewards = 100;
816 let stake_account_stores_per_block = 50;
817 let RewardBank { bank, .. } =
818 create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
819
820 assert_eq!(
823 bank.get_rewards_and_num_partitions(),
824 KeyedRewardsAndNumPartitions {
825 keyed_rewards: vec![],
826 num_partitions: None,
827 }
828 );
829
830 let epoch_boundary_bank = Arc::new(Bank::new_from_parent(
831 bank,
832 &Pubkey::default(),
833 SLOTS_PER_EPOCH,
834 ));
835 let KeyedRewardsAndNumPartitions {
837 keyed_rewards,
838 num_partitions,
839 } = epoch_boundary_bank.get_rewards_and_num_partitions();
840 for (_pubkey, reward) in keyed_rewards.iter() {
841 assert_eq!(reward.reward_type, RewardType::Voting);
842 }
843 assert_eq!(keyed_rewards.len(), num_rewards);
844 assert_eq!(
845 num_partitions,
846 Some(num_rewards as u64 / stake_account_stores_per_block)
847 );
848
849 let mut total_staking_rewards = 0;
850
851 let partition0_bank = Arc::new(Bank::new_from_parent(
852 epoch_boundary_bank,
853 &Pubkey::default(),
854 SLOTS_PER_EPOCH + 1,
855 ));
856 let KeyedRewardsAndNumPartitions {
859 keyed_rewards,
860 num_partitions,
861 } = partition0_bank.get_rewards_and_num_partitions();
862 for (_pubkey, reward) in keyed_rewards.iter() {
863 assert_eq!(reward.reward_type, RewardType::Staking);
864 }
865 total_staking_rewards += keyed_rewards.len();
866 assert_eq!(num_partitions, None);
867
868 let partition1_bank = Arc::new(Bank::new_from_parent(
869 partition0_bank,
870 &Pubkey::default(),
871 SLOTS_PER_EPOCH + 2,
872 ));
873 let KeyedRewardsAndNumPartitions {
876 keyed_rewards,
877 num_partitions,
878 } = partition1_bank.get_rewards_and_num_partitions();
879 for (_pubkey, reward) in keyed_rewards.iter() {
880 assert_eq!(reward.reward_type, RewardType::Staking);
881 }
882 total_staking_rewards += keyed_rewards.len();
883 assert_eq!(num_partitions, None);
884
885 assert_eq!(total_staking_rewards, num_rewards);
887
888 let bank = Bank::new_from_parent(partition1_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 3);
889 assert_eq!(
892 bank.get_rewards_and_num_partitions(),
893 KeyedRewardsAndNumPartitions {
894 keyed_rewards: vec![],
895 num_partitions: None,
896 }
897 );
898 }
899
900 #[test]
901 fn test_rewards_and_partitions_should_record() {
902 let reward = RewardInfo {
903 reward_type: RewardType::Voting,
904 lamports: 55,
905 post_balance: 5555,
906 commission: Some(5),
907 };
908
909 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
910 keyed_rewards: vec![],
911 num_partitions: None,
912 };
913 assert!(!rewards_and_partitions.should_record());
914
915 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
916 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
917 num_partitions: None,
918 };
919 assert!(rewards_and_partitions.should_record());
920
921 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
922 keyed_rewards: vec![],
923 num_partitions: Some(42),
924 };
925 assert!(rewards_and_partitions.should_record());
926
927 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
928 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
929 num_partitions: Some(42),
930 };
931 assert!(rewards_and_partitions.should_record());
932 }
933}