add calculate_reward_points_partitioned (#32050)

* add calculate_reward_points_partitioned

* remove unnecessary clone

* add comment

* clean up test from pr feedback
This commit is contained in:
Jeff Washington (jwash) 2023-06-12 10:36:42 -05:00 committed by GitHub
parent 37efc82a76
commit a001c5eb49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 148 additions and 0 deletions

View File

@ -2878,6 +2878,71 @@ impl Bank {
vote_with_stake_delegations_map
}
/// Calculates epoch reward points from stake/vote accounts.
/// Returns reward lamports and points for the epoch or none if points == 0.
#[allow(dead_code)]
fn calculate_reward_points_partitioned(
&self,
rewards: u64,
thread_pool: &ThreadPool,
metrics: &mut RewardsMetrics,
) -> Option<PointValue> {
let stakes = self.stakes_cache.stakes();
let stake_history = stakes.history().clone();
let stake_delegations = self.filter_stake_delegations(&stakes);
let cached_vote_accounts = stakes.vote_accounts();
let solana_vote_program: Pubkey = solana_vote_program::id();
let get_vote_account = |vote_pubkey: &Pubkey| -> Option<VoteAccount> {
if let Some(vote_account) = cached_vote_accounts.get(vote_pubkey) {
return Some(vote_account.clone());
}
// If accounts-db contains a valid vote account, then it should
// already have been cached in cached_vote_accounts; so the code
// below is only for sanity checking, and can be removed once
// the cache is deemed to be reliable.
let account = self.get_account_with_fixed_root(vote_pubkey)?;
VoteAccount::try_from(account).ok()
};
let (points, measure_us) = measure_us!(thread_pool.install(|| {
stake_delegations
.par_iter()
.map(|(_stake_pubkey, stake_account)| {
let delegation = stake_account.delegation();
let vote_pubkey = delegation.voter_pubkey;
let vote_account = match get_vote_account(&vote_pubkey) {
Some(vote_account) => vote_account,
None => {
return 0;
}
};
if vote_account.owner() != &solana_vote_program {
return 0;
}
let vote_state = match vote_account.vote_state() {
Ok(vote_state) => vote_state,
Err(_) => {
return 0;
}
};
stake_state::calculate_points(
stake_account.stake_state(),
vote_state,
Some(&stake_history),
)
.unwrap_or(0)
})
.sum::<u128>()
}));
metrics.calculate_points_us.fetch_add(measure_us, Relaxed);
(points > 0).then_some(PointValue { rewards, points })
}
fn calculate_reward_points(
&self,
vote_with_stake_delegations_map: &VoteWithStakeDelegationsMap,

View File

@ -12490,6 +12490,89 @@ fn test_squash_timing_add_assign() {
assert!(t0 == expected);
}
/// Helper function to create a bank that pays some rewards
fn create_reward_bank(expected_num_delegations: usize) -> Bank {
let validator_keypairs = (0..expected_num_delegations)
.map(|_| ValidatorVoteKeypairs::new_rand())
.collect::<Vec<_>>();
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config_with_vote_accounts(
1_000_000_000,
&validator_keypairs,
vec![2_000_000_000; expected_num_delegations],
);
let bank = Bank::new_for_tests(&genesis_config);
// Fill bank_forks with banks with votes landing in the next slot
// Create enough banks such that vote account will root
for validator_vote_keypairs in validator_keypairs {
let vote_id = validator_vote_keypairs.vote_keypair.pubkey();
let mut vote_account = bank.get_account(&vote_id).unwrap();
// generate some rewards
let mut vote_state = Some(vote_state::from(&vote_account).unwrap());
for i in 0..MAX_LOCKOUT_HISTORY + 42 {
if let Some(v) = vote_state.as_mut() {
vote_state::process_slot_vote_unchecked(v, i as u64)
}
let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap()));
vote_state::to(&versioned, &mut vote_account).unwrap();
match versioned {
VoteStateVersions::Current(v) => {
vote_state = Some(*v);
}
_ => panic!("Has to be of type Current"),
};
}
bank.store_account_and_update_capitalization(&vote_id, &vote_account);
}
bank
}
#[test]
fn test_rewards_point_calculation() {
solana_logger::setup();
let expected_num_delegations = 100;
let bank = create_reward_bank(expected_num_delegations);
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let mut rewards_metrics = RewardsMetrics::default();
let expected_rewards = 100_000_000_000;
let point_value = bank.calculate_reward_points_partitioned(
expected_rewards,
&thread_pool,
&mut 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();
// bank with no rewards to distribute
let (genesis_config, _mint_keypair) = create_genesis_config(sol_to_lamports(1.0));
let bank = Bank::new_for_tests(&genesis_config);
let thread_pool = ThreadPoolBuilder::new().num_threads(1).build().unwrap();
let mut rewards_metrics: RewardsMetrics = RewardsMetrics::default();
let expected_rewards = 100_000_000_000;
let point_value = bank.calculate_reward_points_partitioned(
expected_rewards,
&thread_pool,
&mut rewards_metrics,
);
assert!(point_value.is_none());
}
/// Test reward computation at epoch boundary
#[test]
fn test_system_instruction_allocate() {
let (genesis_config, mint_keypair) = create_genesis_config(sol_to_lamports(1.0));