implements copy-on-write for vote-accounts (#19362)

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<...>.
This commit is contained in:
behzad nouri
2021-08-30 15:54:01 +00:00
committed by GitHub
parent f19ff84593
commit 8ad52fa095
11 changed files with 215 additions and 175 deletions

View File

@ -29,7 +29,6 @@ use solana_runtime::{
bank_forks::BankForks,
commitment::VOTE_THRESHOLD_SIZE,
epoch_stakes::{EpochAuthorizedVoters, EpochStakes},
stakes::Stakes,
vote_sender_types::{ReplayVoteReceiver, ReplayedVote},
};
use solana_sdk::{
@ -601,7 +600,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)

View File

@ -183,8 +183,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() {
@ -193,7 +193,7 @@ impl AggregateCommitmentService {
&mut rooted_stake,
vote_state,
ancestors,
lamports,
*lamports,
);
}
}

View File

@ -198,17 +198,14 @@ impl Tower {
Self::new(node_pubkey, vote_account, root, &heaviest_bank)
}
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;
@ -217,7 +214,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;
}
@ -1270,56 +1268,60 @@ pub fn reconcile_blockstore_roots_with_tower(
#[cfg(test)]
pub mod test {
use super::*;
use crate::{
fork_choice::ForkChoice, heaviest_subtree_fork_choice::SlotHashKey,
replay_stage::HeaviestForkFailures, tower_storage::FileTowerStorage,
vote_simulator::VoteSimulator,
use {
super::*,
crate::{
fork_choice::ForkChoice, heaviest_subtree_fork_choice::SlotHashKey,
replay_stage::HeaviestForkFailures, tower_storage::FileTowerStorage,
vote_simulator::VoteSimulator,
},
itertools::Itertools,
solana_ledger::{blockstore::make_slot_entries, get_tmp_ledger_path},
solana_runtime::bank::Bank,
solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
clock::Slot,
hash::Hash,
pubkey::Pubkey,
signature::Signer,
slot_history::SlotHistory,
},
solana_vote_program::vote_state::{Vote, VoteStateVersions, MAX_LOCKOUT_HISTORY},
std::{
collections::HashMap,
fs::{remove_file, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
path::PathBuf,
sync::Arc,
},
tempfile::TempDir,
trees::tr,
};
use solana_ledger::{blockstore::make_slot_entries, get_tmp_ledger_path};
use solana_runtime::bank::Bank;
use solana_sdk::{
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
clock::Slot,
hash::Hash,
pubkey::Pubkey,
signature::Signer,
slot_history::SlotHistory,
};
use solana_vote_program::vote_state::{Vote, VoteStateVersions, MAX_LOCKOUT_HISTORY};
use std::{
collections::HashMap,
fs::{remove_file, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
path::PathBuf,
sync::Arc,
};
use tempfile::TempDir;
use trees::tr;
fn gen_stakes(stake_votes: &[(u64, &[u64])]) -> Vec<(Pubkey, (u64, VoteAccount))> {
let mut stakes = vec![];
for (lamports, votes) in stake_votes {
let mut account = AccountSharedData::from(Account {
data: vec![0; VoteState::size_of()],
lamports: *lamports,
..Account::default()
});
let mut vote_state = VoteState::default();
for slot in *votes {
vote_state.process_slot_vote_unchecked(*slot);
}
VoteState::serialize(
&VoteStateVersions::new_current(vote_state),
&mut account.data_as_mut_slice(),
)
.expect("serialize state");
stakes.push((
solana_sdk::pubkey::new_rand(),
(*lamports, VoteAccount::from(account)),
));
}
stakes
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,
..Account::default()
});
let mut vote_state = VoteState::default();
for slot in *votes {
vote_state.process_slot_vote_unchecked(*slot);
}
VoteState::serialize(
&VoteStateVersions::new_current(vote_state),
&mut account.data_as_mut_slice(),
)
.expect("serialize state");
(
solana_sdk::pubkey::new_rand(),
(*lamports, VoteAccount::from(account)),
)
})
.collect()
}
#[test]
@ -1964,10 +1966,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();
@ -1984,7 +1986,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,
@ -2004,10 +2006,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,
@ -2044,7 +2046,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,
@ -2340,7 +2342,7 @@ pub mod test {
} = Tower::collect_vote_lockouts(
&Pubkey::default(),
vote_to_evaluate,
accounts.clone().into_iter(),
&accounts,
&ancestors,
|_| None,
&mut LatestValidatorVotesForFrozenBanks::default(),
@ -2358,7 +2360,7 @@ pub mod test {
} = Tower::collect_vote_lockouts(
&Pubkey::default(),
vote_to_evaluate,
accounts.into_iter(),
&accounts,
&ancestors,
|_| None,
&mut LatestValidatorVotesForFrozenBanks::default(),

View File

@ -2169,7 +2169,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,

View File

@ -1544,7 +1544,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 {