Move distribution methods; partitioned epoch-rewards reorg, 3 of 5 (#528)
* Add distribution sub-submodule * Move distribution methods to sub-submodule * Move unit tests into distribution sub-submodule
This commit is contained in:
parent
64260fc831
commit
5d53389fe4
|
@ -1518,47 +1518,6 @@ impl Bank {
|
|||
);
|
||||
}
|
||||
|
||||
/// Process reward distribution for the block if it is inside reward interval.
|
||||
fn distribute_partitioned_epoch_rewards(&mut self) {
|
||||
let EpochRewardStatus::Active(status) = &self.epoch_reward_status else {
|
||||
return;
|
||||
};
|
||||
|
||||
let height = self.block_height();
|
||||
let start_block_height = status.start_block_height;
|
||||
let credit_start = start_block_height + self.get_reward_calculation_num_blocks();
|
||||
let credit_end_exclusive = credit_start + status.stake_rewards_by_partition.len() as u64;
|
||||
assert!(
|
||||
self.epoch_schedule.get_slots_in_epoch(self.epoch)
|
||||
> credit_end_exclusive.saturating_sub(credit_start)
|
||||
);
|
||||
|
||||
if height >= credit_start && height < credit_end_exclusive {
|
||||
let partition_index = height - credit_start;
|
||||
self.distribute_epoch_rewards_in_partition(
|
||||
&status.stake_rewards_by_partition,
|
||||
partition_index,
|
||||
);
|
||||
}
|
||||
|
||||
if height.saturating_add(1) >= credit_end_exclusive {
|
||||
datapoint_info!(
|
||||
"epoch-rewards-status-update",
|
||||
("slot", self.slot(), i64),
|
||||
("block_height", height, i64),
|
||||
("active", 0, i64),
|
||||
("start_block_height", start_block_height, i64),
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
self.epoch_reward_status,
|
||||
EpochRewardStatus::Active(_)
|
||||
));
|
||||
self.epoch_reward_status = EpochRewardStatus::Inactive;
|
||||
self.destroy_epoch_rewards_sysvar();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn byte_limit_for_scans(&self) -> Option<usize> {
|
||||
self.rc
|
||||
.accounts
|
||||
|
@ -3208,39 +3167,6 @@ impl Bank {
|
|||
.fetch_add(now.elapsed().as_micros() as u64, Relaxed);
|
||||
}
|
||||
|
||||
/// store stake rewards in partition
|
||||
/// return the sum of all the stored rewards
|
||||
///
|
||||
/// Note: even if staker's reward is 0, the stake account still needs to be stored because
|
||||
/// credits observed has changed
|
||||
fn store_stake_accounts_in_partition(&self, stake_rewards: &[StakeReward]) -> u64 {
|
||||
// Verify that stake account `lamports + reward_amount` matches what we have in the
|
||||
// rewarded account. This code will have a performance hit - an extra load and compare of
|
||||
// the stake accounts. This is for debugging. Once we are confident, we can disable the
|
||||
// check.
|
||||
const VERIFY_REWARD_LAMPORT: bool = true;
|
||||
|
||||
if VERIFY_REWARD_LAMPORT {
|
||||
for r in stake_rewards {
|
||||
let stake_pubkey = r.stake_pubkey;
|
||||
let reward_amount = r.get_stake_reward();
|
||||
let post_stake_account = &r.stake_account;
|
||||
if let Some(curr_stake_account) = self.get_account_with_fixed_root(&stake_pubkey) {
|
||||
let pre_lamport = curr_stake_account.lamports();
|
||||
let post_lamport = post_stake_account.lamports();
|
||||
assert_eq!(pre_lamport + u64::try_from(reward_amount).unwrap(), post_lamport,
|
||||
"stake account balance has changed since the reward calculation! account: {stake_pubkey}, pre balance: {pre_lamport}, post balance: {post_lamport}, rewards: {reward_amount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.store_accounts((self.slot(), stake_rewards));
|
||||
stake_rewards
|
||||
.iter()
|
||||
.map(|stake_reward| stake_reward.stake_reward_info.lamports)
|
||||
.sum::<i64>() as u64
|
||||
}
|
||||
|
||||
fn store_vote_accounts_partitioned(
|
||||
&self,
|
||||
vote_account_rewards: VoteRewardsAccounts,
|
||||
|
@ -3375,55 +3301,6 @@ impl Bank {
|
|||
.for_each(|x| rewards.push((x.stake_pubkey, x.stake_reward_info)));
|
||||
}
|
||||
|
||||
/// insert non-zero stake rewards to self.rewards
|
||||
/// Return the number of rewards inserted
|
||||
fn update_reward_history_in_partition(&self, stake_rewards: &[StakeReward]) -> usize {
|
||||
let mut rewards = self.rewards.write().unwrap();
|
||||
rewards.reserve(stake_rewards.len());
|
||||
let initial_len = rewards.len();
|
||||
stake_rewards
|
||||
.iter()
|
||||
.filter(|x| x.get_stake_reward() > 0)
|
||||
.for_each(|x| rewards.push((x.stake_pubkey, x.stake_reward_info)));
|
||||
rewards.len().saturating_sub(initial_len)
|
||||
}
|
||||
|
||||
/// Process reward credits for a partition of rewards
|
||||
/// Store the rewards to AccountsDB, update reward history record and total capitalization.
|
||||
fn distribute_epoch_rewards_in_partition(
|
||||
&self,
|
||||
all_stake_rewards: &[Vec<StakeReward>],
|
||||
partition_index: u64,
|
||||
) {
|
||||
let pre_capitalization = self.capitalization();
|
||||
let this_partition_stake_rewards = &all_stake_rewards[partition_index as usize];
|
||||
|
||||
let (total_rewards_in_lamports, store_stake_accounts_us) =
|
||||
measure_us!(self.store_stake_accounts_in_partition(this_partition_stake_rewards));
|
||||
|
||||
// increase total capitalization by the distributed rewards
|
||||
self.capitalization
|
||||
.fetch_add(total_rewards_in_lamports, Relaxed);
|
||||
|
||||
// decrease distributed capital from epoch rewards sysvar
|
||||
self.update_epoch_rewards_sysvar(total_rewards_in_lamports);
|
||||
|
||||
// update reward history for this partitioned distribution
|
||||
self.update_reward_history_in_partition(this_partition_stake_rewards);
|
||||
|
||||
let metrics = RewardsStoreMetrics {
|
||||
pre_capitalization,
|
||||
post_capitalization: self.capitalization(),
|
||||
total_stake_accounts_count: all_stake_rewards.len(),
|
||||
partition_index,
|
||||
store_stake_accounts_us,
|
||||
store_stake_accounts_count: this_partition_stake_rewards.len(),
|
||||
distributed_rewards: total_rewards_in_lamports,
|
||||
};
|
||||
|
||||
report_partitioned_reward_metrics(self, metrics);
|
||||
}
|
||||
|
||||
fn update_recent_blockhashes_locked(&self, locked_blockhash_queue: &BlockhashQueue) {
|
||||
#[allow(deprecated)]
|
||||
self.update_sysvar_account(&sysvar::recent_blockhashes::id(), |account| {
|
||||
|
|
|
@ -0,0 +1,395 @@
|
|||
use {
|
||||
super::{Bank, EpochRewardStatus},
|
||||
crate::bank::metrics::{report_partitioned_reward_metrics, RewardsStoreMetrics},
|
||||
solana_accounts_db::stake_rewards::StakeReward,
|
||||
solana_measure::measure_us,
|
||||
solana_sdk::account::ReadableAccount,
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
};
|
||||
|
||||
impl Bank {
|
||||
/// Process reward distribution for the block if it is inside reward interval.
|
||||
pub(in crate::bank) fn distribute_partitioned_epoch_rewards(&mut self) {
|
||||
let EpochRewardStatus::Active(status) = &self.epoch_reward_status else {
|
||||
return;
|
||||
};
|
||||
|
||||
let height = self.block_height();
|
||||
let start_block_height = status.start_block_height;
|
||||
let credit_start = start_block_height + self.get_reward_calculation_num_blocks();
|
||||
let credit_end_exclusive = credit_start + status.stake_rewards_by_partition.len() as u64;
|
||||
assert!(
|
||||
self.epoch_schedule.get_slots_in_epoch(self.epoch)
|
||||
> credit_end_exclusive.saturating_sub(credit_start)
|
||||
);
|
||||
|
||||
if height >= credit_start && height < credit_end_exclusive {
|
||||
let partition_index = height - credit_start;
|
||||
self.distribute_epoch_rewards_in_partition(
|
||||
&status.stake_rewards_by_partition,
|
||||
partition_index,
|
||||
);
|
||||
}
|
||||
|
||||
if height.saturating_add(1) >= credit_end_exclusive {
|
||||
datapoint_info!(
|
||||
"epoch-rewards-status-update",
|
||||
("slot", self.slot(), i64),
|
||||
("block_height", height, i64),
|
||||
("active", 0, i64),
|
||||
("start_block_height", start_block_height, i64),
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
self.epoch_reward_status,
|
||||
EpochRewardStatus::Active(_)
|
||||
));
|
||||
self.epoch_reward_status = EpochRewardStatus::Inactive;
|
||||
self.destroy_epoch_rewards_sysvar();
|
||||
}
|
||||
}
|
||||
|
||||
/// Process reward credits for a partition of rewards
|
||||
/// Store the rewards to AccountsDB, update reward history record and total capitalization.
|
||||
fn distribute_epoch_rewards_in_partition(
|
||||
&self,
|
||||
all_stake_rewards: &[Vec<StakeReward>],
|
||||
partition_index: u64,
|
||||
) {
|
||||
let pre_capitalization = self.capitalization();
|
||||
let this_partition_stake_rewards = &all_stake_rewards[partition_index as usize];
|
||||
|
||||
let (total_rewards_in_lamports, store_stake_accounts_us) =
|
||||
measure_us!(self.store_stake_accounts_in_partition(this_partition_stake_rewards));
|
||||
|
||||
// increase total capitalization by the distributed rewards
|
||||
self.capitalization
|
||||
.fetch_add(total_rewards_in_lamports, Relaxed);
|
||||
|
||||
// decrease distributed capital from epoch rewards sysvar
|
||||
self.update_epoch_rewards_sysvar(total_rewards_in_lamports);
|
||||
|
||||
// update reward history for this partitioned distribution
|
||||
self.update_reward_history_in_partition(this_partition_stake_rewards);
|
||||
|
||||
let metrics = RewardsStoreMetrics {
|
||||
pre_capitalization,
|
||||
post_capitalization: self.capitalization(),
|
||||
total_stake_accounts_count: all_stake_rewards.len(),
|
||||
partition_index,
|
||||
store_stake_accounts_us,
|
||||
store_stake_accounts_count: this_partition_stake_rewards.len(),
|
||||
distributed_rewards: total_rewards_in_lamports,
|
||||
};
|
||||
|
||||
report_partitioned_reward_metrics(self, metrics);
|
||||
}
|
||||
|
||||
/// insert non-zero stake rewards to self.rewards
|
||||
/// Return the number of rewards inserted
|
||||
fn update_reward_history_in_partition(&self, stake_rewards: &[StakeReward]) -> usize {
|
||||
let mut rewards = self.rewards.write().unwrap();
|
||||
rewards.reserve(stake_rewards.len());
|
||||
let initial_len = rewards.len();
|
||||
stake_rewards
|
||||
.iter()
|
||||
.filter(|x| x.get_stake_reward() > 0)
|
||||
.for_each(|x| rewards.push((x.stake_pubkey, x.stake_reward_info)));
|
||||
rewards.len().saturating_sub(initial_len)
|
||||
}
|
||||
|
||||
/// store stake rewards in partition
|
||||
/// return the sum of all the stored rewards
|
||||
///
|
||||
/// Note: even if staker's reward is 0, the stake account still needs to be stored because
|
||||
/// credits observed has changed
|
||||
fn store_stake_accounts_in_partition(&self, stake_rewards: &[StakeReward]) -> u64 {
|
||||
// Verify that stake account `lamports + reward_amount` matches what we have in the
|
||||
// rewarded account. This code will have a performance hit - an extra load and compare of
|
||||
// the stake accounts. This is for debugging. Once we are confident, we can disable the
|
||||
// check.
|
||||
const VERIFY_REWARD_LAMPORT: bool = true;
|
||||
|
||||
if VERIFY_REWARD_LAMPORT {
|
||||
for r in stake_rewards {
|
||||
let stake_pubkey = r.stake_pubkey;
|
||||
let reward_amount = r.get_stake_reward();
|
||||
let post_stake_account = &r.stake_account;
|
||||
if let Some(curr_stake_account) = self.get_account_with_fixed_root(&stake_pubkey) {
|
||||
let pre_lamport = curr_stake_account.lamports();
|
||||
let post_lamport = post_stake_account.lamports();
|
||||
assert_eq!(pre_lamport + u64::try_from(reward_amount).unwrap(), post_lamport,
|
||||
"stake account balance has changed since the reward calculation! account: {stake_pubkey}, pre balance: {pre_lamport}, post balance: {post_lamport}, rewards: {reward_amount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.store_accounts((self.slot(), stake_rewards));
|
||||
stake_rewards
|
||||
.iter()
|
||||
.map(|stake_reward| stake_reward.stake_reward_info.lamports)
|
||||
.sum::<i64>() as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
super::*,
|
||||
crate::{
|
||||
bank::tests::create_genesis_config, epoch_rewards_hasher::hash_rewards_into_partitions,
|
||||
},
|
||||
rand::Rng,
|
||||
solana_sdk::{
|
||||
account::from_account, epoch_schedule::EpochSchedule, feature_set, hash::Hash,
|
||||
native_token::LAMPORTS_PER_SOL, sysvar,
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_distribute_partitioned_epoch_rewards() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let expected_num = 100;
|
||||
|
||||
let stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let stake_rewards = hash_rewards_into_partitions(stake_rewards, &Hash::new(&[1; 32]), 2);
|
||||
|
||||
bank.set_epoch_reward_status_active(stake_rewards);
|
||||
|
||||
bank.distribute_partitioned_epoch_rewards();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "self.epoch_schedule.get_slots_in_epoch")]
|
||||
fn test_distribute_partitioned_epoch_rewards_too_many_partitions() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let expected_num = 1;
|
||||
|
||||
let stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let stake_rewards = hash_rewards_into_partitions(
|
||||
stake_rewards,
|
||||
&Hash::new(&[1; 32]),
|
||||
bank.epoch_schedule().slots_per_epoch as usize + 1,
|
||||
);
|
||||
|
||||
bank.set_epoch_reward_status_active(stake_rewards);
|
||||
|
||||
bank.distribute_partitioned_epoch_rewards();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_distribute_partitioned_epoch_rewards_empty() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
bank.set_epoch_reward_status_active(vec![]);
|
||||
|
||||
bank.distribute_partitioned_epoch_rewards();
|
||||
}
|
||||
|
||||
/// Test distribute partitioned epoch rewards
|
||||
#[test]
|
||||
fn test_distribute_partitioned_epoch_rewards_bank_capital_and_sysvar_balance() {
|
||||
let (mut genesis_config, _mint_keypair) =
|
||||
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
bank.activate_feature(&feature_set::enable_partitioned_epoch_reward::id());
|
||||
|
||||
// Set up epoch_rewards sysvar with rewards with 1e9 lamports to distribute.
|
||||
let total_rewards = 1_000_000_000;
|
||||
bank.create_epoch_rewards_sysvar(total_rewards, 0, 42);
|
||||
let pre_epoch_rewards_account = bank.get_account(&sysvar::epoch_rewards::id()).unwrap();
|
||||
assert_eq!(pre_epoch_rewards_account.lamports(), total_rewards);
|
||||
|
||||
// Set up a partition of rewards to distribute
|
||||
let expected_num = 100;
|
||||
let mut stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
let mut rewards_to_distribute = 0;
|
||||
for stake_reward in &mut stake_rewards {
|
||||
stake_reward.credit(100);
|
||||
rewards_to_distribute += 100;
|
||||
}
|
||||
let all_rewards = vec![stake_rewards];
|
||||
|
||||
// Distribute rewards
|
||||
let pre_cap = bank.capitalization();
|
||||
bank.distribute_epoch_rewards_in_partition(&all_rewards, 0);
|
||||
let post_cap = bank.capitalization();
|
||||
let post_epoch_rewards_account = bank.get_account(&sysvar::epoch_rewards::id()).unwrap();
|
||||
let expected_epoch_rewards_sysvar_lamports_remaining =
|
||||
total_rewards - rewards_to_distribute;
|
||||
|
||||
// Assert that epoch rewards sysvar lamports decreases by the distributed rewards
|
||||
assert_eq!(
|
||||
post_epoch_rewards_account.lamports(),
|
||||
expected_epoch_rewards_sysvar_lamports_remaining
|
||||
);
|
||||
|
||||
let epoch_rewards: sysvar::epoch_rewards::EpochRewards =
|
||||
from_account(&post_epoch_rewards_account).unwrap();
|
||||
assert_eq!(epoch_rewards.total_rewards, total_rewards);
|
||||
assert_eq!(epoch_rewards.distributed_rewards, rewards_to_distribute,);
|
||||
|
||||
// Assert that the bank total capital didn't change
|
||||
assert_eq!(pre_cap, post_cap);
|
||||
}
|
||||
|
||||
/// Test partitioned credits and reward history updates of epoch rewards do cover all the rewards
|
||||
/// slice.
|
||||
#[test]
|
||||
fn test_epoch_credit_rewards_and_history_update() {
|
||||
let (mut genesis_config, _mint_keypair) =
|
||||
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
// setup the expected number of stake rewards
|
||||
let expected_num = 12345;
|
||||
|
||||
let mut stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
bank.store_accounts((bank.slot(), &stake_rewards[..]));
|
||||
|
||||
// Simulate rewards
|
||||
let mut expected_rewards = 0;
|
||||
for stake_reward in &mut stake_rewards {
|
||||
stake_reward.credit(1);
|
||||
expected_rewards += 1;
|
||||
}
|
||||
|
||||
let stake_rewards_bucket =
|
||||
hash_rewards_into_partitions(stake_rewards, &Hash::new(&[1; 32]), 100);
|
||||
bank.set_epoch_reward_status_active(stake_rewards_bucket.clone());
|
||||
|
||||
// Test partitioned stores
|
||||
let mut total_rewards = 0;
|
||||
let mut total_num_updates = 0;
|
||||
|
||||
let pre_update_history_len = bank.rewards.read().unwrap().len();
|
||||
|
||||
for stake_rewards in stake_rewards_bucket {
|
||||
let total_rewards_in_lamports = bank.store_stake_accounts_in_partition(&stake_rewards);
|
||||
let num_history_updates = bank.update_reward_history_in_partition(&stake_rewards);
|
||||
assert_eq!(stake_rewards.len(), num_history_updates);
|
||||
total_rewards += total_rewards_in_lamports;
|
||||
total_num_updates += num_history_updates;
|
||||
}
|
||||
|
||||
let post_update_history_len = bank.rewards.read().unwrap().len();
|
||||
|
||||
// assert that all rewards are credited
|
||||
assert_eq!(total_rewards, expected_rewards);
|
||||
assert_eq!(total_num_updates, expected_num);
|
||||
assert_eq!(
|
||||
total_num_updates,
|
||||
post_update_history_len - pre_update_history_len
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_reward_history_in_partition() {
|
||||
for zero_reward in [false, true] {
|
||||
let (genesis_config, _mint_keypair) =
|
||||
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let mut expected_num = 100;
|
||||
|
||||
let mut stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let i_zero = rng.gen_range(0..expected_num);
|
||||
if zero_reward {
|
||||
// pick one entry to have zero rewards so it gets ignored
|
||||
stake_rewards[i_zero].stake_reward_info.lamports = 0;
|
||||
}
|
||||
|
||||
let num_in_history = bank.update_reward_history_in_partition(&stake_rewards);
|
||||
|
||||
if zero_reward {
|
||||
stake_rewards.remove(i_zero);
|
||||
// -1 because one of them had zero rewards and was ignored
|
||||
expected_num -= 1;
|
||||
}
|
||||
|
||||
bank.rewards
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.zip(stake_rewards.iter())
|
||||
.for_each(|((k, reward_info), expected_stake_reward)| {
|
||||
assert_eq!(
|
||||
(
|
||||
&expected_stake_reward.stake_pubkey,
|
||||
&expected_stake_reward.stake_reward_info
|
||||
),
|
||||
(k, reward_info)
|
||||
);
|
||||
});
|
||||
|
||||
assert_eq!(num_in_history, expected_num);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_reward_history_in_partition_empty() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let stake_rewards = vec![];
|
||||
|
||||
let num_in_history = bank.update_reward_history_in_partition(&stake_rewards);
|
||||
assert_eq!(num_in_history, 0);
|
||||
}
|
||||
|
||||
/// Test rewards computation and partitioned rewards distribution at the epoch boundary
|
||||
#[test]
|
||||
fn test_store_stake_accounts_in_partition() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let expected_num = 100;
|
||||
|
||||
let stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let expected_total = stake_rewards
|
||||
.iter()
|
||||
.map(|stake_reward| stake_reward.stake_reward_info.lamports)
|
||||
.sum::<i64>() as u64;
|
||||
|
||||
let total_rewards_in_lamports = bank.store_stake_accounts_in_partition(&stake_rewards);
|
||||
assert_eq!(expected_total, total_rewards_in_lamports);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_stake_accounts_in_partition_empty() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let stake_rewards = vec![];
|
||||
|
||||
let expected_total = 0;
|
||||
|
||||
let total_rewards_in_lamports = bank.store_stake_accounts_in_partition(&stake_rewards);
|
||||
assert_eq!(expected_total, total_rewards_in_lamports);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod distribution;
|
||||
mod sysvar;
|
||||
|
||||
use {
|
||||
|
|
|
@ -55,7 +55,10 @@ impl Bank {
|
|||
}
|
||||
|
||||
/// Update EpochRewards sysvar with distributed rewards
|
||||
pub(in crate::bank) fn update_epoch_rewards_sysvar(&self, distributed: u64) {
|
||||
pub(in crate::bank::partitioned_epoch_rewards) fn update_epoch_rewards_sysvar(
|
||||
&self,
|
||||
distributed: u64,
|
||||
) {
|
||||
assert!(self.is_partitioned_rewards_code_enabled());
|
||||
|
||||
let mut epoch_rewards: sysvar::epoch_rewards::EpochRewards =
|
||||
|
@ -75,7 +78,7 @@ impl Bank {
|
|||
self.log_epoch_rewards_sysvar("update");
|
||||
}
|
||||
|
||||
pub(in crate::bank) fn destroy_epoch_rewards_sysvar(&self) {
|
||||
pub(in crate::bank::partitioned_epoch_rewards) fn destroy_epoch_rewards_sysvar(&self) {
|
||||
if let Some(account) = self.get_account(&sysvar::epoch_rewards::id()) {
|
||||
if account.lamports() > 0 {
|
||||
info!(
|
||||
|
|
|
@ -12248,158 +12248,6 @@ fn test_get_stake_rewards_partition_range_panic() {
|
|||
let _range = &stake_rewards_bucket[15];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_distribute_partitioned_epoch_rewards() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let expected_num = 100;
|
||||
|
||||
let stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let stake_rewards = hash_rewards_into_partitions(stake_rewards, &Hash::new(&[1; 32]), 2);
|
||||
|
||||
bank.set_epoch_reward_status_active(stake_rewards);
|
||||
|
||||
bank.distribute_partitioned_epoch_rewards();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "self.epoch_schedule.get_slots_in_epoch")]
|
||||
fn test_distribute_partitioned_epoch_rewards_too_many_partitions() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let expected_num = 1;
|
||||
|
||||
let stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let stake_rewards = hash_rewards_into_partitions(
|
||||
stake_rewards,
|
||||
&Hash::new(&[1; 32]),
|
||||
bank.epoch_schedule().slots_per_epoch as usize + 1,
|
||||
);
|
||||
|
||||
bank.set_epoch_reward_status_active(stake_rewards);
|
||||
|
||||
bank.distribute_partitioned_epoch_rewards();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_distribute_partitioned_epoch_rewards_empty() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
bank.set_epoch_reward_status_active(vec![]);
|
||||
|
||||
bank.distribute_partitioned_epoch_rewards();
|
||||
}
|
||||
|
||||
/// Test partitioned credits and reward history updates of epoch rewards do cover all the rewards
|
||||
/// slice.
|
||||
#[test]
|
||||
fn test_epoch_credit_rewards_and_history_update() {
|
||||
let (mut genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
// setup the expected number of stake rewards
|
||||
let expected_num = 12345;
|
||||
|
||||
let mut stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
bank.store_accounts((bank.slot(), &stake_rewards[..]));
|
||||
|
||||
// Simulate rewards
|
||||
let mut expected_rewards = 0;
|
||||
for stake_reward in &mut stake_rewards {
|
||||
stake_reward.credit(1);
|
||||
expected_rewards += 1;
|
||||
}
|
||||
|
||||
let stake_rewards_bucket =
|
||||
hash_rewards_into_partitions(stake_rewards, &Hash::new(&[1; 32]), 100);
|
||||
bank.set_epoch_reward_status_active(stake_rewards_bucket.clone());
|
||||
|
||||
// Test partitioned stores
|
||||
let mut total_rewards = 0;
|
||||
let mut total_num_updates = 0;
|
||||
|
||||
let pre_update_history_len = bank.rewards.read().unwrap().len();
|
||||
|
||||
for stake_rewards in stake_rewards_bucket {
|
||||
let total_rewards_in_lamports = bank.store_stake_accounts_in_partition(&stake_rewards);
|
||||
let num_history_updates = bank.update_reward_history_in_partition(&stake_rewards);
|
||||
assert_eq!(stake_rewards.len(), num_history_updates);
|
||||
total_rewards += total_rewards_in_lamports;
|
||||
total_num_updates += num_history_updates;
|
||||
}
|
||||
|
||||
let post_update_history_len = bank.rewards.read().unwrap().len();
|
||||
|
||||
// assert that all rewards are credited
|
||||
assert_eq!(total_rewards, expected_rewards);
|
||||
assert_eq!(total_num_updates, expected_num);
|
||||
assert_eq!(
|
||||
total_num_updates,
|
||||
post_update_history_len - pre_update_history_len
|
||||
);
|
||||
}
|
||||
|
||||
/// Test distribute partitioned epoch rewards
|
||||
#[test]
|
||||
fn test_distribute_partitioned_epoch_rewards_bank_capital_and_sysvar_balance() {
|
||||
let (mut genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
genesis_config.epoch_schedule = EpochSchedule::custom(432000, 432000, false);
|
||||
let mut bank = Bank::new_for_tests(&genesis_config);
|
||||
bank.activate_feature(&feature_set::enable_partitioned_epoch_reward::id());
|
||||
|
||||
// Set up epoch_rewards sysvar with rewards with 1e9 lamports to distribute.
|
||||
let total_rewards = 1_000_000_000;
|
||||
bank.create_epoch_rewards_sysvar(total_rewards, 0, 42);
|
||||
let pre_epoch_rewards_account = bank.get_account(&sysvar::epoch_rewards::id()).unwrap();
|
||||
assert_eq!(pre_epoch_rewards_account.lamports(), total_rewards);
|
||||
|
||||
// Set up a partition of rewards to distribute
|
||||
let expected_num = 100;
|
||||
let mut stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
let mut rewards_to_distribute = 0;
|
||||
for stake_reward in &mut stake_rewards {
|
||||
stake_reward.credit(100);
|
||||
rewards_to_distribute += 100;
|
||||
}
|
||||
let all_rewards = vec![stake_rewards];
|
||||
|
||||
// Distribute rewards
|
||||
let pre_cap = bank.capitalization();
|
||||
bank.distribute_epoch_rewards_in_partition(&all_rewards, 0);
|
||||
let post_cap = bank.capitalization();
|
||||
let post_epoch_rewards_account = bank.get_account(&sysvar::epoch_rewards::id()).unwrap();
|
||||
let expected_epoch_rewards_sysvar_lamports_remaining = total_rewards - rewards_to_distribute;
|
||||
|
||||
// Assert that epoch rewards sysvar lamports decreases by the distributed rewards
|
||||
assert_eq!(
|
||||
post_epoch_rewards_account.lamports(),
|
||||
expected_epoch_rewards_sysvar_lamports_remaining
|
||||
);
|
||||
|
||||
let epoch_rewards: sysvar::epoch_rewards::EpochRewards =
|
||||
from_account(&post_epoch_rewards_account).unwrap();
|
||||
assert_eq!(epoch_rewards.total_rewards, total_rewards);
|
||||
assert_eq!(epoch_rewards.distributed_rewards, rewards_to_distribute,);
|
||||
|
||||
// Assert that the bank total capital didn't change
|
||||
assert_eq!(pre_cap, post_cap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Test rewards computation and partitioned rewards distribution at the epoch boundary
|
||||
fn test_rewards_computation() {
|
||||
|
@ -12718,97 +12566,6 @@ fn test_program_execution_restricted_for_stake_account_in_reward_period() {
|
|||
}
|
||||
}
|
||||
|
||||
/// Test rewards computation and partitioned rewards distribution at the epoch boundary
|
||||
#[test]
|
||||
fn test_store_stake_accounts_in_partition() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let expected_num = 100;
|
||||
|
||||
let stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let expected_total = stake_rewards
|
||||
.iter()
|
||||
.map(|stake_reward| stake_reward.stake_reward_info.lamports)
|
||||
.sum::<i64>() as u64;
|
||||
|
||||
let total_rewards_in_lamports = bank.store_stake_accounts_in_partition(&stake_rewards);
|
||||
assert_eq!(expected_total, total_rewards_in_lamports);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_stake_accounts_in_partition_empty() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let stake_rewards = vec![];
|
||||
|
||||
let expected_total = 0;
|
||||
|
||||
let total_rewards_in_lamports = bank.store_stake_accounts_in_partition(&stake_rewards);
|
||||
assert_eq!(expected_total, total_rewards_in_lamports);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_reward_history_in_partition() {
|
||||
for zero_reward in [false, true] {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let mut expected_num = 100;
|
||||
|
||||
let mut stake_rewards = (0..expected_num)
|
||||
.map(|_| StakeReward::new_random())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let i_zero = rng.gen_range(0..expected_num);
|
||||
if zero_reward {
|
||||
// pick one entry to have zero rewards so it gets ignored
|
||||
stake_rewards[i_zero].stake_reward_info.lamports = 0;
|
||||
}
|
||||
|
||||
let num_in_history = bank.update_reward_history_in_partition(&stake_rewards);
|
||||
|
||||
if zero_reward {
|
||||
stake_rewards.remove(i_zero);
|
||||
// -1 because one of them had zero rewards and was ignored
|
||||
expected_num -= 1;
|
||||
}
|
||||
|
||||
bank.rewards
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.zip(stake_rewards.iter())
|
||||
.for_each(|((k, reward_info), expected_stake_reward)| {
|
||||
assert_eq!(
|
||||
(
|
||||
&expected_stake_reward.stake_pubkey,
|
||||
&expected_stake_reward.stake_reward_info
|
||||
),
|
||||
(k, reward_info)
|
||||
);
|
||||
});
|
||||
|
||||
assert_eq!(num_in_history, expected_num);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_reward_history_in_partition_empty() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
|
||||
let stake_rewards = vec![];
|
||||
|
||||
let num_in_history = bank.update_reward_history_in_partition(&stake_rewards);
|
||||
assert_eq!(num_in_history, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_vote_accounts_partitioned() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
|
||||
|
|
Loading…
Reference in New Issue