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 rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator},
13 solana_account::{AccountSharedData, ReadableAccount},
14 solana_accounts_db::{
15 partitioned_rewards::PartitionedEpochRewardsConfig,
16 stake_rewards::StakeReward,
17 storable_accounts::{AccountForStorage, StorableAccounts},
18 },
19 solana_clock::Slot,
20 solana_pubkey::Pubkey,
21 solana_reward_info::RewardInfo,
22 solana_stake_interface::state::{Delegation, Stake},
23 solana_vote::vote_account::VoteAccounts,
24 std::{mem::MaybeUninit, sync::Arc},
25};
26
27const REWARD_CALCULATION_NUM_BLOCKS: u64 = 1;
30
31#[derive(Debug, Clone, PartialEq)]
32pub(crate) struct PartitionedStakeReward {
33 pub stake_pubkey: Pubkey,
35 pub stake: Stake,
37 pub stake_reward: u64,
39 pub commission: u8,
41}
42
43#[derive(Debug, Default, PartialEq)]
45pub(crate) struct PartitionedStakeRewards {
46 rewards: Vec<Option<PartitionedStakeReward>>,
48 num_rewards: usize,
50}
51
52impl PartitionedStakeRewards {
53 pub(crate) fn with_capacity(capacity: usize) -> Self {
54 let rewards = Vec::with_capacity(capacity);
55 Self {
56 rewards,
57 num_rewards: 0,
58 }
59 }
60
61 pub(crate) fn num_rewards(&self) -> usize {
63 self.num_rewards
64 }
65
66 pub(crate) fn total_len(&self) -> usize {
68 self.rewards.len()
69 }
70
71 pub(crate) fn get(&self, index: usize) -> Option<&Option<PartitionedStakeReward>> {
72 self.rewards.get(index)
73 }
74
75 pub(crate) fn enumerated_rewards_iter(
76 &self,
77 ) -> impl Iterator<Item = (usize, &PartitionedStakeReward)> {
78 self.rewards
79 .iter()
80 .enumerate()
81 .filter_map(|(index, reward)| reward.as_ref().map(|reward| (index, reward)))
82 }
83
84 fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit<Option<PartitionedStakeReward>>] {
85 self.rewards.spare_capacity_mut()
86 }
87
88 unsafe fn assume_init(&mut self, num_stake_rewards: usize) {
89 self.rewards.set_len(self.rewards.capacity());
90 self.num_rewards = num_stake_rewards;
91 }
92}
93
94#[cfg(test)]
95impl FromIterator<Option<PartitionedStakeReward>> for PartitionedStakeRewards {
96 fn from_iter<T: IntoIterator<Item = Option<PartitionedStakeReward>>>(iter: T) -> Self {
97 let mut len_some: usize = 0;
98 let rewards = Vec::from_iter(iter.into_iter().inspect(|reward| {
99 if reward.is_some() {
100 len_some = len_some.saturating_add(1);
101 }
102 }));
103 Self {
104 rewards,
105 num_rewards: len_some,
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq)]
111pub(crate) struct StartBlockHeightAndRewards {
112 pub(crate) distribution_starting_block_height: u64,
114 pub(crate) all_stake_rewards: Arc<PartitionedStakeRewards>,
116}
117
118#[derive(Debug, Clone, PartialEq)]
119pub(crate) struct StartBlockHeightAndPartitionedRewards {
120 pub(crate) distribution_starting_block_height: u64,
122
123 pub(crate) all_stake_rewards: Arc<PartitionedStakeRewards>,
125
126 pub(crate) partition_indices: Vec<Vec<usize>>,
130}
131
132#[derive(Debug, Clone, PartialEq, Default)]
134pub(crate) enum EpochRewardStatus {
135 Active(EpochRewardPhase),
140 #[default]
142 Inactive,
143}
144
145#[derive(Debug, Clone, PartialEq)]
146pub(crate) enum EpochRewardPhase {
147 Calculation(StartBlockHeightAndRewards),
148 Distribution(StartBlockHeightAndPartitionedRewards),
149}
150
151#[derive(Debug, Default)]
152pub(super) struct VoteRewardsAccounts {
153 pub(super) accounts_with_rewards: Vec<(Pubkey, RewardInfo, AccountSharedData)>,
155 pub(super) total_vote_rewards_lamports: u64,
157}
158
159pub(super) struct VoteRewardsAccountsStorable<'a> {
161 pub slot: Slot,
162 pub vote_rewards_accounts: &'a VoteRewardsAccounts,
163}
164
165impl<'a> StorableAccounts<'a> for VoteRewardsAccountsStorable<'a> {
166 fn account<Ret>(
167 &self,
168 index: usize,
169 mut callback: impl for<'local> FnMut(AccountForStorage<'local>) -> Ret,
170 ) -> Ret {
171 let (pubkey, _, account) = &self.vote_rewards_accounts.accounts_with_rewards[index];
172 callback((pubkey, account).into())
173 }
174
175 fn is_zero_lamport(&self, index: usize) -> bool {
176 self.vote_rewards_accounts.accounts_with_rewards[index]
177 .2
178 .lamports()
179 == 0
180 }
181
182 fn data_len(&self, index: usize) -> usize {
183 self.vote_rewards_accounts.accounts_with_rewards[index]
184 .2
185 .data()
186 .len()
187 }
188
189 fn pubkey(&self, index: usize) -> &Pubkey {
190 &self.vote_rewards_accounts.accounts_with_rewards[index].0
191 }
192
193 fn slot(&self, _index: usize) -> Slot {
194 self.target_slot()
195 }
196
197 fn target_slot(&self) -> Slot {
198 self.slot
199 }
200
201 fn len(&self) -> usize {
202 self.vote_rewards_accounts.accounts_with_rewards.len()
203 }
204}
205
206#[derive(Debug, Default)]
207pub(super) struct StakeRewardCalculation {
209 stake_rewards: Arc<PartitionedStakeRewards>,
211 total_stake_rewards_lamports: u64,
213}
214
215#[derive(Debug)]
216struct CalculateValidatorRewardsResult {
217 vote_rewards_accounts: VoteRewardsAccounts,
218 stake_reward_calculation: StakeRewardCalculation,
219 point_value: PointValue,
220}
221
222impl Default for CalculateValidatorRewardsResult {
223 fn default() -> Self {
224 Self {
225 vote_rewards_accounts: VoteRewardsAccounts::default(),
226 stake_reward_calculation: StakeRewardCalculation::default(),
227 point_value: PointValue {
228 points: 0,
229 rewards: 0,
230 },
231 }
232 }
233}
234
235pub(super) struct FilteredStakeDelegations<'a> {
236 stake_delegations: Vec<(&'a Pubkey, &'a StakeAccount<Delegation>)>,
237 min_stake_delegation: Option<u64>,
238}
239
240impl<'a> FilteredStakeDelegations<'a> {
241 pub(super) fn len(&self) -> usize {
242 self.stake_delegations.len()
243 }
244
245 pub(super) fn par_iter(
246 &'a self,
247 ) -> impl IndexedParallelIterator<Item = Option<(&'a Pubkey, &'a StakeAccount<Delegation>)>>
248 {
249 self.stake_delegations
250 .par_iter()
251 .map(|(pubkey, stake_account)| {
258 match self.min_stake_delegation {
259 Some(min_stake_delegation)
260 if stake_account.delegation().stake < min_stake_delegation =>
261 {
262 None
263 }
264 _ => {
265 Some((*pubkey, *stake_account))
267 }
268 }
269 })
270 }
271}
272
273pub(super) struct EpochRewardCalculateParamInfo<'a> {
275 pub(super) stake_history: StakeHistory,
276 pub(super) stake_delegations: FilteredStakeDelegations<'a>,
277 pub(super) cached_vote_accounts: &'a VoteAccounts,
278}
279
280#[derive(Debug)]
284pub(super) struct PartitionedRewardsCalculation {
285 pub(super) vote_account_rewards: VoteRewardsAccounts,
286 pub(super) stake_rewards: StakeRewardCalculation,
287 pub(super) validator_rate: f64,
288 pub(super) foundation_rate: f64,
289 pub(super) prev_epoch_duration_in_years: f64,
290 pub(super) capitalization: u64,
291 point_value: PointValue,
292}
293
294pub(crate) type StakeRewards = Vec<StakeReward>;
295
296#[derive(Debug, PartialEq)]
297pub struct KeyedRewardsAndNumPartitions {
298 pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
299 pub num_partitions: Option<u64>,
300}
301
302impl KeyedRewardsAndNumPartitions {
303 pub fn should_record(&self) -> bool {
304 !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
305 }
306}
307
308impl Bank {
309 pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
310 let keyed_rewards = self.rewards.read().unwrap().clone();
311 let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
312 let epoch_schedule = self.epoch_schedule();
315 let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
316 let is_first_block_in_epoch = self.epoch() > parent_epoch;
317
318 let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
319 .then_some(epoch_rewards_sysvar.num_partitions);
320 KeyedRewardsAndNumPartitions {
321 keyed_rewards,
322 num_partitions,
323 }
324 }
325
326 pub(crate) fn set_epoch_reward_status_calculation(
327 &mut self,
328 distribution_starting_block_height: u64,
329 stake_rewards: Arc<PartitionedStakeRewards>,
330 ) {
331 self.epoch_reward_status =
332 EpochRewardStatus::Active(EpochRewardPhase::Calculation(StartBlockHeightAndRewards {
333 distribution_starting_block_height,
334 all_stake_rewards: stake_rewards,
335 }));
336 }
337
338 pub(crate) fn set_epoch_reward_status_distribution(
339 &mut self,
340 distribution_starting_block_height: u64,
341 all_stake_rewards: Arc<PartitionedStakeRewards>,
342 partition_indices: Vec<Vec<usize>>,
343 ) {
344 self.epoch_reward_status = EpochRewardStatus::Active(EpochRewardPhase::Distribution(
345 StartBlockHeightAndPartitionedRewards {
346 distribution_starting_block_height,
347 all_stake_rewards,
348 partition_indices,
349 },
350 ));
351 }
352
353 pub(super) fn partitioned_epoch_rewards_config(&self) -> &PartitionedEpochRewardsConfig {
354 &self
355 .rc
356 .accounts
357 .accounts_db
358 .partitioned_epoch_rewards_config
359 }
360
361 pub(super) fn partitioned_rewards_stake_account_stores_per_block(&self) -> u64 {
363 self.partitioned_epoch_rewards_config()
364 .stake_account_stores_per_block
365 }
366
367 pub(super) fn get_reward_distribution_num_blocks(
369 &self,
370 rewards: &PartitionedStakeRewards,
371 ) -> u64 {
372 let total_stake_accounts = rewards.num_rewards();
373 if self.epoch_schedule.warmup && self.epoch < self.first_normal_epoch() {
374 1
375 } else {
376 const MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH: u64 = 10;
377 let num_chunks = total_stake_accounts
378 .div_ceil(self.partitioned_rewards_stake_account_stores_per_block() as usize)
379 as u64;
380
381 num_chunks.clamp(
383 1,
384 (self.epoch_schedule.slots_per_epoch / MAX_FACTOR_OF_REWARD_BLOCKS_IN_EPOCH).max(1),
385 )
386 }
387 }
388
389 pub fn force_reward_interval_end_for_tests(&mut self) {
391 self.epoch_reward_status = EpochRewardStatus::Inactive;
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use {
398 super::*,
399 crate::{
400 bank::tests::create_genesis_config,
401 bank_forks::BankForks,
402 genesis_utils::{
403 create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs,
404 },
405 runtime_config::RuntimeConfig,
406 stake_utils,
407 },
408 assert_matches::assert_matches,
409 solana_account::{state_traits::StateMut, Account},
410 solana_accounts_db::accounts_db::{AccountsDbConfig, ACCOUNTS_DB_CONFIG_FOR_TESTING},
411 solana_epoch_schedule::EpochSchedule,
412 solana_hash::Hash,
413 solana_keypair::Keypair,
414 solana_native_token::LAMPORTS_PER_SOL,
415 solana_reward_info::RewardType,
416 solana_signer::Signer,
417 solana_stake_interface::state::StakeStateV2,
418 solana_system_transaction as system_transaction,
419 solana_vote::vote_transaction,
420 solana_vote_interface::state::{VoteStateV4, VoteStateVersions, MAX_LOCKOUT_HISTORY},
421 solana_vote_program::vote_state::{self, handler::VoteStateHandle, TowerSync},
422 std::sync::{Arc, RwLock},
423 };
424
425 impl PartitionedStakeReward {
426 fn maybe_from(stake_reward: &StakeReward) -> Option<Self> {
427 if let Ok(StakeStateV2::Stake(_meta, stake, _flags)) =
428 stake_reward.stake_account.state()
429 {
430 Some(Self {
431 stake_pubkey: stake_reward.stake_pubkey,
432 stake,
433 stake_reward: stake_reward.stake_reward_info.lamports as u64,
434 commission: stake_reward.stake_reward_info.commission.unwrap(),
435 })
436 } else {
437 None
438 }
439 }
440
441 pub fn new_random() -> Self {
442 Self::maybe_from(&StakeReward::new_random()).unwrap()
443 }
444 }
445
446 pub fn build_partitioned_stake_rewards(
447 stake_rewards: &PartitionedStakeRewards,
448 partition_indices: &[Vec<usize>],
449 ) -> Vec<PartitionedStakeRewards> {
450 partition_indices
451 .iter()
452 .map(|partition_index| {
453 partition_index
456 .iter()
457 .map(|&index| stake_rewards.get(index).unwrap().clone())
458 .collect::<PartitionedStakeRewards>()
459 })
460 .collect::<Vec<_>>()
461 }
462
463 pub fn convert_rewards(
464 stake_rewards: impl IntoIterator<Item = StakeReward>,
465 ) -> PartitionedStakeRewards {
466 stake_rewards
467 .into_iter()
468 .map(|stake_reward| Some(PartitionedStakeReward::maybe_from(&stake_reward).unwrap()))
469 .collect()
470 }
471
472 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
473 enum RewardInterval {
474 InsideInterval,
476 OutsideInterval,
478 }
479
480 impl Bank {
481 fn get_reward_interval(&self) -> RewardInterval {
483 if matches!(self.epoch_reward_status, EpochRewardStatus::Active(_)) {
484 RewardInterval::InsideInterval
485 } else {
486 RewardInterval::OutsideInterval
487 }
488 }
489
490 fn is_calculated(&self) -> bool {
491 matches!(
492 self.epoch_reward_status,
493 EpochRewardStatus::Active(EpochRewardPhase::Calculation(_))
494 )
495 }
496
497 fn is_partitioned(&self) -> bool {
498 matches!(
499 self.epoch_reward_status,
500 EpochRewardStatus::Active(EpochRewardPhase::Distribution(_))
501 )
502 }
503
504 fn get_epoch_rewards_from_cache(
505 &self,
506 parent_hash: &Hash,
507 ) -> Option<Arc<PartitionedRewardsCalculation>> {
508 self.epoch_rewards_calculation_cache
509 .lock()
510 .unwrap()
511 .get(parent_hash)
512 .cloned()
513 }
514
515 fn get_epoch_rewards_cache_len(&self) -> usize {
516 self.epoch_rewards_calculation_cache.lock().unwrap().len()
517 }
518 }
519
520 pub(super) const SLOTS_PER_EPOCH: u64 = 32;
521
522 pub(super) struct RewardBank {
523 pub(super) bank: Arc<Bank>,
524 pub(super) voters: Vec<Pubkey>,
525 pub(super) stakers: Vec<Pubkey>,
526 }
527
528 pub(super) fn create_default_reward_bank(
530 expected_num_delegations: usize,
531 advance_num_slots: u64,
532 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
533 create_reward_bank(
534 expected_num_delegations,
535 PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
536 advance_num_slots,
537 )
538 }
539
540 pub(super) fn create_reward_bank(
541 expected_num_delegations: usize,
542 stake_account_stores_per_block: u64,
543 advance_num_slots: u64,
544 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
545 create_reward_bank_with_specific_stakes(
546 vec![2_000_000_000; expected_num_delegations],
547 stake_account_stores_per_block,
548 advance_num_slots,
549 )
550 }
551
552 pub(super) fn create_reward_bank_with_specific_stakes(
553 stakes: Vec<u64>,
554 stake_account_stores_per_block: u64,
555 advance_num_slots: u64,
556 ) -> (RewardBank, Arc<RwLock<BankForks>>) {
557 let validator_keypairs = (0..stakes.len())
558 .map(|_| ValidatorVoteKeypairs::new_rand())
559 .collect::<Vec<_>>();
560
561 let GenesisConfigInfo {
562 mut genesis_config, ..
563 } = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stakes);
564 genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
565
566 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
567 accounts_db_config.partitioned_epoch_rewards_config =
568 PartitionedEpochRewardsConfig::new_for_test(stake_account_stores_per_block);
569
570 let bank = Bank::new_from_genesis(
571 &genesis_config,
572 Arc::new(RuntimeConfig::default()),
573 Vec::new(),
574 None,
575 accounts_db_config,
576 None,
577 Some(Pubkey::new_unique()),
578 Arc::default(),
579 None,
580 None,
581 );
582
583 populate_vote_accounts_with_votes(
586 &bank,
587 validator_keypairs.iter().map(|k| k.vote_keypair.pubkey()),
588 0,
589 );
590
591 let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
594 let bank = Bank::new_from_parent_with_bank_forks(
595 &bank_forks,
596 bank,
597 &Pubkey::default(),
598 advance_num_slots,
599 );
600
601 (
602 RewardBank {
603 bank,
604 voters: validator_keypairs
605 .iter()
606 .map(|k| k.vote_keypair.pubkey())
607 .collect(),
608 stakers: validator_keypairs
609 .iter()
610 .map(|k| k.stake_keypair.pubkey())
611 .collect(),
612 },
613 bank_forks,
614 )
615 }
616
617 pub(super) fn populate_vote_accounts_with_votes(
618 bank: &Bank,
619 vote_pubkeys: impl IntoIterator<Item = Pubkey>,
620 commission: u8,
621 ) {
622 for vote_pubkey in vote_pubkeys {
623 let mut vote_account = bank
624 .get_account(&vote_pubkey)
625 .unwrap_or_else(|| panic!("missing vote account {vote_pubkey:?}"));
626 let mut vote_state =
627 Some(VoteStateV4::deserialize(vote_account.data(), &vote_pubkey).unwrap());
628 if let Some(state) = vote_state.as_mut() {
629 state.set_commission(commission);
630 }
631 for i in 0..MAX_LOCKOUT_HISTORY + 42 {
632 if let Some(state) = vote_state.as_mut() {
633 vote_state::process_slot_vote_unchecked(state, i as u64);
634 }
635 let versioned = VoteStateVersions::V4(Box::new(vote_state.take().unwrap()));
636 vote_account.set_state(&versioned).unwrap();
637 match versioned {
638 VoteStateVersions::V4(v) => {
639 vote_state = Some(*v);
640 }
641 _ => panic!("Has to be of type V4"),
642 };
643 }
644 bank.store_account_and_update_capitalization(&vote_pubkey, &vote_account);
645 }
646 }
647
648 #[test]
649 fn test_force_reward_interval_end() {
650 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
651 let mut bank = Bank::new_for_tests(&genesis_config);
652
653 let expected_num = 100;
654
655 let stake_rewards = (0..expected_num)
656 .map(|_| Some(PartitionedStakeReward::new_random()))
657 .collect::<PartitionedStakeRewards>();
658
659 let partition_indices = vec![(0..expected_num).collect()];
660
661 bank.set_epoch_reward_status_distribution(
662 bank.block_height() + REWARD_CALCULATION_NUM_BLOCKS,
663 Arc::new(stake_rewards),
664 partition_indices,
665 );
666 assert!(bank.get_reward_interval() == RewardInterval::InsideInterval);
667
668 bank.force_reward_interval_end_for_tests();
669 assert!(bank.get_reward_interval() == RewardInterval::OutsideInterval);
670 }
671
672 #[test]
675 fn test_get_reward_distribution_num_blocks_cap() {
676 let (mut genesis_config, _mint_keypair) =
677 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
678 genesis_config.epoch_schedule = EpochSchedule::custom(32, 32, false);
679
680 let mut accounts_db_config: AccountsDbConfig = ACCOUNTS_DB_CONFIG_FOR_TESTING.clone();
682 accounts_db_config.partitioned_epoch_rewards_config =
683 PartitionedEpochRewardsConfig::new_for_test(10);
684
685 let bank = Bank::new_from_genesis(
686 &genesis_config,
687 Arc::new(RuntimeConfig::default()),
688 Vec::new(),
689 None,
690 accounts_db_config,
691 None,
692 Some(Pubkey::new_unique()),
693 Arc::default(),
694 None,
695 None,
696 );
697
698 let stake_account_stores_per_block =
699 bank.partitioned_rewards_stake_account_stores_per_block();
700 assert_eq!(stake_account_stores_per_block, 10);
701
702 let check_num_reward_distribution_blocks =
703 |num_stakes: u64, expected_num_reward_distribution_blocks: u64| {
704 let stake_rewards = (0..num_stakes)
706 .map(|_| Some(PartitionedStakeReward::new_random()))
707 .collect::<PartitionedStakeRewards>();
708
709 assert_eq!(
710 bank.get_reward_distribution_num_blocks(&stake_rewards),
711 expected_num_reward_distribution_blocks
712 );
713 };
714
715 for test_record in [
716 (0, 1),
718 (1, 1),
719 (stake_account_stores_per_block, 1),
720 (2 * stake_account_stores_per_block - 1, 2),
721 (2 * stake_account_stores_per_block, 2),
722 (3 * stake_account_stores_per_block - 1, 3),
723 (3 * stake_account_stores_per_block, 3),
724 (4 * stake_account_stores_per_block, 3), (5 * stake_account_stores_per_block, 3), ] {
727 check_num_reward_distribution_blocks(test_record.0, test_record.1);
728 }
729 }
730
731 #[test]
733 fn test_get_reward_distribution_num_blocks_normal() {
734 agave_logger::setup();
735 let (mut genesis_config, _mint_keypair) =
736 create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
737 genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
738
739 let bank = Bank::new_for_tests(&genesis_config);
740
741 let expected_num = 8192;
743 let stake_rewards = (0..expected_num)
744 .map(|_| Some(PartitionedStakeReward::new_random()))
745 .collect::<PartitionedStakeRewards>();
746
747 assert_eq!(bank.get_reward_distribution_num_blocks(&stake_rewards), 2);
748 }
749
750 #[test]
753 fn test_get_reward_distribution_num_blocks_warmup() {
754 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
755
756 let bank = Bank::new_for_tests(&genesis_config);
757 let rewards = PartitionedStakeRewards::default();
758 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
759 }
760
761 #[test]
767 fn test_get_reward_distribution_num_blocks_none() {
768 let rewards_all = 8192;
769 let expected_rewards_some = 6144;
770 let rewards = (0..rewards_all)
771 .map(|i| {
772 if i % 4 == 0 {
773 None
774 } else {
775 Some(PartitionedStakeReward::new_random())
776 }
777 })
778 .collect::<PartitionedStakeRewards>();
779 assert_eq!(rewards.rewards.len(), rewards_all);
780 assert_eq!(rewards.num_rewards(), expected_rewards_some);
781
782 let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
783 let bank = Bank::new_for_tests(&genesis_config);
784 assert_eq!(bank.get_reward_distribution_num_blocks(&rewards), 1);
785 }
786
787 #[test]
788 fn test_rewards_computation_and_partitioned_distribution_one_block() {
789 agave_logger::setup();
790
791 let starting_slot = SLOTS_PER_EPOCH - 1;
792 let (
793 RewardBank {
794 bank: mut previous_bank,
795 ..
796 },
797 bank_forks,
798 ) = create_default_reward_bank(100, starting_slot - 1);
799
800 for slot in starting_slot..=(2 * SLOTS_PER_EPOCH) + 2 {
802 let pre_cap = previous_bank.capitalization();
803 let curr_bank = Bank::new_from_parent_with_bank_forks(
804 bank_forks.as_ref(),
805 previous_bank.clone(),
806 &Pubkey::default(),
807 slot,
808 );
809 let post_cap = curr_bank.capitalization();
810
811 if slot % SLOTS_PER_EPOCH == 0 {
812 assert_matches!(
815 curr_bank.get_reward_interval(),
816 RewardInterval::InsideInterval
817 );
818
819 assert!(curr_bank.is_calculated());
820
821 assert!(curr_bank
823 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
824 .is_some());
825 assert_eq!(post_cap, pre_cap);
826
827 let _ = bank_forks.write().unwrap().set_root(slot, None, None);
830 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
831 } else if slot == SLOTS_PER_EPOCH + 1 {
832 assert_matches!(
838 curr_bank.get_reward_interval(),
839 RewardInterval::OutsideInterval
840 );
841 let account = curr_bank
842 .get_account(&solana_sysvar::epoch_rewards::id())
843 .unwrap();
844 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
845 solana_account::from_account(&account).unwrap();
846 assert_eq!(post_cap, pre_cap + epoch_rewards.distributed_rewards);
847 } else {
848 assert_matches!(
853 curr_bank.get_reward_interval(),
854 RewardInterval::OutsideInterval
855 );
856
857 assert_eq!(post_cap, pre_cap);
859 }
860 if slot >= SLOTS_PER_EPOCH {
863 let epoch_rewards_lamports =
864 curr_bank.get_balance(&solana_sysvar::epoch_rewards::id());
865 assert!(epoch_rewards_lamports > 0);
866 }
867 previous_bank = curr_bank;
868 }
869 }
870
871 #[test]
873 fn test_rewards_computation_and_partitioned_distribution_two_blocks() {
874 agave_logger::setup();
875
876 let starting_slot = SLOTS_PER_EPOCH - 1;
877 let (
878 RewardBank {
879 bank: mut previous_bank,
880 ..
881 },
882 bank_forks,
883 ) = create_reward_bank(100, 50, starting_slot - 1);
884 let mut starting_hash = None;
885
886 for slot in starting_slot..=SLOTS_PER_EPOCH + 3 {
888 let pre_cap = previous_bank.capitalization();
889
890 let pre_sysvar_account = previous_bank
891 .get_account(&solana_sysvar::epoch_rewards::id())
892 .unwrap_or_default();
893 let pre_epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
894 solana_account::from_account(&pre_sysvar_account).unwrap_or_default();
895 let pre_distributed_rewards = pre_epoch_rewards.distributed_rewards;
896 let curr_bank = Bank::new_from_parent_with_bank_forks(
897 bank_forks.as_ref(),
898 previous_bank.clone(),
899 &Pubkey::default(),
900 slot,
901 );
902 let post_cap = curr_bank.capitalization();
903
904 if slot == SLOTS_PER_EPOCH {
905 assert_matches!(
908 curr_bank.get_reward_interval(),
909 RewardInterval::InsideInterval
910 );
911
912 assert!(curr_bank.is_calculated());
914
915 assert!(curr_bank
917 .get_epoch_rewards_from_cache(&curr_bank.parent_hash)
918 .is_some());
919 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
920 starting_hash = Some(curr_bank.parent_hash);
921 } else if slot == SLOTS_PER_EPOCH + 1 {
922 assert_matches!(
926 curr_bank.get_reward_interval(),
927 RewardInterval::InsideInterval
928 );
929
930 assert!(curr_bank
933 .get_epoch_rewards_from_cache(&starting_hash.unwrap())
934 .is_some());
935 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 1);
936
937 assert!(curr_bank.is_partitioned());
939
940 let account = curr_bank
941 .get_account(&solana_sysvar::epoch_rewards::id())
942 .unwrap();
943 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
944 solana_account::from_account(&account).unwrap();
945 assert_eq!(
946 post_cap,
947 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
948 );
949
950 let _ = bank_forks.write().unwrap().set_root(slot - 1, None, None);
953 assert_eq!(curr_bank.get_epoch_rewards_cache_len(), 0);
954 } else if slot == SLOTS_PER_EPOCH + 2 {
955 assert_matches!(
961 curr_bank.get_reward_interval(),
962 RewardInterval::OutsideInterval
963 );
964
965 let account = curr_bank
966 .get_account(&solana_sysvar::epoch_rewards::id())
967 .unwrap();
968 let epoch_rewards: solana_sysvar::epoch_rewards::EpochRewards =
969 solana_account::from_account(&account).unwrap();
970 assert_eq!(
971 post_cap,
972 pre_cap + epoch_rewards.distributed_rewards - pre_distributed_rewards
973 );
974 } else {
975 assert_matches!(
980 curr_bank.get_reward_interval(),
981 RewardInterval::OutsideInterval
982 );
983
984 assert_eq!(post_cap, pre_cap);
986 }
987 previous_bank = curr_bank;
988 }
989 }
990
991 #[test]
993 fn test_rewards_period_system_transfer() {
994 let validator_vote_keypairs = ValidatorVoteKeypairs::new_rand();
995 let validator_keypairs = vec![&validator_vote_keypairs];
996 let GenesisConfigInfo {
997 mut genesis_config,
998 mint_keypair,
999 ..
1000 } = create_genesis_config_with_vote_accounts(
1001 1_000_000_000,
1002 &validator_keypairs,
1003 vec![1_000_000_000; 1],
1004 );
1005
1006 let vote_key = validator_keypairs[0].vote_keypair.pubkey();
1008 let vote_account = genesis_config
1009 .accounts
1010 .iter()
1011 .find(|(&address, _)| address == vote_key)
1012 .map(|(_, account)| account)
1013 .unwrap()
1014 .clone();
1015
1016 let new_stake_signer = Keypair::new();
1017 let new_stake_address = new_stake_signer.pubkey();
1018 let new_stake_account = Account::from(stake_utils::create_stake_account(
1019 &new_stake_address,
1020 &vote_key,
1021 &vote_account.into(),
1022 &genesis_config.rent,
1023 2_000_000_000,
1024 ));
1025 genesis_config
1026 .accounts
1027 .extend(vec![(new_stake_address, new_stake_account)]);
1028
1029 let (mut previous_bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
1030 let num_slots_in_epoch = previous_bank.get_slots_in_epoch(previous_bank.epoch());
1031 assert_eq!(num_slots_in_epoch, 32);
1032
1033 let transfer_amount = 5_000;
1034
1035 for slot in 1..=num_slots_in_epoch + 2 {
1036 let bank = Bank::new_from_parent_with_bank_forks(
1037 bank_forks.as_ref(),
1038 previous_bank.clone(),
1039 &Pubkey::default(),
1040 slot,
1041 );
1042
1043 let tower_sync = TowerSync::new_from_slot(slot - 1, previous_bank.hash());
1046 let vote = vote_transaction::new_tower_sync_transaction(
1047 tower_sync,
1048 previous_bank.last_blockhash(),
1049 &validator_vote_keypairs.node_keypair,
1050 &validator_vote_keypairs.vote_keypair,
1051 &validator_vote_keypairs.vote_keypair,
1052 None,
1053 );
1054 bank.process_transaction(&vote).unwrap();
1055
1056 let system_tx = system_transaction::transfer(
1058 &mint_keypair,
1059 &new_stake_address,
1060 transfer_amount,
1061 bank.last_blockhash(),
1062 );
1063 let system_result = bank.process_transaction(&system_tx);
1064
1065 assert!(system_result.is_ok());
1067
1068 bank.register_unique_recent_blockhash_for_test();
1072 previous_bank = bank;
1073 }
1074 }
1075
1076 #[test]
1077 fn test_get_rewards_and_partitions() {
1078 let starting_slot = SLOTS_PER_EPOCH - 1;
1079 let num_rewards = 100;
1080 let stake_account_stores_per_block = 50;
1081 let (RewardBank { bank, .. }, _) =
1082 create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
1083
1084 assert_eq!(
1087 bank.get_rewards_and_num_partitions(),
1088 KeyedRewardsAndNumPartitions {
1089 keyed_rewards: vec![],
1090 num_partitions: None,
1091 }
1092 );
1093
1094 let epoch_boundary_bank = Arc::new(Bank::new_from_parent(
1095 bank,
1096 &Pubkey::default(),
1097 SLOTS_PER_EPOCH,
1098 ));
1099 let KeyedRewardsAndNumPartitions {
1101 keyed_rewards,
1102 num_partitions,
1103 } = epoch_boundary_bank.get_rewards_and_num_partitions();
1104 for (_pubkey, reward) in keyed_rewards.iter() {
1105 assert_eq!(reward.reward_type, RewardType::Voting);
1106 }
1107 assert_eq!(keyed_rewards.len(), num_rewards);
1108 assert_eq!(
1109 num_partitions,
1110 Some(num_rewards as u64 / stake_account_stores_per_block)
1111 );
1112
1113 let mut total_staking_rewards = 0;
1114
1115 let partition0_bank = Arc::new(Bank::new_from_parent(
1116 epoch_boundary_bank,
1117 &Pubkey::default(),
1118 SLOTS_PER_EPOCH + 1,
1119 ));
1120 let KeyedRewardsAndNumPartitions {
1123 keyed_rewards,
1124 num_partitions,
1125 } = partition0_bank.get_rewards_and_num_partitions();
1126 for (_pubkey, reward) in keyed_rewards.iter() {
1127 assert_eq!(reward.reward_type, RewardType::Staking);
1128 }
1129 total_staking_rewards += keyed_rewards.len();
1130 assert_eq!(num_partitions, None);
1131
1132 let partition1_bank = Arc::new(Bank::new_from_parent(
1133 partition0_bank,
1134 &Pubkey::default(),
1135 SLOTS_PER_EPOCH + 2,
1136 ));
1137 let KeyedRewardsAndNumPartitions {
1140 keyed_rewards,
1141 num_partitions,
1142 } = partition1_bank.get_rewards_and_num_partitions();
1143 for (_pubkey, reward) in keyed_rewards.iter() {
1144 assert_eq!(reward.reward_type, RewardType::Staking);
1145 }
1146 total_staking_rewards += keyed_rewards.len();
1147 assert_eq!(num_partitions, None);
1148
1149 assert_eq!(total_staking_rewards, num_rewards);
1151
1152 let bank = Bank::new_from_parent(partition1_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 3);
1153 assert_eq!(
1156 bank.get_rewards_and_num_partitions(),
1157 KeyedRewardsAndNumPartitions {
1158 keyed_rewards: vec![],
1159 num_partitions: None,
1160 }
1161 );
1162 }
1163
1164 #[test]
1165 fn test_rewards_and_partitions_should_record() {
1166 let reward = RewardInfo {
1167 reward_type: RewardType::Voting,
1168 lamports: 55,
1169 post_balance: 5555,
1170 commission: Some(5),
1171 };
1172
1173 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1174 keyed_rewards: vec![],
1175 num_partitions: None,
1176 };
1177 assert!(!rewards_and_partitions.should_record());
1178
1179 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1180 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1181 num_partitions: None,
1182 };
1183 assert!(rewards_and_partitions.should_record());
1184
1185 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1186 keyed_rewards: vec![],
1187 num_partitions: Some(42),
1188 };
1189 assert!(rewards_and_partitions.should_record());
1190
1191 let rewards_and_partitions = KeyedRewardsAndNumPartitions {
1192 keyed_rewards: vec![(Pubkey::new_unique(), reward)],
1193 num_partitions: Some(42),
1194 };
1195 assert!(rewards_and_partitions.should_record());
1196 }
1197}