Bank::vote_accounts redundantly clones vote-accounts HashMap even though an immutable reference will suffice: https://github.com/solana-labs/solana/blob/95c998a19/runtime/src/bank.rs#L5174-L5186 This commit implements copy-on-write semantics for vote-accounts by wrapping the underlying HashMap in Arc<...>. Co-authored-by: behzad nouri <behzadnouri@gmail.com>
This commit is contained in:
@@ -31,7 +31,6 @@ use {
|
||||
bank_forks::BankForks,
|
||||
commitment::VOTE_THRESHOLD_SIZE,
|
||||
epoch_stakes::{EpochAuthorizedVoters, EpochStakes},
|
||||
stakes::Stakes,
|
||||
vote_sender_types::{ReplayVoteReceiver, ReplayedVote},
|
||||
},
|
||||
solana_sdk::{
|
||||
@@ -609,7 +608,7 @@ impl ClusterInfoVoteListener {
|
||||
// The last vote slot, which is the greatest slot in the stack
|
||||
// of votes in a vote transaction, qualifies for optimistic confirmation.
|
||||
if slot == last_vote_slot {
|
||||
let vote_accounts = Stakes::vote_accounts(epoch_stakes.stakes());
|
||||
let vote_accounts = epoch_stakes.stakes().vote_accounts();
|
||||
let stake = vote_accounts
|
||||
.get(vote_pubkey)
|
||||
.map(|(stake, _)| *stake)
|
||||
|
@@ -187,8 +187,8 @@ impl AggregateCommitmentService {
|
||||
|
||||
let mut commitment = HashMap::new();
|
||||
let mut rooted_stake: Vec<(Slot, u64)> = Vec::new();
|
||||
for (_, (lamports, account)) in bank.vote_accounts().into_iter() {
|
||||
if lamports == 0 {
|
||||
for (lamports, account) in bank.vote_accounts().values() {
|
||||
if *lamports == 0 {
|
||||
continue;
|
||||
}
|
||||
if let Ok(vote_state) = account.vote_state().as_ref() {
|
||||
@@ -197,7 +197,7 @@ impl AggregateCommitmentService {
|
||||
&mut rooted_stake,
|
||||
vote_state,
|
||||
ancestors,
|
||||
lamports,
|
||||
*lamports,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -223,17 +223,14 @@ impl Tower {
|
||||
Self::new(my_pubkey, vote_account, root, &heaviest_bank, ledger_path)
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vote_lockouts<F>(
|
||||
pub(crate) fn collect_vote_lockouts(
|
||||
vote_account_pubkey: &Pubkey,
|
||||
bank_slot: Slot,
|
||||
vote_accounts: F,
|
||||
vote_accounts: &HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>,
|
||||
ancestors: &HashMap<Slot, HashSet<Slot>>,
|
||||
get_frozen_hash: impl Fn(Slot) -> Option<Hash>,
|
||||
latest_validator_votes_for_frozen_banks: &mut LatestValidatorVotesForFrozenBanks,
|
||||
) -> ComputedBankState
|
||||
where
|
||||
F: IntoIterator<Item = (Pubkey, (u64, VoteAccount))>,
|
||||
{
|
||||
) -> ComputedBankState {
|
||||
let mut vote_slots = HashSet::new();
|
||||
let mut voted_stakes = HashMap::new();
|
||||
let mut total_stake = 0;
|
||||
@@ -242,7 +239,8 @@ impl Tower {
|
||||
// keyed by end of the range
|
||||
let mut lockout_intervals = LockoutIntervals::new();
|
||||
let mut my_latest_landed_vote = None;
|
||||
for (key, (voted_stake, account)) in vote_accounts {
|
||||
for (&key, (voted_stake, account)) in vote_accounts.iter() {
|
||||
let voted_stake = *voted_stake;
|
||||
if voted_stake == 0 {
|
||||
continue;
|
||||
}
|
||||
@@ -1369,6 +1367,7 @@ pub mod test {
|
||||
replay_stage::{HeaviestForkFailures, ReplayStage},
|
||||
unfrozen_gossip_verified_vote_hashes::UnfrozenGossipVerifiedVoteHashes,
|
||||
},
|
||||
itertools::Itertools,
|
||||
solana_ledger::{blockstore::make_slot_entries, get_tmp_ledger_path},
|
||||
solana_runtime::{
|
||||
accounts_background_service::AbsRequestSender,
|
||||
@@ -1722,9 +1721,10 @@ pub mod test {
|
||||
(bank_forks, progress, heaviest_subtree_fork_choice)
|
||||
}
|
||||
|
||||
fn gen_stakes(stake_votes: &[(u64, &[u64])]) -> Vec<(Pubkey, (u64, VoteAccount))> {
|
||||
let mut stakes = vec![];
|
||||
for (lamports, votes) in stake_votes {
|
||||
fn gen_stakes(stake_votes: &[(u64, &[u64])]) -> HashMap<Pubkey, (u64, VoteAccount)> {
|
||||
stake_votes
|
||||
.iter()
|
||||
.map(|(lamports, votes)| {
|
||||
let mut account = AccountSharedData::from(Account {
|
||||
data: vec![0; VoteState::size_of()],
|
||||
lamports: *lamports,
|
||||
@@ -1739,12 +1739,12 @@ pub mod test {
|
||||
&mut account.data_as_mut_slice(),
|
||||
)
|
||||
.expect("serialize state");
|
||||
stakes.push((
|
||||
(
|
||||
solana_sdk::pubkey::new_rand(),
|
||||
(*lamports, VoteAccount::from(account)),
|
||||
));
|
||||
}
|
||||
stakes
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2389,10 +2389,10 @@ pub mod test {
|
||||
#[test]
|
||||
fn test_collect_vote_lockouts_sums() {
|
||||
//two accounts voting for slot 0 with 1 token staked
|
||||
let mut accounts = gen_stakes(&[(1, &[0]), (1, &[0])]);
|
||||
accounts.sort_by_key(|(pk, _)| *pk);
|
||||
let accounts = gen_stakes(&[(1, &[0]), (1, &[0])]);
|
||||
let account_latest_votes: Vec<(Pubkey, SlotHashKey)> = accounts
|
||||
.iter()
|
||||
.sorted_by_key(|(pk, _)| *pk)
|
||||
.map(|(pubkey, _)| (*pubkey, (0, Hash::default())))
|
||||
.collect();
|
||||
|
||||
@@ -2409,7 +2409,7 @@ pub mod test {
|
||||
} = Tower::collect_vote_lockouts(
|
||||
&Pubkey::default(),
|
||||
1,
|
||||
accounts.into_iter(),
|
||||
&accounts,
|
||||
&ancestors,
|
||||
|_| Some(Hash::default()),
|
||||
&mut latest_validator_votes_for_frozen_banks,
|
||||
@@ -2429,10 +2429,10 @@ pub mod test {
|
||||
fn test_collect_vote_lockouts_root() {
|
||||
let votes: Vec<u64> = (0..MAX_LOCKOUT_HISTORY as u64).collect();
|
||||
//two accounts voting for slots 0..MAX_LOCKOUT_HISTORY with 1 token staked
|
||||
let mut accounts = gen_stakes(&[(1, &votes), (1, &votes)]);
|
||||
accounts.sort_by_key(|(pk, _)| *pk);
|
||||
let accounts = gen_stakes(&[(1, &votes), (1, &votes)]);
|
||||
let account_latest_votes: Vec<(Pubkey, SlotHashKey)> = accounts
|
||||
.iter()
|
||||
.sorted_by_key(|(pk, _)| *pk)
|
||||
.map(|(pubkey, _)| {
|
||||
(
|
||||
*pubkey,
|
||||
@@ -2469,7 +2469,7 @@ pub mod test {
|
||||
} = Tower::collect_vote_lockouts(
|
||||
&Pubkey::default(),
|
||||
MAX_LOCKOUT_HISTORY as u64,
|
||||
accounts.into_iter(),
|
||||
&accounts,
|
||||
&ancestors,
|
||||
|_| Some(Hash::default()),
|
||||
&mut latest_validator_votes_for_frozen_banks,
|
||||
@@ -2765,7 +2765,7 @@ pub mod test {
|
||||
} = Tower::collect_vote_lockouts(
|
||||
&Pubkey::default(),
|
||||
vote_to_evaluate,
|
||||
accounts.clone().into_iter(),
|
||||
&accounts,
|
||||
&ancestors,
|
||||
|_| None,
|
||||
&mut LatestValidatorVotesForFrozenBanks::default(),
|
||||
@@ -2783,7 +2783,7 @@ pub mod test {
|
||||
} = Tower::collect_vote_lockouts(
|
||||
&Pubkey::default(),
|
||||
vote_to_evaluate,
|
||||
accounts.into_iter(),
|
||||
&accounts,
|
||||
&ancestors,
|
||||
|_| None,
|
||||
&mut LatestValidatorVotesForFrozenBanks::default(),
|
||||
|
@@ -1879,7 +1879,7 @@ impl ReplayStage {
|
||||
let computed_bank_state = Tower::collect_vote_lockouts(
|
||||
my_vote_pubkey,
|
||||
bank_slot,
|
||||
bank.vote_accounts().into_iter(),
|
||||
&bank.vote_accounts(),
|
||||
ancestors,
|
||||
|slot| progress.get_hash(slot),
|
||||
latest_validator_votes_for_frozen_banks,
|
||||
|
@@ -1549,7 +1549,8 @@ fn get_stake_percent_in_gossip(bank: &Bank, cluster_info: &ClusterInfo, log: boo
|
||||
let my_shred_version = cluster_info.my_shred_version();
|
||||
let my_id = cluster_info.id();
|
||||
|
||||
for (_, (activated_stake, vote_account)) in bank.vote_accounts() {
|
||||
for (activated_stake, vote_account) in bank.vote_accounts().values() {
|
||||
let activated_stake = *activated_stake;
|
||||
total_activated_stake += activated_stake;
|
||||
|
||||
if activated_stake == 0 {
|
||||
|
@@ -334,18 +334,18 @@ fn graph_forks(bank_forks: &BankForks, include_all_votes: bool) -> String {
|
||||
.iter()
|
||||
.map(|(_, (stake, _))| stake)
|
||||
.sum();
|
||||
for (_, (stake, vote_account)) in bank.vote_accounts() {
|
||||
for (stake, vote_account) in bank.vote_accounts().values() {
|
||||
let vote_state = vote_account.vote_state();
|
||||
let vote_state = vote_state.as_ref().unwrap_or(&default_vote_state);
|
||||
if let Some(last_vote) = vote_state.votes.iter().last() {
|
||||
let entry = last_votes.entry(vote_state.node_pubkey).or_insert((
|
||||
last_vote.slot,
|
||||
vote_state.clone(),
|
||||
stake,
|
||||
*stake,
|
||||
total_stake,
|
||||
));
|
||||
if entry.0 < last_vote.slot {
|
||||
*entry = (last_vote.slot, vote_state.clone(), stake, total_stake);
|
||||
*entry = (last_vote.slot, vote_state.clone(), *stake, total_stake);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,7 +375,7 @@ fn graph_forks(bank_forks: &BankForks, include_all_votes: bool) -> String {
|
||||
|
||||
let mut first = true;
|
||||
loop {
|
||||
for (_, (_, vote_account)) in bank.vote_accounts() {
|
||||
for (_, vote_account) in bank.vote_accounts().values() {
|
||||
let vote_state = vote_account.vote_state();
|
||||
let vote_state = vote_state.as_ref().unwrap_or(&default_vote_state);
|
||||
if let Some(last_vote) = vote_state.votes.iter().last() {
|
||||
|
@@ -1092,7 +1092,7 @@ fn load_frozen_forks(
|
||||
supermajority_root_from_vote_accounts(
|
||||
bank.slot(),
|
||||
bank.total_epoch_stake(),
|
||||
bank.vote_accounts(),
|
||||
&bank.vote_accounts(),
|
||||
).and_then(|supermajority_root| {
|
||||
if supermajority_root > *root {
|
||||
// If there's a cluster confirmed root greater than our last
|
||||
@@ -1200,18 +1200,15 @@ fn supermajority_root(roots: &[(Slot, u64)], total_epoch_stake: u64) -> Option<S
|
||||
None
|
||||
}
|
||||
|
||||
fn supermajority_root_from_vote_accounts<I>(
|
||||
fn supermajority_root_from_vote_accounts(
|
||||
bank_slot: Slot,
|
||||
total_epoch_stake: u64,
|
||||
vote_accounts: I,
|
||||
) -> Option<Slot>
|
||||
where
|
||||
I: IntoIterator<Item = (Pubkey, (u64, VoteAccount))>,
|
||||
{
|
||||
vote_accounts: &HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>,
|
||||
) -> Option<Slot> {
|
||||
let mut roots_stakes: Vec<(Slot, u64)> = vote_accounts
|
||||
.into_iter()
|
||||
.iter()
|
||||
.filter_map(|(key, (stake, account))| {
|
||||
if stake == 0 {
|
||||
if *stake == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1223,7 +1220,7 @@ where
|
||||
);
|
||||
None
|
||||
}
|
||||
Ok(vote_state) => vote_state.root_slot.map(|root_slot| (root_slot, stake)),
|
||||
Ok(vote_state) => Some((vote_state.root_slot?, *stake)),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -3591,7 +3588,7 @@ pub mod tests {
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn test_supermajority_root_from_vote_accounts() {
|
||||
let convert_to_vote_accounts =
|
||||
|roots_stakes: Vec<(Slot, u64)>| -> Vec<(Pubkey, (u64, VoteAccount))> {
|
||||
|roots_stakes: Vec<(Slot, u64)>| -> HashMap<Pubkey, (u64, VoteAccount)> {
|
||||
roots_stakes
|
||||
.into_iter()
|
||||
.map(|(root, stake)| {
|
||||
@@ -3609,7 +3606,7 @@ pub mod tests {
|
||||
(stake, VoteAccount::from(vote_account)),
|
||||
)
|
||||
})
|
||||
.collect_vec()
|
||||
.collect()
|
||||
};
|
||||
|
||||
let total_stake = 10;
|
||||
@@ -3617,22 +3614,19 @@ pub mod tests {
|
||||
|
||||
// Supermajority root should be None
|
||||
assert!(
|
||||
supermajority_root_from_vote_accounts(slot, total_stake, std::iter::empty()).is_none()
|
||||
supermajority_root_from_vote_accounts(slot, total_stake, &HashMap::default()).is_none()
|
||||
);
|
||||
|
||||
// Supermajority root should be None
|
||||
let roots_stakes = vec![(8, 1), (3, 1), (4, 1), (8, 1)];
|
||||
let accounts = convert_to_vote_accounts(roots_stakes);
|
||||
assert!(
|
||||
supermajority_root_from_vote_accounts(slot, total_stake, accounts.into_iter())
|
||||
.is_none()
|
||||
);
|
||||
assert!(supermajority_root_from_vote_accounts(slot, total_stake, &accounts).is_none());
|
||||
|
||||
// Supermajority root should be 4, has 7/10 of the stake
|
||||
let roots_stakes = vec![(8, 1), (3, 1), (4, 1), (8, 5)];
|
||||
let accounts = convert_to_vote_accounts(roots_stakes);
|
||||
assert_eq!(
|
||||
supermajority_root_from_vote_accounts(slot, total_stake, accounts.into_iter()).unwrap(),
|
||||
supermajority_root_from_vote_accounts(slot, total_stake, &accounts).unwrap(),
|
||||
4
|
||||
);
|
||||
|
||||
@@ -3640,7 +3634,7 @@ pub mod tests {
|
||||
let roots_stakes = vec![(8, 1), (3, 1), (4, 1), (8, 6)];
|
||||
let accounts = convert_to_vote_accounts(roots_stakes);
|
||||
assert_eq!(
|
||||
supermajority_root_from_vote_accounts(slot, total_stake, accounts.into_iter()).unwrap(),
|
||||
supermajority_root_from_vote_accounts(slot, total_stake, &accounts).unwrap(),
|
||||
8
|
||||
);
|
||||
}
|
||||
|
@@ -1,10 +1,6 @@
|
||||
use {
|
||||
solana_runtime::bank::Bank,
|
||||
solana_sdk::{
|
||||
clock::{Epoch, Slot},
|
||||
pubkey::Pubkey,
|
||||
},
|
||||
std::collections::HashMap,
|
||||
solana_sdk::clock::{Epoch, Slot},
|
||||
};
|
||||
|
||||
/// Looks through vote accounts, and finds the latest slot that has achieved
|
||||
@@ -19,13 +15,6 @@ pub fn get_supermajority_slot(bank: &Bank, epoch: Epoch) -> Option<u64> {
|
||||
find_supermajority_slot(supermajority_stake, stakes_and_lockouts.iter())
|
||||
}
|
||||
|
||||
pub fn vote_account_stakes(bank: &Bank) -> HashMap<Pubkey, u64> {
|
||||
bank.vote_accounts()
|
||||
.into_iter()
|
||||
.map(|(id, (stake, _))| (id, stake))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn epoch_stakes_and_lockouts(bank: &Bank, epoch: Epoch) -> Vec<(u64, Option<u64>)> {
|
||||
bank.epoch_vote_accounts(epoch)
|
||||
.expect("Bank state for epoch is missing")
|
||||
|
@@ -2078,7 +2078,8 @@ impl Bank {
|
||||
reward_calc_tracer: Option<impl Fn(&RewardCalculationEvent) + Send + Sync>,
|
||||
) -> LoadVoteAndStakeAccountsResult {
|
||||
let stakes = self.stakes_cache.stakes();
|
||||
let vote_with_stake_delegations_map = DashMap::with_capacity(stakes.vote_accounts().len());
|
||||
let vote_with_stake_delegations_map =
|
||||
DashMap::with_capacity(stakes.vote_accounts().as_ref().len());
|
||||
let invalid_stake_keys: DashMap<Pubkey, InvalidCacheEntryReason> = DashMap::new();
|
||||
let invalid_vote_keys: DashMap<Pubkey, InvalidCacheEntryReason> = DashMap::new();
|
||||
|
||||
@@ -2386,24 +2387,20 @@ impl Bank {
|
||||
) -> Option<UnixTimestamp> {
|
||||
let mut get_timestamp_estimate_time = Measure::start("get_timestamp_estimate");
|
||||
let slots_per_epoch = self.epoch_schedule().slots_per_epoch;
|
||||
let recent_timestamps =
|
||||
self.vote_accounts()
|
||||
.into_iter()
|
||||
.filter_map(|(pubkey, (_, account))| {
|
||||
let vote_accounts = self.vote_accounts();
|
||||
let recent_timestamps = vote_accounts.iter().filter_map(|(pubkey, (_, account))| {
|
||||
let vote_state = account.vote_state();
|
||||
let vote_state = vote_state.as_ref().ok()?;
|
||||
let slot_delta = self.slot().checked_sub(vote_state.last_timestamp.slot)?;
|
||||
if slot_delta <= slots_per_epoch {
|
||||
Some((
|
||||
pubkey,
|
||||
(slot_delta <= slots_per_epoch).then(|| {
|
||||
(
|
||||
*pubkey,
|
||||
(
|
||||
vote_state.last_timestamp.slot,
|
||||
vote_state.last_timestamp.timestamp,
|
||||
),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
)
|
||||
})
|
||||
});
|
||||
let slot_duration = Duration::from_nanos(self.ns_per_slot as u64);
|
||||
let epoch = self.epoch_schedule().get_epoch(self.slot());
|
||||
@@ -3807,24 +3804,25 @@ impl Bank {
|
||||
//
|
||||
// Ref: collect_fees
|
||||
#[allow(clippy::needless_collect)]
|
||||
fn distribute_rent_to_validators<I>(&self, vote_accounts: I, rent_to_be_distributed: u64)
|
||||
where
|
||||
I: IntoIterator<Item = (Pubkey, (u64, VoteAccount))>,
|
||||
{
|
||||
fn distribute_rent_to_validators(
|
||||
&self,
|
||||
vote_accounts: &HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>,
|
||||
rent_to_be_distributed: u64,
|
||||
) {
|
||||
let mut total_staked = 0;
|
||||
|
||||
// Collect the stake associated with each validator.
|
||||
// Note that a validator may be present in this vector multiple times if it happens to have
|
||||
// more than one staked vote account somehow
|
||||
let mut validator_stakes = vote_accounts
|
||||
.into_iter()
|
||||
.iter()
|
||||
.filter_map(|(_vote_pubkey, (staked, account))| {
|
||||
if staked == 0 {
|
||||
if *staked == 0 {
|
||||
None
|
||||
} else {
|
||||
total_staked += staked;
|
||||
total_staked += *staked;
|
||||
let node_pubkey = account.vote_state().as_ref().ok()?.node_pubkey;
|
||||
Some((node_pubkey, staked))
|
||||
Some((node_pubkey, *staked))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(Pubkey, u64)>>();
|
||||
@@ -3939,7 +3937,7 @@ impl Bank {
|
||||
return;
|
||||
}
|
||||
|
||||
self.distribute_rent_to_validators(self.vote_accounts(), rent_to_be_distributed);
|
||||
self.distribute_rent_to_validators(&self.vote_accounts(), rent_to_be_distributed);
|
||||
}
|
||||
|
||||
fn collect_rent(
|
||||
@@ -5196,13 +5194,8 @@ impl Bank {
|
||||
/// attributed to each account
|
||||
/// Note: This clones the entire vote-accounts hashmap. For a single
|
||||
/// account lookup use get_vote_account instead.
|
||||
pub fn vote_accounts(&self) -> Vec<(Pubkey, (/*stake:*/ u64, VoteAccount))> {
|
||||
self.stakes_cache
|
||||
.stakes()
|
||||
.vote_accounts()
|
||||
.iter()
|
||||
.map(|(k, v)| (*k, v.clone()))
|
||||
.collect()
|
||||
pub fn vote_accounts(&self) -> Arc<HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>> {
|
||||
Arc::from(self.stakes_cache.stakes().vote_accounts())
|
||||
}
|
||||
|
||||
/// Vote account for the given vote account pubkey along with the stake.
|
||||
@@ -5233,9 +5226,8 @@ impl Bank {
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
) -> Option<&HashMap<Pubkey, (u64, VoteAccount)>> {
|
||||
self.epoch_stakes
|
||||
.get(&epoch)
|
||||
.map(|epoch_stakes| Stakes::vote_accounts(epoch_stakes.stakes()))
|
||||
let epoch_stakes = self.epoch_stakes.get(&epoch)?.stakes();
|
||||
Some(epoch_stakes.vote_accounts().as_ref())
|
||||
}
|
||||
|
||||
/// Get the fixed authorized voter for the given vote account for the
|
||||
@@ -6717,7 +6709,7 @@ pub(crate) mod tests {
|
||||
|
||||
let bank = Bank::new(&genesis_config);
|
||||
let old_validator_lamports = bank.get_balance(&validator_pubkey);
|
||||
bank.distribute_rent_to_validators(bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED);
|
||||
bank.distribute_rent_to_validators(&bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED);
|
||||
let new_validator_lamports = bank.get_balance(&validator_pubkey);
|
||||
assert_eq!(
|
||||
new_validator_lamports,
|
||||
@@ -6731,7 +6723,7 @@ pub(crate) mod tests {
|
||||
let bank = std::panic::AssertUnwindSafe(Bank::new(&genesis_config));
|
||||
let old_validator_lamports = bank.get_balance(&validator_pubkey);
|
||||
let new_validator_lamports = std::panic::catch_unwind(|| {
|
||||
bank.distribute_rent_to_validators(bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED);
|
||||
bank.distribute_rent_to_validators(&bank.vote_accounts(), RENT_TO_BE_DISTRIBUTED);
|
||||
bank.get_balance(&validator_pubkey)
|
||||
});
|
||||
|
||||
@@ -9570,7 +9562,7 @@ pub(crate) mod tests {
|
||||
|
||||
bank.process_transaction(&transaction).unwrap();
|
||||
|
||||
let vote_accounts = bank.vote_accounts().into_iter().collect::<HashMap<_, _>>();
|
||||
let vote_accounts = bank.vote_accounts();
|
||||
|
||||
assert_eq!(vote_accounts.len(), 2);
|
||||
|
||||
@@ -9946,7 +9938,7 @@ pub(crate) mod tests {
|
||||
// Non-builtin loader accounts can not be used for instruction processing
|
||||
{
|
||||
let stakes = bank.stakes_cache.stakes();
|
||||
assert!(stakes.vote_accounts().is_empty());
|
||||
assert!(stakes.vote_accounts().as_ref().is_empty());
|
||||
}
|
||||
assert!(bank.stakes_cache.stakes().stake_delegations().is_empty());
|
||||
assert_eq!(bank.calculate_capitalization(true), bank.capitalization());
|
||||
@@ -9959,7 +9951,7 @@ pub(crate) mod tests {
|
||||
bank.store_account(&stake_id, &stake_account);
|
||||
{
|
||||
let stakes = bank.stakes_cache.stakes();
|
||||
assert!(!stakes.vote_accounts().is_empty());
|
||||
assert!(!stakes.vote_accounts().as_ref().is_empty());
|
||||
}
|
||||
assert!(!bank.stakes_cache.stakes().stake_delegations().is_empty());
|
||||
assert_eq!(bank.calculate_capitalization(true), bank.capitalization());
|
||||
@@ -9968,7 +9960,7 @@ pub(crate) mod tests {
|
||||
bank.add_builtin("mock_program2", stake_id, mock_ix_processor);
|
||||
{
|
||||
let stakes = bank.stakes_cache.stakes();
|
||||
assert!(stakes.vote_accounts().is_empty());
|
||||
assert!(stakes.vote_accounts().as_ref().is_empty());
|
||||
}
|
||||
assert!(bank.stakes_cache.stakes().stake_delegations().is_empty());
|
||||
assert_eq!(bank.calculate_capitalization(true), bank.capitalization());
|
||||
@@ -9991,7 +9983,7 @@ pub(crate) mod tests {
|
||||
assert_eq!(old_hash, new_hash);
|
||||
{
|
||||
let stakes = bank.stakes_cache.stakes();
|
||||
assert!(stakes.vote_accounts().is_empty());
|
||||
assert!(stakes.vote_accounts().as_ref().is_empty());
|
||||
}
|
||||
assert!(bank.stakes_cache.stakes().stake_delegations().is_empty());
|
||||
assert_eq!(bank.calculate_capitalization(true), bank.capitalization());
|
||||
|
@@ -24,9 +24,9 @@ pub struct EpochStakes {
|
||||
|
||||
impl EpochStakes {
|
||||
pub fn new(stakes: &Stakes, leader_schedule_epoch: Epoch) -> Self {
|
||||
let epoch_vote_accounts = Stakes::vote_accounts(stakes);
|
||||
let epoch_vote_accounts = stakes.vote_accounts();
|
||||
let (total_stake, node_id_to_vote_accounts, epoch_authorized_voters) =
|
||||
Self::parse_epoch_vote_accounts(epoch_vote_accounts, leader_schedule_epoch);
|
||||
Self::parse_epoch_vote_accounts(epoch_vote_accounts.as_ref(), leader_schedule_epoch);
|
||||
Self {
|
||||
stakes: Arc::new(stakes.clone()),
|
||||
total_stake,
|
||||
@@ -52,7 +52,8 @@ impl EpochStakes {
|
||||
}
|
||||
|
||||
pub fn vote_account_stake(&self, vote_account: &Pubkey) -> u64 {
|
||||
Stakes::vote_accounts(&self.stakes)
|
||||
self.stakes
|
||||
.vote_accounts()
|
||||
.get(vote_account)
|
||||
.map(|(stake, _)| *stake)
|
||||
.unwrap_or(0)
|
||||
|
@@ -23,7 +23,7 @@ use {
|
||||
solana_vote_program::vote_state::VoteState,
|
||||
std::{
|
||||
collections::HashMap,
|
||||
sync::{RwLock, RwLockReadGuard},
|
||||
sync::{Arc, RwLock, RwLockReadGuard},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -200,7 +200,7 @@ impl Stakes {
|
||||
.collect();
|
||||
|
||||
// overwrite vote accounts so that staked nodes singleton is reset
|
||||
self.vote_accounts = VoteAccounts::from(vote_accounts_for_next_epoch);
|
||||
self.vote_accounts = VoteAccounts::from(Arc::new(vote_accounts_for_next_epoch));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,8 +327,8 @@ impl Stakes {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vote_accounts(&self) -> &VoteAccountsHashMap {
|
||||
self.vote_accounts.as_ref()
|
||||
pub fn vote_accounts(&self) -> &VoteAccounts {
|
||||
&self.vote_accounts
|
||||
}
|
||||
|
||||
pub fn stake_delegations(&self) -> &HashMap<Pubkey, Delegation> {
|
||||
|
@@ -36,7 +36,7 @@ pub type VoteAccountsHashMap = HashMap<Pubkey, (/*stake:*/ u64, VoteAccount)>;
|
||||
|
||||
#[derive(Debug, AbiExample)]
|
||||
pub struct VoteAccounts {
|
||||
vote_accounts: VoteAccountsHashMap,
|
||||
vote_accounts: Arc<VoteAccountsHashMap>,
|
||||
// Inner Arc is meant to implement copy-on-write semantics as opposed to
|
||||
// sharing mutations (hence RwLock<Arc<...>> instead of Arc<RwLock<...>>).
|
||||
staked_nodes: RwLock<
|
||||
@@ -49,7 +49,7 @@ pub struct VoteAccounts {
|
||||
}
|
||||
|
||||
impl VoteAccount {
|
||||
pub fn lamports(&self) -> u64 {
|
||||
pub(crate) fn lamports(&self) -> u64 {
|
||||
self.0.account.lamports
|
||||
}
|
||||
|
||||
@@ -87,37 +87,43 @@ impl VoteAccounts {
|
||||
self.staked_nodes.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Pubkey, &(u64, VoteAccount))> {
|
||||
pub fn get(&self, pubkey: &Pubkey) -> Option<&(/*stake:*/ u64, VoteAccount)> {
|
||||
self.vote_accounts.get(pubkey)
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (&Pubkey, &(u64, VoteAccount))> {
|
||||
self.vote_accounts.iter()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, pubkey: Pubkey, (stake, vote_account): (u64, VoteAccount)) {
|
||||
pub(crate) fn insert(&mut self, pubkey: Pubkey, (stake, vote_account): (u64, VoteAccount)) {
|
||||
self.add_node_stake(stake, &vote_account);
|
||||
if let Some((stake, vote_account)) =
|
||||
self.vote_accounts.insert(pubkey, (stake, vote_account))
|
||||
{
|
||||
let vote_accounts = Arc::make_mut(&mut self.vote_accounts);
|
||||
if let Some((stake, vote_account)) = vote_accounts.insert(pubkey, (stake, vote_account)) {
|
||||
self.sub_node_stake(stake, &vote_account);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, pubkey: &Pubkey) -> Option<(u64, VoteAccount)> {
|
||||
let value = self.vote_accounts.remove(pubkey);
|
||||
if let Some((stake, ref vote_account)) = value {
|
||||
pub(crate) fn remove(&mut self, pubkey: &Pubkey) -> Option<(u64, VoteAccount)> {
|
||||
let vote_accounts = Arc::make_mut(&mut self.vote_accounts);
|
||||
let entry = vote_accounts.remove(pubkey);
|
||||
if let Some((stake, ref vote_account)) = entry {
|
||||
self.sub_node_stake(stake, vote_account);
|
||||
}
|
||||
value
|
||||
entry
|
||||
}
|
||||
|
||||
pub fn add_stake(&mut self, pubkey: &Pubkey, delta: u64) {
|
||||
if let Some((stake, vote_account)) = self.vote_accounts.get_mut(pubkey) {
|
||||
pub(crate) fn add_stake(&mut self, pubkey: &Pubkey, delta: u64) {
|
||||
let vote_accounts = Arc::make_mut(&mut self.vote_accounts);
|
||||
if let Some((stake, vote_account)) = vote_accounts.get_mut(pubkey) {
|
||||
*stake += delta;
|
||||
let vote_account = vote_account.clone();
|
||||
self.add_node_stake(delta, &vote_account);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub_stake(&mut self, pubkey: &Pubkey, delta: u64) {
|
||||
if let Some((stake, vote_account)) = self.vote_accounts.get_mut(pubkey) {
|
||||
pub(crate) fn sub_stake(&mut self, pubkey: &Pubkey, delta: u64) {
|
||||
let vote_accounts = Arc::make_mut(&mut self.vote_accounts);
|
||||
if let Some((stake, vote_account)) = vote_accounts.get_mut(pubkey) {
|
||||
*stake = stake
|
||||
.checked_sub(delta)
|
||||
.expect("subtraction value exceeds account's stake");
|
||||
@@ -223,7 +229,7 @@ impl PartialEq<VoteAccountInner> for VoteAccountInner {
|
||||
impl Default for VoteAccounts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
vote_accounts: HashMap::default(),
|
||||
vote_accounts: Arc::default(),
|
||||
staked_nodes: RwLock::default(),
|
||||
staked_nodes_once: Once::new(),
|
||||
}
|
||||
@@ -257,8 +263,8 @@ impl PartialEq<VoteAccounts> for VoteAccounts {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VoteAccountsHashMap> for VoteAccounts {
|
||||
fn from(vote_accounts: VoteAccountsHashMap) -> Self {
|
||||
impl From<Arc<VoteAccountsHashMap>> for VoteAccounts {
|
||||
fn from(vote_accounts: Arc<VoteAccountsHashMap>) -> Self {
|
||||
Self {
|
||||
vote_accounts,
|
||||
staked_nodes: RwLock::default(),
|
||||
@@ -273,12 +279,18 @@ impl AsRef<VoteAccountsHashMap> for VoteAccounts {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&VoteAccounts> for Arc<VoteAccountsHashMap> {
|
||||
fn from(vote_accounts: &VoteAccounts) -> Self {
|
||||
Arc::clone(&vote_accounts.vote_accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(Pubkey, (/*stake:*/ u64, VoteAccount))> for VoteAccounts {
|
||||
fn from_iter<I>(iter: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = (Pubkey, (u64, VoteAccount))>,
|
||||
{
|
||||
Self::from(HashMap::from_iter(iter))
|
||||
Self::from(Arc::new(HashMap::from_iter(iter)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +309,7 @@ impl<'de> Deserialize<'de> for VoteAccounts {
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let vote_accounts = VoteAccountsHashMap::deserialize(deserializer)?;
|
||||
Ok(Self::from(vote_accounts))
|
||||
Ok(Self::from(Arc::new(vote_accounts)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +444,7 @@ mod tests {
|
||||
let mut rng = rand::thread_rng();
|
||||
let vote_accounts_hash_map: HashMap<Pubkey, (u64, VoteAccount)> =
|
||||
new_rand_vote_accounts(&mut rng, 64).take(1024).collect();
|
||||
let vote_accounts = VoteAccounts::from(vote_accounts_hash_map.clone());
|
||||
let vote_accounts = VoteAccounts::from(Arc::new(vote_accounts_hash_map.clone()));
|
||||
assert!(vote_accounts.staked_nodes().len() > 32);
|
||||
assert_eq!(
|
||||
bincode::serialize(&vote_accounts).unwrap(),
|
||||
@@ -454,12 +466,12 @@ mod tests {
|
||||
let data = bincode::serialize(&vote_accounts_hash_map).unwrap();
|
||||
let vote_accounts: VoteAccounts = bincode::deserialize(&data).unwrap();
|
||||
assert!(vote_accounts.staked_nodes().len() > 32);
|
||||
assert_eq!(vote_accounts.vote_accounts, vote_accounts_hash_map);
|
||||
assert_eq!(*vote_accounts.vote_accounts, vote_accounts_hash_map);
|
||||
let data = bincode::options()
|
||||
.serialize(&vote_accounts_hash_map)
|
||||
.unwrap();
|
||||
let vote_accounts: VoteAccounts = bincode::options().deserialize(&data).unwrap();
|
||||
assert_eq!(vote_accounts.vote_accounts, vote_accounts_hash_map);
|
||||
assert_eq!(*vote_accounts.vote_accounts, vote_accounts_hash_map);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -512,4 +524,70 @@ mod tests {
|
||||
}
|
||||
assert!(vote_accounts.staked_nodes.read().unwrap().is_empty());
|
||||
}
|
||||
|
||||
// Asserts that returned staked-nodes are copy-on-write references.
|
||||
#[test]
|
||||
fn test_staked_nodes_cow() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut accounts = new_rand_vote_accounts(&mut rng, 64);
|
||||
// Add vote accounts.
|
||||
let mut vote_accounts = VoteAccounts::default();
|
||||
for (pubkey, (stake, vote_account)) in (&mut accounts).take(1024) {
|
||||
vote_accounts.insert(pubkey, (stake, vote_account));
|
||||
}
|
||||
let staked_nodes = vote_accounts.staked_nodes();
|
||||
let (pubkey, (more_stake, vote_account)) =
|
||||
accounts.find(|(_, (stake, _))| *stake != 0).unwrap();
|
||||
let node_pubkey = vote_account.node_pubkey().unwrap();
|
||||
vote_accounts.insert(pubkey, (more_stake, vote_account));
|
||||
assert_ne!(staked_nodes, vote_accounts.staked_nodes());
|
||||
assert_eq!(
|
||||
vote_accounts.staked_nodes()[&node_pubkey],
|
||||
more_stake + staked_nodes.get(&node_pubkey).copied().unwrap_or_default()
|
||||
);
|
||||
for (pubkey, stake) in vote_accounts.staked_nodes().iter() {
|
||||
if *pubkey != node_pubkey {
|
||||
assert_eq!(*stake, staked_nodes[pubkey]);
|
||||
} else {
|
||||
assert_eq!(
|
||||
*stake,
|
||||
more_stake + staked_nodes.get(pubkey).copied().unwrap_or_default()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Asserts that returned vote-accounts are copy-on-write references.
|
||||
#[test]
|
||||
fn test_vote_accounts_cow() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut accounts = new_rand_vote_accounts(&mut rng, 64);
|
||||
// Add vote accounts.
|
||||
let mut vote_accounts = VoteAccounts::default();
|
||||
for (pubkey, (stake, vote_account)) in (&mut accounts).take(1024) {
|
||||
vote_accounts.insert(pubkey, (stake, vote_account));
|
||||
}
|
||||
let vote_accounts_hashmap = Arc::<VoteAccountsHashMap>::from(&vote_accounts);
|
||||
assert_eq!(vote_accounts_hashmap, vote_accounts.vote_accounts);
|
||||
assert!(Arc::ptr_eq(
|
||||
&vote_accounts_hashmap,
|
||||
&vote_accounts.vote_accounts
|
||||
));
|
||||
let (pubkey, (more_stake, vote_account)) =
|
||||
accounts.find(|(_, (stake, _))| *stake != 0).unwrap();
|
||||
vote_accounts.insert(pubkey, (more_stake, vote_account.clone()));
|
||||
assert!(!Arc::ptr_eq(
|
||||
&vote_accounts_hashmap,
|
||||
&vote_accounts.vote_accounts
|
||||
));
|
||||
assert_ne!(vote_accounts_hashmap, vote_accounts.vote_accounts);
|
||||
let other = (more_stake, vote_account);
|
||||
for (pk, value) in vote_accounts.iter() {
|
||||
if *pk != pubkey {
|
||||
assert_eq!(value, &vote_accounts_hashmap[pk]);
|
||||
} else {
|
||||
assert_eq!(value, &other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user