1mod calculation;
2mod compare;
3mod distribution;
4mod epoch_rewards_hasher;
5mod sysvar;
6
7use {
8 super::Bank,
9 crate::{stake_account::StakeAccount, stake_history::StakeHistory},
10 solana_accounts_db::{
11 partitioned_rewards::PartitionedEpochRewardsConfig, stake_rewards::StakeReward,
12 },
13 solana_feature_set as feature_set,
14 solana_sdk::{
15 account::AccountSharedData,
16 account_utils::StateMut,
17 pubkey::Pubkey,
18 reward_info::RewardInfo,
19 stake::state::{Delegation, Stake, StakeStateV2},
20 },
21 solana_stake_program::points::PointValue,
22 solana_vote::vote_account::VoteAccounts,
23 std::sync::Arc,
24};
25
26const REWARD_CALCULATION_NUM_BLOCKS: u64 = 1;
29
30#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
31pub(crate) struct PartitionedStakeReward {
32 pub stake_pubkey: Pubkey,
34 pub stake: Stake,
36 pub stake_reward_info: RewardInfo,
40}
41
42impl PartitionedStakeReward {
43 fn maybe_from(stake_reward: &StakeReward) -> Option<Self> {
44 if let Ok(StakeStateV2::Stake(_meta, stake, _flags)) = stake_reward.stake_account.state() {
45 Some(Self {
46 stake_pubkey: stake_reward.stake_pubkey,
47 stake,
48 stake_reward_info: stake_reward.stake_reward_info,
49 })
50 } else {
51 None
52 }
53 }
54}
55
56type PartitionedStakeRewards = Vec<PartitionedStakeReward>;
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub(crate) struct StartBlockHeightAndRewards {
60 pub(crate) distribution_starting_block_height: u64,
62 pub(crate) stake_rewards_by_partition: Arc<Vec<PartitionedStakeRewards>>,
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
68pub(crate) enum EpochRewardStatus {
69 Active(StartBlockHeightAndRewards),
74 #[default]
76 Inactive,
77}
78
79#[derive(Debug, Default)]
80pub(super) struct VoteRewardsAccounts {
81 pub(super) rewards: Vec<(Pubkey, RewardInfo)>,
84 pub(super) accounts_to_store: Vec<Option<AccountSharedData>>,
88}
89
90#[derive(Debug, Default)]
91struct StakeRewardCalculation {
93 stake_rewards: PartitionedStakeRewards,
95 total_stake_rewards_lamports: u64,
97}
98
99#[derive(Debug)]
100struct CalculateValidatorRewardsResult {
101 vote_rewards_accounts: VoteRewardsAccounts,
102 stake_reward_calculation: StakeRewardCalculation,
103 point_value: PointValue,
104}
105
106impl Default for CalculateValidatorRewardsResult {
107 fn default() -> Self {
108 Self {
109 vote_rewards_accounts: VoteRewardsAccounts::default(),
110 stake_reward_calculation: StakeRewardCalculation::default(),
111 point_value: PointValue {
112 points: 0,
113 rewards: 0,
114 },
115 }
116 }
117}
118
119pub(super) struct EpochRewardCalculateParamInfo<'a> {
121 pub(super) stake_history: StakeHistory,
122 pub(super) stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)>,
123 pub(super) cached_vote_accounts: &'a VoteAccounts,
124}
125
126pub(super) struct PartitionedRewardsCalculation {
130 pub(super) vote_account_rewards: VoteRewardsAccounts,
131 pub(super) stake_rewards_by_partition: StakeRewardCalculationPartitioned,
132 pub(super) old_vote_balance_and_staked: u64,
133 pub(super) validator_rate: f64,
134 pub(super) foundation_rate: f64,
135 pub(super) prev_epoch_duration_in_years: f64,
136 pub(super) capitalization: u64,
137 point_value: PointValue,
138}
139
140pub(super) struct StakeRewardCalculationPartitioned {
142 pub(super) stake_rewards_by_partition: Vec<PartitionedStakeRewards>,
144 pub(super) total_stake_rewards_lamports: u64,
146}
147
148pub(super) struct CalculateRewardsAndDistributeVoteRewardsResult {
149 pub(super) total_rewards: u64,
152 pub(super) distributed_rewards: u64,
154 pub(super) point_value: PointValue,
159 pub(super) stake_rewards_by_partition: Vec<PartitionedStakeRewards>,
161}
162
163pub(crate) type StakeRewards = Vec<StakeReward>;
164
165#[derive(Debug, PartialEq)]
166pub struct KeyedRewardsAndNumPartitions {
167 pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
168 pub num_partitions: Option<u64>,
169}
170
171impl KeyedRewardsAndNumPartitions {
172 pub fn should_record(&self) -> bool {
173 !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
174 }
175}
176
177impl Bank {
178 pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
179 let keyed_rewards = self.rewards.read().unwrap().clone();
180 let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
181 let epoch_schedule = self.epoch_schedule();
184 let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
185 let is_first_block_in_epoch = self.epoch() > parent_epoch;
186
187 let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
188 .then_some(epoch_rewards_sysvar.num_partitions);
189 KeyedRewardsAndNumPartitions {
190 keyed_rewards,
191 num_partitions,
192 }
193 }
194
195 pub(super) fn is_partitioned_rewards_feature_enabled(&self) -> bool {
196 self.feature_set
197 .is_active(&feature_set::enable_partitioned_epoch_reward::id())
198 || self
199 .feature_set
200 .is_active(&feature_set::partitioned_epoch_rewards_superfeature::id())
201 }
202
203 pub(crate) fn set_epoch_reward_status_active(
204 &mut self,
205 distribution_starting_block_height: u64,
206 stake_rewards_by_partition: Vec<PartitionedStakeRewards>,
207 ) {
208 self.epoch_reward_status = EpochRewardStatus::Active(StartBlockHeightAndRewards {
209 distribution_starting_block_height,
210 stake_rewards_by_partition: Arc::new(stake_rewards_by_partition),
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(super) fn is_partitioned_rewards_code_enabled(&self) -> bool {
254 self.is_partitioned_rewards_feature_enabled()
255 || self
256 .partitioned_epoch_rewards_config()
257 .test_enable_partitioned_rewards
258 }
259
260 pub fn force_reward_interval_end_for_tests(&mut self) {
262 self.epoch_reward_status = EpochRewardStatus::Inactive;
263 }
264
265 pub(super) fn force_partition_rewards_in_first_block_of_epoch(&self) -> bool {
266 self.partitioned_epoch_rewards_config()
267 .test_enable_partitioned_rewards
268 && self.partitioned_rewards_stake_account_stores_per_block() == u64::MAX
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use {
275 super::*,
276 crate::{
277 bank::tests::{create_genesis_config, new_bank_from_parent_with_bank_forks},
278 genesis_utils::{
279 create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs,
280 },
281 runtime_config::RuntimeConfig,
282 },
283 assert_matches::assert_matches,
284 solana_accounts_db::{
285 accounts_db::{AccountsDbConfig, ACCOUNTS_DB_CONFIG_FOR_TESTING},
286 partitioned_rewards::TestPartitionedEpochRewards,
287 },
288 solana_sdk::{
289 account::Account,
290 epoch_schedule::EpochSchedule,
291 native_token::LAMPORTS_PER_SOL,
292 reward_type::RewardType,
293 signature::Signer,
294 signer::keypair::Keypair,
295 stake::instruction::StakeError,
296 system_transaction,
297 transaction::Transaction,
298 vote::state::{VoteStateVersions, MAX_LOCKOUT_HISTORY},
299 },
300 solana_vote_program::{
301 vote_state::{self, TowerSync},
302 vote_transaction,
303 },
304 };
305
306 impl PartitionedStakeReward {
307 pub fn new_random() -> Self {
308 Self::maybe_from(&StakeReward::new_random()).unwrap()
309 }
310 }
311
312 pub fn convert_rewards(
313 stake_rewards: impl IntoIterator<Item = StakeReward>,
314 ) -> PartitionedStakeRewards {
315 stake_rewards
316 .into_iter()
317 .map(|stake_reward| PartitionedStakeReward::maybe_from(&stake_reward).unwrap())
318 .collect()
319 }
320
321 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
322 enum RewardInterval {
323 InsideInterval,
325 OutsideInterval,
327 }
328
329 impl Bank {
330 fn get_reward_interval(&self) -> RewardInterval {
332 if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) {
333 RewardInterval::InsideInterval
334 } else {
335 RewardInterval::OutsideInterval
336 }
337 }
338 }
339
340 pub(super) const SLOTS_PER_EPOCH: u64 = 32;
341
342 pub(super) struct RewardBank {
343 pub(super) bank: Arc<Bank>,
344 pub(super) voters: Vec<Pubkey>,
345 pub(super) stakers: Vec<Pubkey>,
346 }
347
348 pub(super) fn create_default_reward_bank(
350 expected_num_delegations: usize,
351 advance_num_slots: u64,
352 ) -> RewardBank {
353 create_reward_bank(
354 expected_num_delegations,
355 PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
356 advance_num_slots,
357 )
358 }
359
360 pub(super) fn create_reward_bank(
361 expected_num_delegations: usize,
362 stake_account_stores_per_block: u64,
363 advance_num_slots: u64,
364 ) -> RewardBank {
365 create_reward_bank_with_specific_stakes(
366 vec![2_000_000_000; expected_num_delegations],
367 stake_account_stores_per_block,
368 advance_num_slots,
369 )
370 }
371
372 pub(super) fn create_reward_bank_with_specific_stakes(
373 stakes: Vec<u64>,
374 stake_account_stores_per_block: u64,
375 advance_num_slots: u64,
376 ) -> RewardBank {
377 let validator_keypairs = (0..stakes.len())
378 .map(|_| ValidatorVoteKeypairs::new_rand())
379 .collect::<Vec<_>>();
380
381 let GenesisConfigInfo {
382 mut genesis_config, ..
383 } = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stakes);
384 genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
385
386 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
387 accounts_db_config.test_partitioned_epoch_rewards =
388 TestPartitionedEpochRewards::PartitionedEpochRewardsConfigRewardBlocks {
389 stake_account_stores_per_block,
390 };
391
392 let bank = Bank::new_with_paths(
393 &genesis_config,
394 Arc::new(RuntimeConfig::default()),
395 Vec::new(),
396 None,
397 None,
398 false,
399 Some(accounts_db_config),
400 None,
401 Some(Pubkey::new_unique()),
402 Arc::default(),
403 None,
404 None,
405 );
406
407 for validator_vote_keypairs in &validator_keypairs {
410 let vote_id = validator_vote_keypairs.vote_keypair.pubkey();
411 let mut vote_account = bank.get_account(&vote_id).unwrap();
412 let mut vote_state = Some(vote_state::from(&vote_account).unwrap());
414 for i in 0..MAX_LOCKOUT_HISTORY + 42 {
415 if let Some(v) = vote_state.as_mut() {
416 vote_state::process_slot_vote_unchecked(v, i as u64)
417 }
418 let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap()));
419 vote_state::to(&versioned, &mut vote_account).unwrap();
420 match versioned {
421 VoteStateVersions::Current(v) => {
422 vote_state = Some(*v);
423 }
424 _ => panic!("Has to be of type Current"),
425 };
426 }
427 bank.store_account_and_update_capitalization(&vote_id, &vote_account);
428 }
429
430 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
433 let bank = new_bank_from_parent_with_bank_forks(
434 &bank_forks,
435 bank,
436 &Pubkey::default(),
437 advance_num_slots,
438 );
439
440 RewardBank {
441 bank,
442 voters: validator_keypairs
443 .iter()
444 .map(|k| k.vote_keypair.pubkey())
445 .collect(),
446 stakers: validator_keypairs
447 .iter()
448 .map(|k| k.stake_keypair.pubkey())
449 .collect(),
450 }
451 }
452
453 #[test]
454 fn test_force_reward_interval_end() {
455 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
456 let mut bank = Bank::new_for_tests(&genesis_config);
457
458 let expected_num = 100;
459
460 let stake_rewards = (0..expected_num)
461 .map(|_| PartitionedStakeReward::new_random())
462 .collect::<Vec<_>>();
463
464 bank.set_epoch_reward_status_active(
465 bank.block_height() + REWARD_CALCULATION_NUM_BLOCKS,
466 vec![stake_rewards],
467 );
468 assert!(bank.get_reward_interval() == RewardInterval::InsideInterval);
469
470 bank.force_reward_interval_end_for_tests();
471 assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval);
472 }
473
474 #[test]
475 fn test_is_partitioned_reward_feature_enable() {
476 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
477
478 let mut bank = Bank::new_for_tests(&genesis_config);
479 assert!(!bank.is_partitioned_rewards_feature_enabled());
480 bank.activate_feature(&feature_set::partitioned_epoch_rewards_superfeature::id());
481 assert!(bank.is_partitioned_rewards_feature_enabled());
482 }
483
484 #[test]
487 fn test_get_reward_distribution_num_blocks_cap() {
488 let (mut genesis_config, _mint_keypair) =
489 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
490 genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false);
491
492 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
494 accounts_db_config.test_partitioned_epoch_rewards =
495 TestPartitionedEpochRewards::PartitionedEpochRewardsConfigRewardBlocks {
496 stake_account_stores_per_block: 10,
497 };
498
499 let bank = Bank::new_with_paths(
500 &genesis_config,
501 Arc::new(RuntimeConfig::default()),
502 Vec::new(),
503 None,
504 None,
505 false,
506 Some(accounts_db_config),
507 None,
508 Some(Pubkey::new_unique()),
509 Arc::default(),
510 None,
511 None,
512 );
513
514 let stake_account_stores_per_block =
515 bank.partitioned_rewards_stake_account_stores_per_block();
516 assert_eq!(stake_account_stores_per_block, 10);
517
518 let check_num_reward_distribution_blocks =
519 |num_stakes: u64, expected_num_reward_distribution_blocks: u64| {
520 let stake_rewards = (0..num_stakes)
522 .map(|_| PartitionedStakeReward::new_random())
523 .collect::<Vec<_>>();
524
525 assert_eq!(
526 bank.get_reward_distribution_num_blocks(&stake_rewards),
527 expected_num_reward_distribution_blocks
528 );
529 };
530
531 for test_record in [
532 (0, 1),
534 (1, 1),
535 (stake_account_stores_per_block, 1),
536 (2 * stake_account_stores_per_block - 1, 2),
537 (2 * stake_account_stores_per_block, 2),
538 (3 * stake_account_stores_per_block - 1, 3),
539 (3 * stake_account_stores_per_block, 3),
540 (4 * stake_account_stores_per_block, 3), (5 * stake_account_stores_per_block, 3), ] {
543 check_num_reward_distribution_blocks(test_record.0, test_record.1);
544 }
545 }
546
547 #[test]
549 fn test_get_reward_distribution_num_blocks_normal() {
550 solana_logger::setup();
551 let (mut genesis_config, _mint_keypair) =
552 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
553 genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
554
555 let bank = Bank::new_for_tests(&genesis_config);
556
557 let expected_num = 8192;
559 let stake_rewards = (0..expected_num)
560 .map(|_| PartitionedStakeReward::new_random())
561 .collect::<Vec<_>>();
562
563 assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2);
564 }
565
566 #[test]
569 fn test_get_reward_distribution_num_blocks_warmup() {
570 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
571
572 let bank = Bank::new_for_tests(&genesis_config);
573 let rewards = vec![];
574 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
575 }
576
577 #[test]
578 fn test_rewards_computation_and_partitioned_distribution_one_block() {
579 solana_logger::setup();
580
581 let starting_slot = SLOTS_PER_EPOCH - 1;
582 let RewardBank {
583 bank: mut previous_bank,
584 ..
585 } = create_default_reward_bank(100, starting_slot - 1);
586
587 for slot in starting_slot..=(2 * SLOTS_PER_EPOCH) + 2 {
589 let pre_cap = previous_bank.capitalization();
590 let curr_bank = Bank::new_from_parent(previous_bank, &Pubkey::default(), slot);
591 let post_cap = curr_bank.capitalization();
592
593 if slot % SLOTS_PER_EPOCH == 0 {
594 assert_matches!(
597 curr_bank.get_reward_interval(),
598 RewardInterval::InsideInterval
599 );
600
601 if slot == SLOTS_PER_EPOCH {
602 assert!(post_cap > pre_cap);
604 } else {
605 assert_eq!(post_cap, pre_cap);
606 }
607 } else if slot == SLOTS_PER_EPOCH + 1 {
608 assert_matches!(
614 curr_bank.get_reward_interval(),
615 RewardInterval::OutsideInterval
616 );
617
618 let account = curr_bank
619 .get_account(&solana_sdk::sysvar::epoch_rewards::id())
620 .unwrap();
621 let epoch_rewards: solana_sdk::sysvar::epoch_rewards::EpochRewards =
622 solana_sdk::account::from_account(&account).unwrap();
623 assert_eq!(post_cap, pre_cap + epoch_rewards.distributed_rewards);
624 } else {
625 assert_matches!(
630 curr_bank.get_reward_interval(),
631 RewardInterval::OutsideInterval
632 );
633
634 assert_eq!(post_cap, pre_cap);
636 }
637 if slot >= SLOTS_PER_EPOCH {
640 let epoch_rewards_lamports =
641 curr_bank.get_balance(&solana_sdk::sysvar::epoch_rewards::id());
642 assert!(epoch_rewards_lamports > 0);
643 }
644 previous_bank = Arc::new(curr_bank);
645 }
646 }
647
648 #[test]
650 fn test_rewards_computation_and_partitioned_distribution_two_blocks() {
651 solana_logger::setup();
652
653 let starting_slot = SLOTS_PER_EPOCH - 1;
654 let RewardBank {
655 bank: mut previous_bank,
656 ..
657 } = create_reward_bank(100, 50, starting_slot - 1);
658
659 for slot in starting_slot..=SLOTS_PER_EPOCH + 3 {
661 let pre_cap = previous_bank.capitalization();
662
663 let pre_sysvar_account = previous_bank
664 .get_account(&solana_sdk::sysvar::epoch_rewards::id())
665 .unwrap_or_default();
666 let pre_epoch_rewards: solana_sdk::sysvar::epoch_rewards::EpochRewards =
667 solana_sdk::account::from_account(&pre_sysvar_account).unwrap_or_default();
668 let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
669
670 let curr_bank = Bank::new_from_parent(previous_bank, &Pubkey::default(), slot);
671 let post_cap = curr_bank.capitalization();
672
673 if slot == SLOTS_PER_EPOCH {
674 assert_matches!(
677 curr_bank.get_reward_interval(),
678 RewardInterval::InsideInterval
679 );
680
681 assert!(post_cap > pre_cap);
683 } else if slot == SLOTS_PER_EPOCH + 1 {
684 assert_matches!(
688 curr_bank.get_reward_interval(),
689 RewardInterval::InsideInterval
690 );
691
692 let account = curr_bank
693 .get_account(&solana_sdk::sysvar::epoch_rewards::id())
694 .unwrap();
695 let epoch_rewards: solana_sdk::sysvar::epoch_rewards::EpochRewards =
696 solana_sdk::account::from_account(&account).unwrap();
697 assert_eq!(
698 post_cap,
699 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
700 );
701 } else if slot == SLOTS_PER_EPOCH + 2 {
702 assert_matches!(
708 curr_bank.get_reward_interval(),
709 RewardInterval::OutsideInterval
710 );
711
712 let account = curr_bank
713 .get_account(&solana_sdk::sysvar::epoch_rewards::id())
714 .unwrap();
715 let epoch_rewards: solana_sdk::sysvar::epoch_rewards::EpochRewards =
716 solana_sdk::account::from_account(&account).unwrap();
717 assert_eq!(
718 post_cap,
719 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
720 );
721 } else {
722 assert_matches!(
727 curr_bank.get_reward_interval(),
728 RewardInterval::OutsideInterval
729 );
730
731 assert_eq!(post_cap, pre_cap);
733 }
734 previous_bank = Arc::new(curr_bank);
735 }
736 }
737
738 #[test]
742 fn test_program_execution_restricted_for_stake_account_in_reward_period() {
743 use solana_sdk::transaction::TransactionError::InstructionError;
744
745 let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
746 let validator_keypairs = vec![&validator_vote_keypairs];
747 let GenesisConfigInfo {
748 mut genesis_config,
749 mint_keypair,
750 ..
751 } = create_genesis_config_with_vote_accounts(
752 1_000_000_000,
753 &validator_keypairs,
754 vec![1_000_000_000; 1],
755 );
756
757 let vote_key = validator_keypairs[0].vote_keypair.pubkey();
759 let vote_account = genesis_config
760 .accounts
761 .iter()
762 .find(|(&address, _)| address == vote_key)
763 .map(|(_, account)| account)
764 .unwrap()
765 .clone();
766
767 let new_stake_signer = Keypair::new();
768 let new_stake_address = new_stake_signer.pubkey();
769 let new_stake_account = Account::from(solana_stake_program::stake_state::create_account(
770 &new_stake_address,
771 &vote_key,
772 &vote_account.into(),
773 &genesis_config.rent,
774 2_000_000_000,
775 ));
776 genesis_config
777 .accounts
778 .extend(vec![(new_stake_address, new_stake_account)]);
779
780 let (mut previous_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
781 let num_slots_in_epoch = previous_bank.get_slots_in_epoch(previous_bank.epoch());
782 assert_eq!(num_slots_in_epoch, 32);
783
784 let transfer_amount = 5_000;
785
786 for slot in 1..=num_slots_in_epoch + 2 {
787 let bank = new_bank_from_parent_with_bank_forks(
788 bank_forks.as_ref(),
789 previous_bank.clone(),
790 &Pubkey::default(),
791 slot,
792 );
793
794 let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash());
797 let vote = vote_transaction::new_tower_sync_transaction(
798 tower_sync,
799 previous_bank.last_blockhash(),
800 &validator_vote_keypairs.node_keypair,
801 &validator_vote_keypairs.vote_keypair,
802 &validator_vote_keypairs.vote_keypair,
803 None,
804 );
805 bank.process_transaction(&vote).unwrap();
806
807 let system_tx = system_transaction::transfer(
809 &mint_keypair,
810 &new_stake_address,
811 transfer_amount,
812 bank.last_blockhash(),
813 );
814 let system_result = bank.process_transaction(&system_tx);
815
816 assert!(system_result.is_ok());
818
819 let stake_ix = solana_sdk::stake::instruction::withdraw(
821 &new_stake_address,
822 &new_stake_address,
823 &mint_keypair.pubkey(),
824 transfer_amount,
825 None,
826 );
827 let stake_tx = Transaction::new_signed_with_payer(
828 &[stake_ix],
829 Some(&mint_keypair.pubkey()),
830 &[&mint_keypair, &new_stake_signer],
831 bank.last_blockhash(),
832 );
833 let stake_result = bank.process_transaction(&stake_tx);
834
835 if slot == num_slots_in_epoch {
836 assert_eq!(
840 stake_result,
841 Err(InstructionError(0, StakeError::EpochRewardsActive.into()))
842 );
843 } else {
844 assert!(stake_result.is_ok());
847 }
848
849 bank.register_unique_recent_blockhash_for_test();
853 previous_bank = bank;
854 }
855 }
856
857 #[test]
858 fn test_get_rewards_and_partitions() {
859 let starting_slot = SLOTS_PER_EPOCH - 1;
860 let num_rewards = 100;
861 let stake_account_stores_per_block = 50;
862 let RewardBank { bank, .. } =
863 create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
864
865 assert!(bank.is_partitioned_rewards_feature_enabled());
866 assert_eq!(
869 bank.get_rewards_and_num_partitions(),
870 KeyedRewardsAndNumPartitions {
871 keyed_rewards: vec![],
872 num_partitions: None,
873 }
874 );
875
876 let epoch_boundary_bank = Arc::new(Bank::new_from_parent(
877 bank,
878 &Pubkey::default(),
879 SLOTS_PER_EPOCH,
880 ));
881 assert!(epoch_boundary_bank.is_partitioned_rewards_feature_enabled());
882 let KeyedRewardsAndNumPartitions {
884 keyed_rewards,
885 num_partitions,
886 } = epoch_boundary_bank.get_rewards_and_num_partitions();
887 for (_pubkey, reward) in keyed_rewards.iter() {
888 assert_eq!(reward.reward_type, RewardType::Voting);
889 }
890 assert_eq!(keyed_rewards.len(), num_rewards);
891 assert_eq!(
892 num_partitions,
893 Some(num_rewards as u64 / stake_account_stores_per_block)
894 );
895
896 let mut total_staking_rewards = 0;
897
898 let partition0_bank = Arc::new(Bank::new_from_parent(
899 epoch_boundary_bank,
900 &Pubkey::default(),
901 SLOTS_PER_EPOCH + 1,
902 ));
903 assert!(partition0_bank.is_partitioned_rewards_feature_enabled());
904 let KeyedRewardsAndNumPartitions {
907 keyed_rewards,
908 num_partitions,
909 } = partition0_bank.get_rewards_and_num_partitions();
910 for (_pubkey, reward) in keyed_rewards.iter() {
911 assert_eq!(reward.reward_type, RewardType::Staking);
912 }
913 total_staking_rewards += keyed_rewards.len();
914 assert_eq!(num_partitions, None);
915
916 let partition1_bank = Arc::new(Bank::new_from_parent(
917 partition0_bank,
918 &Pubkey::default(),
919 SLOTS_PER_EPOCH + 2,
920 ));
921 assert!(partition1_bank.is_partitioned_rewards_feature_enabled());
922 let KeyedRewardsAndNumPartitions {
925 keyed_rewards,
926 num_partitions,
927 } = partition1_bank.get_rewards_and_num_partitions();
928 for (_pubkey, reward) in keyed_rewards.iter() {
929 assert_eq!(reward.reward_type, RewardType::Staking);
930 }
931 total_staking_rewards += keyed_rewards.len();
932 assert_eq!(num_partitions, None);
933
934 assert_eq!(total_staking_rewards, num_rewards);
936
937 let bank = Bank::new_from_parent(partition1_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 3);
938 assert!(bank.is_partitioned_rewards_feature_enabled());
939 assert_eq!(
942 bank.get_rewards_and_num_partitions(),
943 KeyedRewardsAndNumPartitions {
944 keyed_rewards: vec![],
945 num_partitions: None,
946 }
947 );
948 }
949
950 #[test]
951 fn test_get_rewards_and_partitions_before_feature() {
952 let starting_slot = SLOTS_PER_EPOCH - 1;
953 let num_rewards = 100;
954
955 let validator_keypairs = (0..num_rewards)
956 .map(|_| ValidatorVoteKeypairs::new_rand())
957 .collect::<Vec<_>>();
958
959 let GenesisConfigInfo {
960 mut genesis_config, ..
961 } = create_genesis_config_with_vote_accounts(
962 1_000_000_000,
963 &validator_keypairs,
964 vec![2_000_000_000; num_rewards],
965 );
966 genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
967
968 genesis_config
970 .accounts
971 .remove(&feature_set::enable_partitioned_epoch_reward::id());
972 genesis_config
973 .accounts
974 .remove(&feature_set::partitioned_epoch_rewards_superfeature::id());
975
976 let bank = Bank::new_for_tests(&genesis_config);
977
978 for validator_vote_keypairs in &validator_keypairs {
979 let vote_id = validator_vote_keypairs.vote_keypair.pubkey();
980 let mut vote_account = bank.get_account(&vote_id).unwrap();
981 let mut vote_state = Some(vote_state::from(&vote_account).unwrap());
983 for i in 0..MAX_LOCKOUT_HISTORY + 42 {
984 if let Some(v) = vote_state.as_mut() {
985 vote_state::process_slot_vote_unchecked(v, i as u64)
986 }
987 let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap()));
988 vote_state::to(&versioned, &mut vote_account).unwrap();
989 match versioned {
990 VoteStateVersions::Current(v) => {
991 vote_state = Some(*v);
992 }
993 _ => panic!("Has to be of type Current"),
994 };
995 }
996 bank.store_account_and_update_capitalization(&vote_id, &vote_account);
997 }
998
999 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
1000 let bank = new_bank_from_parent_with_bank_forks(
1001 &bank_forks,
1002 bank,
1003 &Pubkey::default(),
1004 starting_slot,
1005 );
1006
1007 assert!(!bank.is_partitioned_rewards_feature_enabled());
1008 assert_eq!(
1011 bank.get_rewards_and_num_partitions(),
1012 KeyedRewardsAndNumPartitions {
1013 keyed_rewards: vec![],
1014 num_partitions: None,
1015 }
1016 );
1017
1018 let epoch_boundary_bank = Arc::new(Bank::new_from_parent(
1019 bank,
1020 &Pubkey::default(),
1021 SLOTS_PER_EPOCH,
1022 ));
1023 assert!(!epoch_boundary_bank.is_partitioned_rewards_feature_enabled());
1024 let KeyedRewardsAndNumPartitions {
1026 keyed_rewards,
1027 num_partitions,
1028 } = epoch_boundary_bank.get_rewards_and_num_partitions();
1029 let mut voting_rewards_count = 0;
1030 let mut staking_rewards_count = 0;
1031 for (_pubkey, reward) in keyed_rewards.iter() {
1032 match reward.reward_type {
1033 RewardType::Voting => {
1034 voting_rewards_count += 1;
1035 }
1036 RewardType::Staking => {
1037 staking_rewards_count += 1;
1038 }
1039 _ => {}
1040 }
1041 }
1042 assert_eq!(
1043 keyed_rewards.len(),
1044 voting_rewards_count + staking_rewards_count
1045 );
1046 assert_eq!(voting_rewards_count, num_rewards);
1047 assert_eq!(staking_rewards_count, num_rewards);
1048 assert!(num_partitions.is_none());
1049
1050 let bank =
1051 Bank::new_from_parent(epoch_boundary_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 1);
1052 assert!(!bank.is_partitioned_rewards_feature_enabled());
1053 assert_eq!(
1056 bank.get_rewards_and_num_partitions(),
1057 KeyedRewardsAndNumPartitions {
1058 keyed_rewards: vec![],
1059 num_partitions: None,
1060 }
1061 );
1062 }
1063
1064 #[test]
1065 fn test_rewards_and_partitions_should_record() {
1066 let reward = RewardInfo {
1067 reward_type: RewardType::Voting,
1068 lamports: 55,
1069 post_balance: 5555,
1070 commission: Some(5),
1071 };
1072
1073 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1074 keyed_rewards: vec![],
1075 num_partitions: None,
1076 };
1077 assert!(!rewards_and_partitions.should_record());
1078
1079 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1080 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1081 num_partitions: None,
1082 };
1083 assert!(rewards_and_partitions.should_record());
1084
1085 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1086 keyed_rewards: vec![],
1087 num_partitions: Some(42),
1088 };
1089 assert!(rewards_and_partitions.should_record());
1090
1091 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1092 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1093 num_partitions: Some(42),
1094 };
1095 assert!(rewards_and_partitions.should_record());
1096 }
1097}