implements copy-on-write for staked-nodes (#19090)

Bank::staked_nodes and Bank::epoch_staked_nodes redundantly clone
staked-nodes HashMap even though an immutable reference will suffice:
https://github.com/solana-labs/solana/blob/a9014cece/runtime/src/vote_account.rs#L77

This commit implements copy-on-write semantics for staked-nodes by
wrapping the underlying HashMap in Arc<...>.
This commit is contained in:
behzad nouri 2021-08-10 12:59:12 +00:00 committed by GitHub
parent c18bd08021
commit f302774cf7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 105 additions and 56 deletions

View File

@ -1690,7 +1690,7 @@ impl ClusterInfo {
Some(root_bank.feature_set.clone()), Some(root_bank.feature_set.clone()),
) )
} }
None => (HashMap::new(), None), None => (Arc::default(), None),
}; };
let require_stake_for_gossip = let require_stake_for_gossip =
self.require_stake_for_gossip(feature_set.as_deref(), &stakes); self.require_stake_for_gossip(feature_set.as_deref(), &stakes);
@ -2485,7 +2485,7 @@ impl ClusterInfo {
// feature does not roll back (if the feature happens to get enabled in // feature does not roll back (if the feature happens to get enabled in
// a minority fork). // a minority fork).
let (feature_set, stakes) = match bank_forks { let (feature_set, stakes) = match bank_forks {
None => (None, HashMap::default()), None => (None, Arc::default()),
Some(bank_forks) => { Some(bank_forks) => {
let bank = bank_forks.read().unwrap().root_bank(); let bank = bank_forks.read().unwrap().root_bank();
let feature_set = bank.feature_set.clone(); let feature_set = bank.feature_set.clone();

View File

@ -12,7 +12,10 @@ pub fn leader_schedule(epoch: Epoch, bank: &Bank) -> Option<LeaderSchedule> {
bank.epoch_staked_nodes(epoch).map(|stakes| { bank.epoch_staked_nodes(epoch).map(|stakes| {
let mut seed = [0u8; 32]; let mut seed = [0u8; 32];
seed[0..8].copy_from_slice(&epoch.to_le_bytes()); seed[0..8].copy_from_slice(&epoch.to_le_bytes());
let mut stakes: Vec<_> = stakes.into_iter().collect(); let mut stakes: Vec<_> = stakes
.iter()
.map(|(pubkey, stake)| (*pubkey, *stake))
.collect();
sort_stakes(&mut stakes); sort_stakes(&mut stakes);
LeaderSchedule::new( LeaderSchedule::new(
&stakes, &stakes,
@ -88,7 +91,11 @@ mod tests {
.genesis_config; .genesis_config;
let bank = Bank::new_for_tests(&genesis_config); let bank = Bank::new_for_tests(&genesis_config);
let pubkeys_and_stakes: Vec<_> = bank.staked_nodes().into_iter().collect(); let pubkeys_and_stakes: Vec<_> = bank
.staked_nodes()
.iter()
.map(|(pubkey, stake)| (*pubkey, *stake))
.collect();
let seed = [0u8; 32]; let seed = [0u8; 32];
let leader_schedule = LeaderSchedule::new( let leader_schedule = LeaderSchedule::new(
&pubkeys_and_stakes, &pubkeys_and_stakes,

View File

@ -5137,7 +5137,7 @@ impl Bank {
self.stakes.read().unwrap().stake_delegations().clone() self.stakes.read().unwrap().stake_delegations().clone()
} }
pub fn staked_nodes(&self) -> HashMap<Pubkey, u64> { pub fn staked_nodes(&self) -> Arc<HashMap<Pubkey, u64>> {
self.stakes.read().unwrap().staked_nodes() self.stakes.read().unwrap().staked_nodes()
} }
@ -5177,7 +5177,7 @@ impl Bank {
&self.epoch_stakes &self.epoch_stakes
} }
pub fn epoch_staked_nodes(&self, epoch: Epoch) -> Option<HashMap<Pubkey, u64>> { pub fn epoch_staked_nodes(&self, epoch: Epoch) -> Option<Arc<HashMap<Pubkey, u64>>> {
Some(self.epoch_stakes.get(&epoch)?.stakes().staked_nodes()) Some(self.epoch_stakes.get(&epoch)?.stakes().staked_nodes())
} }

View File

@ -1,19 +1,21 @@
//! Stakes serve as a cache of stake and vote accounts to derive //! Stakes serve as a cache of stake and vote accounts to derive
//! node stakes //! node stakes
use crate::vote_account::{ArcVoteAccount, VoteAccounts}; use {
use solana_sdk::{ crate::vote_account::{ArcVoteAccount, VoteAccounts},
account::{AccountSharedData, ReadableAccount}, solana_sdk::{
clock::Epoch, account::{AccountSharedData, ReadableAccount},
pubkey::Pubkey, clock::Epoch,
stake::{ pubkey::Pubkey,
self, stake::{
state::{Delegation, StakeState}, self,
state::{Delegation, StakeState},
},
sysvar::stake_history::StakeHistory,
}, },
sysvar::stake_history::StakeHistory, solana_stake_program::stake_state,
solana_vote_program::vote_state::VoteState,
std::{borrow::Borrow, collections::HashMap, sync::Arc},
}; };
use solana_stake_program::stake_state;
use solana_vote_program::vote_state::VoteState;
use std::{borrow::Borrow, collections::HashMap};
#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize, AbiExample)] #[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize, AbiExample)]
pub struct Stakes { pub struct Stakes {
@ -217,7 +219,7 @@ impl Stakes {
&self.stake_delegations &self.stake_delegations
} }
pub fn staked_nodes(&self) -> HashMap<Pubkey, u64> { pub fn staked_nodes(&self) -> Arc<HashMap<Pubkey, u64>> {
self.vote_accounts.staked_nodes() self.vote_accounts.staked_nodes()
} }

View File

@ -1,16 +1,19 @@
use serde::de::{Deserialize, Deserializer}; use {
use serde::ser::{Serialize, Serializer}; itertools::Itertools,
use solana_sdk::{ serde::de::{Deserialize, Deserializer},
account::Account, account::AccountSharedData, instruction::InstructionError, pubkey::Pubkey, serde::ser::{Serialize, Serializer},
}; solana_sdk::{
use solana_vote_program::vote_state::VoteState; account::Account, account::AccountSharedData, instruction::InstructionError, pubkey::Pubkey,
use std::{ },
borrow::Borrow, solana_vote_program::vote_state::VoteState,
cmp::Ordering, std::{
collections::{hash_map::Entry, HashMap}, borrow::Borrow,
iter::FromIterator, cmp::Ordering,
ops::Deref, collections::{hash_map::Entry, HashMap},
sync::{Arc, Once, RwLock, RwLockReadGuard}, iter::FromIterator,
ops::Deref,
sync::{Arc, Once, RwLock, RwLockReadGuard},
},
}; };
// The value here does not matter. It will be overwritten // The value here does not matter. It will be overwritten
@ -31,10 +34,14 @@ pub struct VoteAccount {
#[derive(Debug, AbiExample)] #[derive(Debug, AbiExample)]
pub struct VoteAccounts { pub struct VoteAccounts {
vote_accounts: HashMap<Pubkey, (u64 /*stake*/, ArcVoteAccount)>, vote_accounts: HashMap<Pubkey, (u64 /*stake*/, ArcVoteAccount)>,
// 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< staked_nodes: RwLock<
HashMap< Arc<
Pubkey, // VoteAccount.vote_state.node_pubkey. HashMap<
u64, // Total stake across all vote-accounts. Pubkey, // VoteAccount.vote_state.node_pubkey.
u64, // Total stake across all vote-accounts.
>,
>, >,
>, >,
staked_nodes_once: Once, staked_nodes_once: Once,
@ -59,20 +66,19 @@ impl VoteAccount {
} }
impl VoteAccounts { impl VoteAccounts {
pub fn staked_nodes(&self) -> HashMap<Pubkey, u64> { pub fn staked_nodes(&self) -> Arc<HashMap<Pubkey, u64>> {
self.staked_nodes_once.call_once(|| { self.staked_nodes_once.call_once(|| {
let mut staked_nodes = HashMap::new(); let staked_nodes = self
for (stake, vote_account) in .vote_accounts
self.vote_accounts.values().filter(|(stake, _)| *stake != 0) .values()
{ .filter(|(stake, _)| *stake != 0)
if let Some(node_pubkey) = vote_account.node_pubkey() { .filter_map(|(stake, vote_account)| {
staked_nodes let node_pubkey = vote_account.node_pubkey()?;
.entry(node_pubkey) Some((node_pubkey, stake))
.and_modify(|s| *s += *stake) })
.or_insert(*stake); .into_grouping_map()
} .aggregate(|acc, _node_pubkey, stake| Some(acc.unwrap_or_default() + stake));
} *self.staked_nodes.write().unwrap() = Arc::new(staked_nodes)
*self.staked_nodes.write().unwrap() = staked_nodes
}); });
self.staked_nodes.read().unwrap().clone() self.staked_nodes.read().unwrap().clone()
} }
@ -119,9 +125,9 @@ impl VoteAccounts {
fn add_node_stake(&mut self, stake: u64, vote_account: &ArcVoteAccount) { fn add_node_stake(&mut self, stake: u64, vote_account: &ArcVoteAccount) {
if stake != 0 && self.staked_nodes_once.is_completed() { if stake != 0 && self.staked_nodes_once.is_completed() {
if let Some(node_pubkey) = vote_account.node_pubkey() { if let Some(node_pubkey) = vote_account.node_pubkey() {
self.staked_nodes let mut staked_nodes = self.staked_nodes.write().unwrap();
.write() let staked_nodes = Arc::make_mut(&mut staked_nodes);
.unwrap() staked_nodes
.entry(node_pubkey) .entry(node_pubkey)
.and_modify(|s| *s += stake) .and_modify(|s| *s += stake)
.or_insert(stake); .or_insert(stake);
@ -132,7 +138,9 @@ impl VoteAccounts {
fn sub_node_stake(&mut self, stake: u64, vote_account: &ArcVoteAccount) { fn sub_node_stake(&mut self, stake: u64, vote_account: &ArcVoteAccount) {
if stake != 0 && self.staked_nodes_once.is_completed() { if stake != 0 && self.staked_nodes_once.is_completed() {
if let Some(node_pubkey) = vote_account.node_pubkey() { if let Some(node_pubkey) = vote_account.node_pubkey() {
match self.staked_nodes.write().unwrap().entry(node_pubkey) { let mut staked_nodes = self.staked_nodes.write().unwrap();
let staked_nodes = Arc::make_mut(&mut staked_nodes);
match staked_nodes.entry(node_pubkey) {
Entry::Vacant(_) => panic!("this should not happen!"), Entry::Vacant(_) => panic!("this should not happen!"),
Entry::Occupied(mut entry) => match entry.get().cmp(&stake) { Entry::Occupied(mut entry) => match entry.get().cmp(&stake) {
Ordering::Less => panic!("subtraction value exceeds node's stake"), Ordering::Less => panic!("subtraction value exceeds node's stake"),
@ -474,7 +482,7 @@ mod tests {
if (k + 1) % 128 == 0 { if (k + 1) % 128 == 0 {
assert_eq!( assert_eq!(
staked_nodes(&accounts[..k + 1]), staked_nodes(&accounts[..k + 1]),
vote_accounts.staked_nodes() *vote_accounts.staked_nodes()
); );
} }
} }
@ -484,7 +492,7 @@ mod tests {
let (pubkey, (_, _)) = accounts.swap_remove(index); let (pubkey, (_, _)) = accounts.swap_remove(index);
vote_accounts.remove(&pubkey); vote_accounts.remove(&pubkey);
if (k + 1) % 32 == 0 { if (k + 1) % 32 == 0 {
assert_eq!(staked_nodes(&accounts), vote_accounts.staked_nodes()); assert_eq!(staked_nodes(&accounts), *vote_accounts.staked_nodes());
} }
} }
// Modify the stakes for some of the accounts. // Modify the stakes for some of the accounts.
@ -499,7 +507,7 @@ mod tests {
} }
*stake = new_stake; *stake = new_stake;
if (k + 1) % 128 == 0 { if (k + 1) % 128 == 0 {
assert_eq!(staked_nodes(&accounts), vote_accounts.staked_nodes()); assert_eq!(staked_nodes(&accounts), *vote_accounts.staked_nodes());
} }
} }
// Remove everything. // Remove everything.
@ -508,9 +516,41 @@ mod tests {
let (pubkey, (_, _)) = accounts.swap_remove(index); let (pubkey, (_, _)) = accounts.swap_remove(index);
vote_accounts.remove(&pubkey); vote_accounts.remove(&pubkey);
if accounts.len() % 32 == 0 { if accounts.len() % 32 == 0 {
assert_eq!(staked_nodes(&accounts), vote_accounts.staked_nodes()); assert_eq!(staked_nodes(&accounts), *vote_accounts.staked_nodes());
} }
} }
assert!(vote_accounts.staked_nodes.read().unwrap().is_empty()); 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()
);
}
}
}
} }