use {
super::{
epoch_rewards_hasher::hash_rewards_into_partitions, Bank,
CalculateRewardsAndDistributeVoteRewardsResult, CalculateValidatorRewardsResult,
EpochRewardCalculateParamInfo, PartitionedRewardsCalculation, PartitionedStakeReward,
StakeRewardCalculation, VoteRewardsAccounts, VoteRewardsAccountsStorable,
REWARD_CALCULATION_NUM_BLOCKS,
},
crate::{
bank::{
PrevEpochInflationRewards, RewardCalcTracer, RewardCalculationEvent, RewardsMetrics,
VoteReward, VoteRewards,
},
inflation_rewards::{
points::{calculate_points, PointValue},
redeem_rewards,
},
stake_account::StakeAccount,
stakes::Stakes,
},
ahash::random_state::RandomState as AHashRandomState,
dashmap::DashMap,
log::{debug, info},
rayon::{
iter::{IntoParallelRefIterator, ParallelIterator},
ThreadPool,
},
solana_account::ReadableAccount,
solana_clock::{Epoch, Slot},
solana_measure::measure_us,
solana_pubkey::Pubkey,
solana_stake_interface::state::Delegation,
solana_sysvar::epoch_rewards::EpochRewards,
solana_vote::vote_account::VoteAccount,
solana_vote_program::vote_state::VoteStateVersions,
std::sync::{
atomic::{AtomicU64, Ordering::Relaxed},
Arc,
},
};
impl Bank {
pub(in crate::bank) fn begin_partitioned_rewards(
&mut self,
reward_calc_tracer: Option<impl Fn(&RewardCalculationEvent) + Send + Sync>,
thread_pool: &ThreadPool,
parent_epoch: Epoch,
parent_slot: Slot,
parent_block_height: u64,
rewards_metrics: &mut RewardsMetrics,
) {
let CalculateRewardsAndDistributeVoteRewardsResult {
distributed_rewards,
point_value,
stake_rewards,
} = self.calculate_rewards_and_distribute_vote_rewards(
parent_epoch,
reward_calc_tracer,
thread_pool,
rewards_metrics,
);
let slot = self.slot();
let distribution_starting_block_height =
self.block_height() + REWARD_CALCULATION_NUM_BLOCKS;
let num_partitions = self.get_reward_distribution_num_blocks(&stake_rewards);
self.set_epoch_reward_status_calculation(distribution_starting_block_height, stake_rewards);
self.create_epoch_rewards_sysvar(
distributed_rewards,
distribution_starting_block_height,
num_partitions,
point_value,
);
datapoint_info!(
"epoch-rewards-status-update",
("start_slot", slot, i64),
("calculation_block_height", self.block_height(), i64),
("active", 1, i64),
("parent_slot", parent_slot, i64),
("parent_block_height", parent_block_height, i64),
);
}
fn calculate_rewards_and_distribute_vote_rewards(
&self,
prev_epoch: Epoch,
reward_calc_tracer: Option<impl Fn(&RewardCalculationEvent) + Send + Sync>,
thread_pool: &ThreadPool,
metrics: &mut RewardsMetrics,
) -> CalculateRewardsAndDistributeVoteRewardsResult {
let mut epoch_rewards_calculation_cache =
self.epoch_rewards_calculation_cache.lock().unwrap();
let rewards_calculation = epoch_rewards_calculation_cache
.entry(self.parent_hash)
.or_insert_with(|| {
Arc::new(self.calculate_rewards_for_partitioning(
prev_epoch,
reward_calc_tracer,
thread_pool,
metrics,
))
})
.clone();
drop(epoch_rewards_calculation_cache);
let PartitionedRewardsCalculation {
vote_account_rewards,
stake_rewards,
validator_rate,
foundation_rate,
prev_epoch_duration_in_years,
capitalization,
point_value,
} = rewards_calculation.as_ref();
let total_vote_rewards = vote_account_rewards.total_vote_rewards_lamports;
self.store_vote_accounts_partitioned(vote_account_rewards, metrics);
self.update_vote_rewards(vote_account_rewards);
let StakeRewardCalculation {
stake_rewards,
total_stake_rewards_lamports,
} = stake_rewards;
assert!(point_value.rewards >= total_vote_rewards + total_stake_rewards_lamports);
info!(
"distributed vote rewards: {} out of {}, remaining {}",
total_vote_rewards, point_value.rewards, total_stake_rewards_lamports
);
let (num_stake_accounts, num_vote_accounts) = {
let stakes = self.stakes_cache.stakes();
(
stakes.stake_delegations().len(),
stakes.vote_accounts().len(),
)
};
self.capitalization.fetch_add(total_vote_rewards, Relaxed);
let active_stake = if let Some(stake_history_entry) =
self.stakes_cache.stakes().history().get(prev_epoch)
{
stake_history_entry.effective
} else {
0
};
datapoint_info!(
"epoch_rewards",
("slot", self.slot, i64),
("epoch", prev_epoch, i64),
("validator_rate", *validator_rate, f64),
("foundation_rate", *foundation_rate, f64),
(
"epoch_duration_in_years",
*prev_epoch_duration_in_years,
f64
),
("validator_rewards", total_vote_rewards, i64),
("active_stake", active_stake, i64),
("pre_capitalization", *capitalization, i64),
("post_capitalization", self.capitalization(), i64),
("num_stake_accounts", num_stake_accounts, i64),
("num_vote_accounts", num_vote_accounts, i64),
);
CalculateRewardsAndDistributeVoteRewardsResult {
distributed_rewards: total_vote_rewards,
point_value: point_value.clone(),
stake_rewards: Arc::clone(stake_rewards),
}
}
fn store_vote_accounts_partitioned(
&self,
vote_account_rewards: &VoteRewardsAccounts,
metrics: &RewardsMetrics,
) {
let (_, measure_us) = measure_us!({
let storable = VoteRewardsAccountsStorable {
slot: self.slot(),
vote_rewards_accounts: vote_account_rewards,
};
self.store_accounts(storable);
});
metrics
.store_vote_accounts_us
.fetch_add(measure_us, Relaxed);
}
pub(super) fn calculate_rewards_for_partitioning(
&self,
prev_epoch: Epoch,
reward_calc_tracer: Option<impl Fn(&RewardCalculationEvent) + Send + Sync>,
thread_pool: &ThreadPool,
metrics: &mut RewardsMetrics,
) -> PartitionedRewardsCalculation {
let capitalization = self.capitalization();
let PrevEpochInflationRewards {
validator_rewards,
prev_epoch_duration_in_years,
validator_rate,
foundation_rate,
} = self.calculate_previous_epoch_inflation_rewards(capitalization, prev_epoch);
let CalculateValidatorRewardsResult {
vote_rewards_accounts: vote_account_rewards,
stake_reward_calculation: stake_rewards,
point_value,
} = self
.calculate_validator_rewards(
prev_epoch,
validator_rewards,
reward_calc_tracer,
thread_pool,
metrics,
)
.unwrap_or_default();
info!(
"calculated rewards for epoch: {}, parent_slot: {}, parent_hash: {}",
self.epoch, self.parent_slot, self.parent_hash
);
PartitionedRewardsCalculation {
vote_account_rewards,
stake_rewards,
validator_rate,
foundation_rate,
prev_epoch_duration_in_years,
capitalization,
point_value,
}
}
fn calculate_validator_rewards(
&self,
rewarded_epoch: Epoch,
rewards: u64,
reward_calc_tracer: Option<impl RewardCalcTracer>,
thread_pool: &ThreadPool,
metrics: &mut RewardsMetrics,
) -> Option<CalculateValidatorRewardsResult> {
let stakes = self.stakes_cache.stakes();
let reward_calculate_param = self.get_epoch_reward_calculate_param_info(&stakes);
self.calculate_reward_points_partitioned(
&reward_calculate_param,
rewards,
thread_pool,
metrics,
)
.map(|point_value| {
let (vote_rewards_accounts, stake_reward_calculation) = self
.calculate_stake_vote_rewards(
&reward_calculate_param,
rewarded_epoch,
point_value.clone(),
thread_pool,
reward_calc_tracer,
metrics,
);
CalculateValidatorRewardsResult {
vote_rewards_accounts,
stake_reward_calculation,
point_value,
}
})
}
fn get_epoch_reward_calculate_param_info<'a>(
&'a self,
stakes: &'a Stakes<StakeAccount<Delegation>>,
) -> EpochRewardCalculateParamInfo<'a> {
let stake_history = stakes.history().clone();
let stake_delegations = self.filter_stake_delegations(stakes);
let leader_schedule_epoch = self.epoch_schedule().get_leader_schedule_epoch(self.slot());
let cached_vote_accounts = self
.epoch_stakes(leader_schedule_epoch)
.expect(
"calculation should always run after \
Bank::update_epoch_stakes(leader_schedule_epoch)",
)
.stakes()
.vote_accounts();
EpochRewardCalculateParamInfo {
stake_history,
stake_delegations,
cached_vote_accounts,
}
}
fn calculate_stake_vote_rewards(
&self,
reward_calculate_params: &EpochRewardCalculateParamInfo,
rewarded_epoch: Epoch,
point_value: PointValue,
thread_pool: &ThreadPool,
reward_calc_tracer: Option<impl RewardCalcTracer>,
metrics: &mut RewardsMetrics,
) -> (VoteRewardsAccounts, StakeRewardCalculation) {
let EpochRewardCalculateParamInfo {
stake_history,
stake_delegations,
cached_vote_accounts,
} = reward_calculate_params;
let new_warmup_cooldown_rate_epoch = self.new_warmup_cooldown_rate_epoch();
let estimated_num_vote_accounts = cached_vote_accounts.len();
let vote_account_rewards: VoteRewards = DashMap::with_capacity_and_hasher_and_shard_amount(
estimated_num_vote_accounts,
AHashRandomState::default(),
1024, );
let total_stake_rewards = AtomicU64::default();
const ASSERT_STAKE_CACHE: bool = false; let (stake_rewards, measure_stake_rewards_us) = measure_us!(thread_pool.install(|| {
stake_delegations
.par_iter()
.filter_map(|(stake_pubkey, stake_account)| {
let reward_calc_tracer = reward_calc_tracer.as_ref().map(|outer| {
move |inner_event: &_| {
outer(&RewardCalculationEvent::Staking(stake_pubkey, inner_event))
}
});
let stake_pubkey = **stake_pubkey;
let vote_pubkey = stake_account.delegation().voter_pubkey;
let vote_account_from_cache = cached_vote_accounts.get(&vote_pubkey);
if ASSERT_STAKE_CACHE && vote_account_from_cache.is_none() {
let account_from_db = self.get_account_with_fixed_root(&vote_pubkey);
if let Some(account_from_db) = account_from_db {
if VoteStateVersions::is_correct_size_and_initialized(
account_from_db.data(),
) && VoteAccount::try_from(account_from_db.clone()).is_ok()
{
panic!(
"Vote account {vote_pubkey} not found in cache, but found in \
db: {account_from_db:?}"
);
}
}
}
let vote_account = vote_account_from_cache?;
let vote_state_view = vote_account.vote_state_view();
let mut stake_state = *stake_account.stake_state();
let redeemed = redeem_rewards(
rewarded_epoch,
&mut stake_state,
vote_state_view,
&point_value,
stake_history,
reward_calc_tracer.as_ref(),
new_warmup_cooldown_rate_epoch,
);
if let Ok((stakers_reward, voters_reward)) = redeemed {
let commission = vote_state_view.commission();
let mut voters_reward_entry = vote_account_rewards
.entry(vote_pubkey)
.or_insert(VoteReward {
commission,
vote_account: vote_account.into(),
vote_rewards: 0,
});
voters_reward_entry.vote_rewards = voters_reward_entry
.vote_rewards
.saturating_add(voters_reward);
total_stake_rewards.fetch_add(stakers_reward, Relaxed);
let stake = stake_state.stake().unwrap();
return Some(PartitionedStakeReward {
stake_pubkey,
stake_reward: stakers_reward,
stake,
commission,
});
} else {
debug!("redeem_rewards() failed for {stake_pubkey}: {redeemed:?}");
}
None
})
.collect()
}));
let (vote_rewards, measure_vote_rewards_us) =
measure_us!(Self::calc_vote_accounts_to_store(vote_account_rewards));
metrics.redeem_rewards_us += measure_stake_rewards_us + measure_vote_rewards_us;
(
vote_rewards,
StakeRewardCalculation {
stake_rewards: Arc::new(stake_rewards),
total_stake_rewards_lamports: total_stake_rewards.load(Relaxed),
},
)
}
fn calculate_reward_points_partitioned(
&self,
reward_calculate_params: &EpochRewardCalculateParamInfo,
rewards: u64,
thread_pool: &ThreadPool,
metrics: &RewardsMetrics,
) -> Option<PointValue> {
let EpochRewardCalculateParamInfo {
stake_history,
stake_delegations,
cached_vote_accounts,
} = reward_calculate_params;
let atlas_vote_program: Pubkey = solana_vote_program::id();
let new_warmup_cooldown_rate_epoch = self.new_warmup_cooldown_rate_epoch();
let (points, measure_us) = measure_us!(thread_pool.install(|| {
stake_delegations
.par_iter()
.map(|(_stake_pubkey, stake_account)| {
let vote_pubkey = stake_account.delegation().voter_pubkey;
let Some(vote_account) = cached_vote_accounts.get(&vote_pubkey) else {
return 0;
};
if vote_account.owner() != &atlas_vote_program {
return 0;
}
calculate_points(
stake_account.stake_state(),
vote_account.vote_state_view(),
stake_history,
new_warmup_cooldown_rate_epoch,
)
.unwrap_or(0)
})
.sum::<u128>()
}));
metrics.calculate_points_us.fetch_add(measure_us, Relaxed);
(points > 0).then_some(PointValue { rewards, points })
}
pub(in crate::bank) fn recalculate_partitioned_rewards(
&mut self,
reward_calc_tracer: Option<impl RewardCalcTracer>,
thread_pool: &ThreadPool,
) {
let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
if epoch_rewards_sysvar.active {
let (stake_rewards, partition_indices) = self.recalculate_stake_rewards(
&epoch_rewards_sysvar,
reward_calc_tracer,
thread_pool,
);
self.set_epoch_reward_status_distribution(
epoch_rewards_sysvar.distribution_starting_block_height,
stake_rewards,
partition_indices,
);
}
}
fn recalculate_stake_rewards(
&self,
epoch_rewards_sysvar: &EpochRewards,
reward_calc_tracer: Option<impl RewardCalcTracer>,
thread_pool: &ThreadPool,
) -> (Arc<Vec<PartitionedStakeReward>>, Vec<Vec<usize>>) {
assert!(epoch_rewards_sysvar.active);
let rewarded_epoch = self.epoch().saturating_sub(1);
let point_value = PointValue {
rewards: epoch_rewards_sysvar.total_rewards,
points: epoch_rewards_sysvar.total_points,
};
let stakes = self.stakes_cache.stakes();
let reward_calculate_param = self.get_epoch_reward_calculate_param_info(&stakes);
let (_, StakeRewardCalculation { stake_rewards, .. }) = self.calculate_stake_vote_rewards(
&reward_calculate_param,
rewarded_epoch,
point_value,
thread_pool,
reward_calc_tracer,
&mut RewardsMetrics::default(), );
drop(stakes);
let partition_indices = hash_rewards_into_partitions(
&stake_rewards,
&epoch_rewards_sysvar.parent_blockhash,
epoch_rewards_sysvar.num_partitions as usize,
);
(stake_rewards, partition_indices)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
bank::{
null_tracer,
partitioned_epoch_rewards::{
tests::{
build_partitioned_stake_rewards, create_default_reward_bank,
create_reward_bank, create_reward_bank_with_specific_stakes, RewardBank,
SLOTS_PER_EPOCH,
},
EpochRewardPhase, EpochRewardStatus, PartitionedStakeRewards,
StartBlockHeightAndPartitionedRewards,
},
tests::create_genesis_config,
RewardInfo, VoteReward,
},
stake_account::StakeAccount,
stakes::Stakes,
},
rayon::ThreadPoolBuilder,
solana_account::{accounts_equal, state_traits::StateMut, ReadableAccount},
solana_native_token::LAMPORTS_PER_SOL,
solana_reward_info::RewardType,
solana_stake_interface::state::{Delegation, StakeStateV2},
solana_vote_interface::state::VoteStateV3,
std::sync::{Arc, RwLockReadGuard},
};
#[test]
fn test_store_vote_accounts_partitioned() {
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
let bank = Bank::new_for_tests(&genesis_config);
let expected_vote_rewards_num = 100;
let vote_rewards = (0..expected_vote_rewards_num)
.map(|_| (Pubkey::new_unique(), VoteReward::new_random()))
.collect::<Vec<_>>();
let mut vote_rewards_account = VoteRewardsAccounts::default();
vote_rewards
.iter()
.for_each(|(vote_key, vote_reward_info)| {
let info = RewardInfo {
reward_type: RewardType::Voting,
lamports: vote_reward_info.vote_rewards as i64,
post_balance: vote_reward_info.vote_rewards,
commission: Some(vote_reward_info.commission),
};
vote_rewards_account.accounts_with_rewards.push((
*vote_key,
info,
vote_reward_info.vote_account.clone(),
));
vote_rewards_account.total_vote_rewards_lamports += vote_reward_info.vote_rewards;
});
let metrics = RewardsMetrics::default();
let total_vote_rewards = vote_rewards_account.total_vote_rewards_lamports;
bank.store_vote_accounts_partitioned(&vote_rewards_account, &metrics);
assert_eq!(
expected_vote_rewards_num,
vote_rewards_account.accounts_with_rewards.len()
);
assert_eq!(
vote_rewards
.iter()
.map(|(_, vote_reward_info)| vote_reward_info.vote_rewards)
.sum::<u64>(),
total_vote_rewards
);
vote_rewards
.iter()
.for_each(|(vote_key, vote_reward_info)| {
let loaded_account = bank
.load_slow_with_fixed_root(&bank.ancestors, vote_key)
.unwrap();
assert!(accounts_equal(
&loaded_account.0,
&vote_reward_info.vote_account
));
});
}
#[test]
fn test_store_vote_accounts_partitioned_empty() {
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
let bank = Bank::new_for_tests(&genesis_config);
let expected = 0;
let vote_rewards = VoteRewardsAccounts::default();
let metrics = RewardsMetrics::default();
let total_vote_rewards = vote_rewards.total_vote_rewards_lamports;
bank.store_vote_accounts_partitioned(&vote_rewards, &metrics);
assert_eq!(expected, vote_rewards.accounts_with_rewards.len());
assert_eq!(0, total_vote_rewards);
}
#[test]
fn test_rewards_computation() {
solana_logger::setup();
let expected_num_delegations = 100;
let bank = create_default_reward_bank(expected_num_delegations, SLOTS_PER_EPOCH)
.0
.bank;
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let mut rewards_metrics = RewardsMetrics::default();
let expected_rewards = 100_000_000_000;
let calculated_rewards = bank.calculate_validator_rewards(
1,
expected_rewards,
null_tracer(),
&thread_pool,
&mut rewards_metrics,
);
let vote_rewards = &calculated_rewards.as_ref().unwrap().vote_rewards_accounts;
let stake_rewards = &calculated_rewards
.as_ref()
.unwrap()
.stake_reward_calculation;
let total_vote_rewards: u64 = vote_rewards
.accounts_with_rewards
.iter()
.map(|(_, reward_info, _)| reward_info.lamports)
.sum::<i64>() as u64;
assert_eq!(
stake_rewards.total_stake_rewards_lamports + total_vote_rewards,
expected_rewards
);
assert_eq!(stake_rewards.stake_rewards.len(), expected_num_delegations);
}
#[test]
fn test_rewards_point_calculation() {
solana_logger::setup();
let expected_num_delegations = 100;
let RewardBank { bank, .. } =
create_default_reward_bank(expected_num_delegations, SLOTS_PER_EPOCH).0;
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let rewards_metrics = RewardsMetrics::default();
let expected_rewards = 100_000_000_000;
let stakes: RwLockReadGuard<Stakes<StakeAccount<Delegation>>> = bank.stakes_cache.stakes();
let reward_calculate_param = bank.get_epoch_reward_calculate_param_info(&stakes);
let point_value = bank.calculate_reward_points_partitioned(
&reward_calculate_param,
expected_rewards,
&thread_pool,
&rewards_metrics,
);
assert!(point_value.is_some());
assert_eq!(point_value.as_ref().unwrap().rewards, expected_rewards);
assert_eq!(point_value.as_ref().unwrap().points, 8400000000000);
}
#[test]
fn test_rewards_point_calculation_empty() {
solana_logger::setup();
let (genesis_config, _mint_keypair) = create_genesis_config(LAMPORTS_PER_SOL);
let bank = Bank::new_for_tests(&genesis_config);
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let rewards_metrics: RewardsMetrics = RewardsMetrics::default();
let expected_rewards = 100_000_000_000;
let stakes: RwLockReadGuard<Stakes<StakeAccount<Delegation>>> = bank.stakes_cache.stakes();
let reward_calculate_param = bank.get_epoch_reward_calculate_param_info(&stakes);
let point_value = bank.calculate_reward_points_partitioned(
&reward_calculate_param,
expected_rewards,
&thread_pool,
&rewards_metrics,
);
assert!(point_value.is_none());
}
#[test]
fn test_calculate_stake_vote_rewards() {
solana_logger::setup();
let expected_num_delegations = 1;
let RewardBank {
bank,
voters,
stakers,
} = create_default_reward_bank(expected_num_delegations, SLOTS_PER_EPOCH).0;
let vote_pubkey = voters.first().unwrap();
let stake_pubkey = *stakers.first().unwrap();
let stake_account = bank
.load_slow_with_fixed_root(&bank.ancestors, &stake_pubkey)
.unwrap()
.0;
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let mut rewards_metrics = RewardsMetrics::default();
let point_value = PointValue {
rewards: 100000, points: 1000, };
let tracer = |_event: &RewardCalculationEvent| {};
let reward_calc_tracer = Some(tracer);
let rewarded_epoch = bank.epoch();
let stakes: RwLockReadGuard<Stakes<StakeAccount<Delegation>>> = bank.stakes_cache.stakes();
let reward_calculate_param = bank.get_epoch_reward_calculate_param_info(&stakes);
let (vote_rewards_accounts, stake_reward_calculation) = bank.calculate_stake_vote_rewards(
&reward_calculate_param,
rewarded_epoch,
point_value,
&thread_pool,
reward_calc_tracer,
&mut rewards_metrics,
);
let vote_account = bank
.load_slow_with_fixed_root(&bank.ancestors, vote_pubkey)
.unwrap()
.0;
let vote_state = VoteStateV3::deserialize(vote_account.data()).unwrap();
assert_eq!(
vote_rewards_accounts.accounts_with_rewards.len(),
vote_rewards_accounts.accounts_with_rewards.len()
);
assert_eq!(vote_rewards_accounts.accounts_with_rewards.len(), 1);
let (vote_pubkey_from_result, rewards, account) =
&vote_rewards_accounts.accounts_with_rewards[0];
let vote_rewards = 0;
let commission = vote_state.commission;
assert_eq!(account.lamports(), vote_account.lamports());
assert!(accounts_equal(account, &vote_account));
assert_eq!(
*rewards,
RewardInfo {
reward_type: RewardType::Voting,
lamports: vote_rewards as i64,
post_balance: vote_account.lamports(),
commission: Some(commission),
}
);
assert_eq!(vote_pubkey_from_result, vote_pubkey);
assert_eq!(stake_reward_calculation.stake_rewards.len(), 1);
let expected_reward = {
let stake_reward = 8_400_000_000_000;
let stake_state: StakeStateV2 = stake_account.state().unwrap();
let mut stake = stake_state.stake().unwrap();
stake.credits_observed = vote_state.credits();
stake.delegation.stake += stake_reward;
PartitionedStakeReward {
stake,
stake_pubkey,
stake_reward,
commission,
}
};
assert_eq!(stake_reward_calculation.stake_rewards[0], expected_reward);
}
fn compare_stake_rewards(
expected_stake_rewards: &[PartitionedStakeRewards],
received_stake_rewards: &[PartitionedStakeRewards],
) {
for (i, partition) in received_stake_rewards.iter().enumerate() {
let expected_partition = &expected_stake_rewards[i];
assert_eq!(partition.len(), expected_partition.len());
for reward in partition {
assert!(expected_partition.iter().any(|x| x == reward));
}
}
}
#[test]
fn test_recalculate_stake_rewards() {
let expected_num_delegations = 4;
let num_rewards_per_block = 2;
let (RewardBank { bank, .. }, _) = create_reward_bank(
expected_num_delegations,
num_rewards_per_block,
SLOTS_PER_EPOCH,
);
let rewarded_epoch = bank.epoch();
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let mut rewards_metrics = RewardsMetrics::default();
let PartitionedRewardsCalculation {
stake_rewards:
StakeRewardCalculation {
stake_rewards: expected_stake_rewards,
..
},
..
} = bank.calculate_rewards_for_partitioning(
rewarded_epoch,
null_tracer(),
&thread_pool,
&mut rewards_metrics,
);
let epoch_rewards_sysvar = bank.get_epoch_rewards_sysvar();
let (recalculated_rewards, recalculated_partition_indices) =
bank.recalculate_stake_rewards(&epoch_rewards_sysvar, null_tracer(), &thread_pool);
let recalculated_rewards =
build_partitioned_stake_rewards(&recalculated_rewards, &recalculated_partition_indices);
let expected_partition_indices = hash_rewards_into_partitions(
&expected_stake_rewards,
&epoch_rewards_sysvar.parent_blockhash,
epoch_rewards_sysvar.num_partitions as usize,
);
let expected_stake_rewards_partitioned =
build_partitioned_stake_rewards(&expected_stake_rewards, &expected_partition_indices);
assert_eq!(
expected_stake_rewards_partitioned.len(),
recalculated_rewards.len()
);
compare_stake_rewards(&expected_stake_rewards_partitioned, &recalculated_rewards);
let new_slot = bank.slot() + 2;
let bank = Arc::new(Bank::new_from_parent(bank, &Pubkey::default(), new_slot));
let epoch_rewards_sysvar = bank.get_epoch_rewards_sysvar();
let (recalculated_rewards, recalculated_partition_indices) =
bank.recalculate_stake_rewards(&epoch_rewards_sysvar, null_tracer(), &thread_pool);
let recalculated_rewards =
build_partitioned_stake_rewards(&recalculated_rewards, &recalculated_partition_indices);
assert_eq!(
expected_stake_rewards_partitioned.len(),
recalculated_rewards.len()
);
assert_eq!(recalculated_rewards[0].len(), 0);
let starting_index = (bank.block_height() + 1
- epoch_rewards_sysvar.distribution_starting_block_height)
as usize;
compare_stake_rewards(
&expected_stake_rewards_partitioned[starting_index..],
&recalculated_rewards[starting_index..],
);
let new_slot = bank.slot() + 1;
let bank = Arc::new(Bank::new_from_parent(bank, &Pubkey::default(), new_slot));
let epoch_rewards_sysvar = bank.get_epoch_rewards_sysvar();
assert!(!epoch_rewards_sysvar.active);
}
#[test]
#[should_panic]
fn test_recalculate_stake_rewards_distribution_complete() {
let expected_num_delegations = 2;
let num_rewards_per_block = 2;
let (RewardBank { bank, .. }, _) = create_reward_bank(
expected_num_delegations,
num_rewards_per_block,
SLOTS_PER_EPOCH,
);
let rewarded_epoch = bank.epoch();
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let mut rewards_metrics = RewardsMetrics::default();
let PartitionedRewardsCalculation {
stake_rewards:
StakeRewardCalculation {
stake_rewards: expected_stake_rewards,
..
},
..
} = bank.calculate_rewards_for_partitioning(
rewarded_epoch,
null_tracer(),
&thread_pool,
&mut rewards_metrics,
);
let epoch_rewards_sysvar = bank.get_epoch_rewards_sysvar();
let expected_partition_indices = hash_rewards_into_partitions(
&expected_stake_rewards,
&epoch_rewards_sysvar.parent_blockhash,
epoch_rewards_sysvar.num_partitions as usize,
);
let expected_stake_rewards =
build_partitioned_stake_rewards(&expected_stake_rewards, &expected_partition_indices);
let (recalculated_rewards, recalculated_partition_indices) =
bank.recalculate_stake_rewards(&epoch_rewards_sysvar, null_tracer(), &thread_pool);
let recalculated_rewards =
build_partitioned_stake_rewards(&recalculated_rewards, &recalculated_partition_indices);
assert_eq!(expected_stake_rewards.len(), recalculated_rewards.len());
compare_stake_rewards(&expected_stake_rewards, &recalculated_rewards);
let new_slot = bank.slot() + 1;
let bank = Arc::new(Bank::new_from_parent(bank, &Pubkey::default(), new_slot));
let epoch_rewards_sysvar = bank.get_epoch_rewards_sysvar();
assert!(!epoch_rewards_sysvar.active);
let _recalculated_rewards =
bank.recalculate_stake_rewards(&epoch_rewards_sysvar, null_tracer(), &thread_pool);
}
#[test]
fn test_recalculate_partitioned_rewards() {
let expected_num_delegations = 3;
let num_rewards_per_block = 2;
let mut stakes = vec![2_000_000_000; expected_num_delegations];
stakes.push(40_000_000_000);
let (RewardBank { bank, .. }, _) = create_reward_bank_with_specific_stakes(
stakes,
num_rewards_per_block,
SLOTS_PER_EPOCH - 1,
);
let rewarded_epoch = bank.epoch();
let new_slot = bank.slot() + 1;
let mut bank = Bank::new_from_parent(bank, &Pubkey::default(), new_slot);
let expected_starting_block_height = bank.block_height() + 1;
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let mut rewards_metrics = RewardsMetrics::default();
let PartitionedRewardsCalculation {
stake_rewards:
StakeRewardCalculation {
stake_rewards: expected_stake_rewards,
..
},
point_value,
..
} = bank.calculate_rewards_for_partitioning(
rewarded_epoch,
null_tracer(),
&thread_pool,
&mut rewards_metrics,
);
bank.recalculate_partitioned_rewards(null_tracer(), &thread_pool);
let EpochRewardStatus::Active(EpochRewardPhase::Distribution(
StartBlockHeightAndPartitionedRewards {
distribution_starting_block_height,
all_stake_rewards: ref recalculated_rewards,
ref partition_indices,
},
)) = bank.epoch_reward_status
else {
panic!("{:?} not active", bank.epoch_reward_status);
};
assert_eq!(
expected_starting_block_height,
distribution_starting_block_height
);
let recalculated_rewards =
build_partitioned_stake_rewards(recalculated_rewards, partition_indices);
let epoch_rewards_sysvar = bank.get_epoch_rewards_sysvar();
let expected_partition_indices = hash_rewards_into_partitions(
&expected_stake_rewards,
&epoch_rewards_sysvar.parent_blockhash,
epoch_rewards_sysvar.num_partitions as usize,
);
let expected_stake_rewards =
build_partitioned_stake_rewards(&expected_stake_rewards, &expected_partition_indices);
assert_eq!(expected_stake_rewards.len(), recalculated_rewards.len());
compare_stake_rewards(&expected_stake_rewards, &recalculated_rewards);
let sysvar = bank.get_epoch_rewards_sysvar();
assert_eq!(point_value.rewards, sysvar.total_rewards);
let mut bank =
Bank::new_from_parent(Arc::new(bank), &Pubkey::default(), SLOTS_PER_EPOCH + 1);
bank.recalculate_partitioned_rewards(null_tracer(), &thread_pool);
let EpochRewardStatus::Active(EpochRewardPhase::Distribution(
StartBlockHeightAndPartitionedRewards {
distribution_starting_block_height,
all_stake_rewards: ref recalculated_rewards,
ref partition_indices,
},
)) = bank.epoch_reward_status
else {
panic!("{:?} not active", bank.epoch_reward_status);
};
let recalculated_rewards =
build_partitioned_stake_rewards(recalculated_rewards, partition_indices);
assert_eq!(
expected_starting_block_height,
distribution_starting_block_height
);
assert_eq!(expected_stake_rewards.len(), recalculated_rewards.len());
assert_eq!(recalculated_rewards[0].len(), 0);
let epoch_rewards_sysvar = bank.get_epoch_rewards_sysvar();
let starting_index = (bank.block_height() + 1
- epoch_rewards_sysvar.distribution_starting_block_height)
as usize;
compare_stake_rewards(
&expected_stake_rewards[starting_index..],
&recalculated_rewards[starting_index..],
);
let mut bank =
Bank::new_from_parent(Arc::new(bank), &Pubkey::default(), SLOTS_PER_EPOCH + 2);
bank.recalculate_partitioned_rewards(null_tracer(), &thread_pool);
assert_eq!(bank.epoch_reward_status, EpochRewardStatus::Inactive);
}
}