Skip to content

Commit f69ece2

Browse files
committed
runtime: Add test for epoch boundary (anza-xyz#8838)
Add test that ensures correct values in `Bank` and `PartitionedRewardsCalculation` after crossing epoch boundary. (cherry picked from commit 30d8afd) # Conflicts: # runtime/src/bank/partitioned_epoch_rewards/calculation.rs
1 parent 5665731 commit f69ece2

2 files changed

Lines changed: 273 additions & 25 deletions

File tree

runtime/src/bank/partitioned_epoch_rewards/calculation.rs

Lines changed: 236 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -672,8 +672,8 @@ mod tests {
672672
partitioned_epoch_rewards::{
673673
tests::{
674674
build_partitioned_stake_rewards, create_default_reward_bank,
675-
create_reward_bank, create_reward_bank_with_specific_stakes, RewardBank,
676-
SLOTS_PER_EPOCH,
675+
create_reward_bank, create_reward_bank_with_specific_stakes,
676+
populate_vote_accounts_with_votes, RewardBank, SLOTS_PER_EPOCH,
677677
},
678678
EpochRewardPhase, EpochRewardStatus, PartitionedStakeRewards,
679679
StartBlockHeightAndPartitionedRewards,
@@ -682,7 +682,12 @@ mod tests {
682682
RewardInfo, VoteReward,
683683
},
684684
stake_account::StakeAccount,
685+
<<<<<<< HEAD
685686
stakes::Stakes,
687+
=======
688+
stake_utils,
689+
stakes::{tests::create_staked_node_accounts, Stakes},
690+
>>>>>>> 30d8afd32 (runtime: Add test for epoch boundary (#8838))
686691
},
687692
agave_feature_set::FeatureSet,
688693
rayon::ThreadPoolBuilder,
@@ -693,7 +698,10 @@ mod tests {
693698
solana_stake_interface::state::{Delegation, StakeStateV2},
694699
solana_vote_interface::state::VoteStateV4,
695700
solana_vote_program::vote_state,
696-
std::sync::{Arc, RwLockReadGuard},
701+
std::{
702+
collections::HashSet,
703+
sync::{Arc, RwLockReadGuard},
704+
},
697705
};
698706

699707
#[test]
@@ -1414,4 +1422,229 @@ mod tests {
14141422
assert_eq!(vote_reward_c.commission, 10);
14151423
assert_eq!(vote_reward_c.vote_rewards, 50);
14161424
}
1425+
<<<<<<< HEAD
1426+
=======
1427+
1428+
#[test]
1429+
fn test_epoch_rewards_cache_multiple_forks() {
1430+
let (mut genesis_config, _mint_keypair) =
1431+
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
1432+
1433+
const NUM_STAKES: usize = 1000;
1434+
1435+
for _i in 0..NUM_STAKES {
1436+
let vote_pubkey = Pubkey::new_unique();
1437+
let stake_pubkey = Pubkey::new_unique();
1438+
1439+
genesis_config.accounts.insert(
1440+
vote_pubkey,
1441+
vote_state::create_v4_account_with_authorized(
1442+
&vote_pubkey,
1443+
&Pubkey::new_unique(),
1444+
&Pubkey::new_unique(),
1445+
None,
1446+
0,
1447+
100_000_000_000,
1448+
)
1449+
.into(),
1450+
);
1451+
1452+
let stake_lamports = 1_000_000_000_000;
1453+
let stake_account = stake_utils::create_stake_account(
1454+
&stake_pubkey,
1455+
&vote_pubkey,
1456+
&vote_state::create_v4_account_with_authorized(
1457+
&vote_pubkey,
1458+
&Pubkey::new_unique(),
1459+
&Pubkey::new_unique(),
1460+
None,
1461+
0,
1462+
100_000_000_000,
1463+
),
1464+
&genesis_config.rent,
1465+
stake_lamports,
1466+
);
1467+
genesis_config
1468+
.accounts
1469+
.insert(stake_pubkey, stake_account.into());
1470+
}
1471+
1472+
let bank = Arc::new(Bank::new_for_tests(&genesis_config));
1473+
let slots_per_epoch = bank.epoch_schedule().slots_per_epoch;
1474+
{
1475+
let cache = bank.epoch_rewards_calculation_cache.lock().unwrap();
1476+
assert!(
1477+
!cache.contains_key(&bank.parent_hash()),
1478+
"cache should be empty"
1479+
);
1480+
}
1481+
1482+
let bank_fork1 =
1483+
Bank::new_from_parent(Arc::clone(&bank), &Pubkey::default(), slots_per_epoch);
1484+
{
1485+
let cache = bank_fork1.epoch_rewards_calculation_cache.lock().unwrap();
1486+
assert!(
1487+
cache.contains_key(&bank_fork1.parent_hash()),
1488+
"cache should be populated"
1489+
);
1490+
}
1491+
1492+
let bank_fork2 = Bank::new_from_parent(bank, &Pubkey::default(), slots_per_epoch);
1493+
{
1494+
let cache = bank_fork2.epoch_rewards_calculation_cache.lock().unwrap();
1495+
assert!(
1496+
cache.contains_key(&bank_fork2.parent_hash()),
1497+
"cache should be populated"
1498+
);
1499+
}
1500+
}
1501+
1502+
fn add_voters_and_populate(
1503+
bank: &Arc<Bank>,
1504+
voters: &mut HashSet<Pubkey>,
1505+
stakers: &mut HashSet<Pubkey>,
1506+
count: usize,
1507+
stake_lamports: u64,
1508+
commission: u8,
1509+
) {
1510+
for _ in 0..count {
1511+
let ((vote_pubkey, vote_account), (stake_pubkey, stake_account)) =
1512+
create_staked_node_accounts(stake_lamports);
1513+
bank.store_account_and_update_capitalization(&vote_pubkey, &vote_account);
1514+
bank.store_account_and_update_capitalization(&stake_pubkey, &stake_account);
1515+
voters.insert(vote_pubkey);
1516+
stakers.insert(stake_pubkey);
1517+
}
1518+
populate_vote_accounts_with_votes(bank, voters.iter().copied(), commission);
1519+
}
1520+
1521+
#[allow(clippy::too_many_arguments)]
1522+
fn assert_cached_rewards(
1523+
bank: &Arc<Bank>,
1524+
expected_cache_len: usize,
1525+
expected_voters: &HashSet<Pubkey>,
1526+
expected_stakers: &HashSet<Pubkey>,
1527+
expected_vote_rewards: u64,
1528+
expected_stake_rewards: u64,
1529+
expected_rewards: u64,
1530+
expected_points: u128,
1531+
parent_capitalization: Option<u64>,
1532+
) {
1533+
let cache = bank.epoch_rewards_calculation_cache.lock().unwrap();
1534+
assert_eq!(cache.len(), expected_cache_len);
1535+
let partitioned = cache.get(&bank.parent_hash()).unwrap().as_ref();
1536+
let VoteRewardsAccounts {
1537+
accounts_with_rewards,
1538+
total_vote_rewards_lamports,
1539+
..
1540+
} = &partitioned.vote_account_rewards;
1541+
let StakeRewardCalculation {
1542+
stake_rewards,
1543+
total_stake_rewards_lamports,
1544+
..
1545+
} = &partitioned.stake_rewards;
1546+
let point_value = &partitioned.point_value;
1547+
let voters: HashSet<_> = accounts_with_rewards
1548+
.iter()
1549+
.map(|(pubkey, _reward, _acc)| *pubkey)
1550+
.collect();
1551+
let stakers: HashSet<_> = stake_rewards
1552+
.rewards
1553+
.iter()
1554+
.filter_map(|reward| reward.as_ref())
1555+
.map(|reward| reward.stake_pubkey)
1556+
.collect();
1557+
assert_eq!(expected_voters, &voters);
1558+
assert_eq!(expected_stakers, &stakers);
1559+
assert_eq!(*total_vote_rewards_lamports, expected_vote_rewards);
1560+
assert_eq!(*total_stake_rewards_lamports, expected_stake_rewards);
1561+
assert_eq!(point_value.rewards, expected_rewards);
1562+
assert_eq!(point_value.points, expected_points);
1563+
if let Some(parent_cap) = parent_capitalization {
1564+
assert_eq!(bank.capitalization(), parent_cap + expected_vote_rewards);
1565+
}
1566+
}
1567+
1568+
#[test]
1569+
fn test_epoch_boundary() {
1570+
let delegations = 100;
1571+
let stake_lamports = 2_000_000_000;
1572+
let stakes: Vec<_> = (0..delegations).map(|_| stake_lamports).collect();
1573+
let (
1574+
RewardBank {
1575+
bank: bank1,
1576+
voters,
1577+
stakers,
1578+
..
1579+
},
1580+
_bank_forks,
1581+
) = create_reward_bank_with_specific_stakes(
1582+
stakes,
1583+
PartitionedEpochRewardsConfig::default().stake_account_stores_per_block,
1584+
SLOTS_PER_EPOCH,
1585+
);
1586+
let mut voters: HashSet<_> = voters.into_iter().collect();
1587+
let mut stakers: HashSet<_> = stakers.into_iter().collect();
1588+
1589+
// The sysvar account holds the rent-exempt lamport added after
1590+
// reward calculation, so the bank capitalization exceeds the cached
1591+
// value by this amount.
1592+
let epoch_rewards_sysvar_balance = bank1.get_balance(&solana_sysvar::epoch_rewards::id());
1593+
assert_eq!(epoch_rewards_sysvar_balance, 1);
1594+
1595+
assert_cached_rewards(
1596+
&bank1,
1597+
1, // expected_cache_len
1598+
&voters, // expected_voters
1599+
&stakers, // expected_stakers
1600+
0, // expected_vote_rewards
1601+
12300, // expected_stake_rewards
1602+
12392, // expected_rewards
1603+
8_400_000_000_000u128, // expected_points
1604+
None, // parent_capitalization
1605+
);
1606+
1607+
add_voters_and_populate(&bank1, &mut voters, &mut stakers, 5, 5_000_000_000, 10);
1608+
let parent_capitalization = bank1.capitalization();
1609+
1610+
let bank2 = Arc::new(Bank::new_from_parent(
1611+
Arc::clone(&bank1),
1612+
&Pubkey::default(),
1613+
SLOTS_PER_EPOCH * 2,
1614+
));
1615+
1616+
assert_cached_rewards(
1617+
&bank2,
1618+
2, // expected_cache_len
1619+
&voters, // expected_voters
1620+
&stakers, // expected_stakers
1621+
1245, // expected_vote_rewards
1622+
11810, // expected_stake_rewards
1623+
13163, // expected_rewards
1624+
9_450_000_000_000u128, // expected_points
1625+
Some(parent_capitalization), // parent_capitalization
1626+
);
1627+
1628+
add_voters_and_populate(&bank2, &mut voters, &mut stakers, 10, 8_000_000_000, 10);
1629+
let parent_capitalization = bank2.capitalization();
1630+
1631+
let bank3 = Arc::new(Bank::new_from_parent(
1632+
Arc::clone(&bank2),
1633+
&Pubkey::default(),
1634+
SLOTS_PER_EPOCH * 3,
1635+
));
1636+
1637+
assert_cached_rewards(
1638+
&bank3,
1639+
3, // expected_cache_len
1640+
&voters, // expected_voters
1641+
&stakers, // expected_stakers
1642+
1525, // expected_vote_rewards
1643+
13930, // expected_stake_rewards
1644+
15629, // expected_rewards
1645+
12_810_000_000_000u128, // expected_points
1646+
Some(parent_capitalization), // parent_capitalization
1647+
);
1648+
}
1649+
>>>>>>> 30d8afd32 (runtime: Add test for epoch boundary (#8838))
14171650
}

runtime/src/bank/partitioned_epoch_rewards/mod.rs

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ mod tests {
391391
solana_system_transaction as system_transaction,
392392
solana_vote::vote_transaction,
393393
solana_vote_interface::state::{VoteStateV4, VoteStateVersions, MAX_LOCKOUT_HISTORY},
394-
solana_vote_program::vote_state::{self, TowerSync},
394+
solana_vote_program::vote_state::{self, handler::VoteStateHandle, TowerSync},
395395
std::sync::{Arc, RwLock},
396396
};
397397

@@ -555,27 +555,11 @@ mod tests {
555555

556556
// Fill bank_forks with banks with votes landing in the next slot
557557
// Create enough banks such that vote account will root
558-
for validator_vote_keypairs in &validator_keypairs {
559-
let vote_id = validator_vote_keypairs.vote_keypair.pubkey();
560-
let mut vote_account = bank.get_account(&vote_id).unwrap();
561-
// generate some rewards
562-
let mut vote_state =
563-
Some(VoteStateV4::deserialize(vote_account.data(), &vote_id).unwrap());
564-
for i in 0..MAX_LOCKOUT_HISTORY + 42 {
565-
if let Some(v) = vote_state.as_mut() {
566-
vote_state::process_slot_vote_unchecked(v, i as u64)
567-
}
568-
let versioned = VoteStateVersions::V4(Box::new(vote_state.take().unwrap()));
569-
vote_account.set_state(&versioned).unwrap();
570-
match versioned {
571-
VoteStateVersions::V4(v) => {
572-
vote_state = Some(*v);
573-
}
574-
_ => panic!("Has to be of type V4"),
575-
};
576-
}
577-
bank.store_account_and_update_capitalization(&vote_id, &vote_account);
578-
}
558+
populate_vote_accounts_with_votes(
559+
&bank,
560+
validator_keypairs.iter().map(|k| k.vote_keypair.pubkey()),
561+
0,
562+
);
579563

580564
// Advance some num slots; usually to the next epoch boundary to update
581565
// EpochStakes
@@ -603,6 +587,37 @@ mod tests {
603587
)
604588
}
605589

590+
pub(super) fn populate_vote_accounts_with_votes(
591+
bank: &Bank,
592+
vote_pubkeys: impl IntoIterator<Item = Pubkey>,
593+
commission: u8,
594+
) {
595+
for vote_pubkey in vote_pubkeys {
596+
let mut vote_account = bank
597+
.get_account(&vote_pubkey)
598+
.unwrap_or_else(|| panic!("missing vote account {vote_pubkey:?}"));
599+
let mut vote_state =
600+
Some(VoteStateV4::deserialize(vote_account.data(), &vote_pubkey).unwrap());
601+
if let Some(state) = vote_state.as_mut() {
602+
state.set_commission(commission);
603+
}
604+
for i in 0..MAX_LOCKOUT_HISTORY + 42 {
605+
if let Some(state) = vote_state.as_mut() {
606+
vote_state::process_slot_vote_unchecked(state, i as u64);
607+
}
608+
let versioned = VoteStateVersions::V4(Box::new(vote_state.take().unwrap()));
609+
vote_account.set_state(&versioned).unwrap();
610+
match versioned {
611+
VoteStateVersions::V4(v) => {
612+
vote_state = Some(*v);
613+
}
614+
_ => panic!("Has to be of type V4"),
615+
};
616+
}
617+
bank.store_account_and_update_capitalization(&vote_pubkey, &vote_account);
618+
}
619+
}
620+
606621
#[test]
607622
fn test_force_reward_interval_end() {
608623
let (genesis_config, _mint_keypair) = create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);

0 commit comments

Comments
 (0)