diff --git a/core/src/broadcast_stage.rs b/core/src/broadcast_stage.rs index d76cb8c125..484dc0e39f 100644 --- a/core/src/broadcast_stage.rs +++ b/core/src/broadcast_stage.rs @@ -17,7 +17,7 @@ use crossbeam_channel::{ Receiver as CrossbeamReceiver, RecvTimeoutError as CrossbeamRecvTimeoutError, Sender as CrossbeamSender, }; -use solana_ledger::{blockstore::Blockstore, shred::Shred, staking_utils}; +use solana_ledger::{blockstore::Blockstore, shred::Shred}; use solana_measure::measure::Measure; use solana_metrics::{inc_new_counter_error, inc_new_counter_info}; use solana_runtime::bank::Bank; @@ -306,7 +306,7 @@ impl BroadcastStage { for (_, bank) in retransmit_slots.iter() { let bank_epoch = bank.get_leader_schedule_epoch(bank.slot()); - let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch); + let stakes = bank.epoch_staked_nodes(bank_epoch); let stakes = stakes.map(Arc::new); let data_shreds = Arc::new( blockstore diff --git a/core/src/broadcast_stage/fail_entry_verification_broadcast_run.rs b/core/src/broadcast_stage/fail_entry_verification_broadcast_run.rs index a53596bd03..fcfd39104b 100644 --- a/core/src/broadcast_stage/fail_entry_verification_broadcast_run.rs +++ b/core/src/broadcast_stage/fail_entry_verification_broadcast_run.rs @@ -102,7 +102,7 @@ impl BroadcastRun for FailEntryVerificationBroadcastRun { blockstore_sender.send((data_shreds.clone(), None))?; // 4) Start broadcast step let bank_epoch = bank.get_leader_schedule_epoch(bank.slot()); - let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch); + let stakes = bank.epoch_staked_nodes(bank_epoch); let stakes = stakes.map(Arc::new); socket_sender.send(((stakes.clone(), data_shreds), None))?; if let Some((good_last_data_shred, bad_last_data_shred)) = last_shreds { diff --git a/core/src/broadcast_stage/standard_broadcast_run.rs b/core/src/broadcast_stage/standard_broadcast_run.rs index e78626db64..fa91dcd9e6 100644 --- a/core/src/broadcast_stage/standard_broadcast_run.rs +++ b/core/src/broadcast_stage/standard_broadcast_run.rs @@ -213,7 +213,7 @@ impl StandardBroadcastRun { let mut get_leader_schedule_time = Measure::start("broadcast_get_leader_schedule"); let bank_epoch = bank.get_leader_schedule_epoch(bank.slot()); - let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch); + let stakes = bank.epoch_staked_nodes(bank_epoch); let stakes = stakes.map(Arc::new); // Broadcast the last shred of the interrupted slot if necessary diff --git a/core/src/cluster_info.rs b/core/src/cluster_info.rs index b9beda7fce..37835fbf8b 100644 --- a/core/src/cluster_info.rs +++ b/core/src/cluster_info.rs @@ -39,7 +39,6 @@ use itertools::Itertools; use rayon::prelude::*; use rayon::{ThreadPool, ThreadPoolBuilder}; use serde::ser::Serialize; -use solana_ledger::staking_utils; use solana_measure::measure::Measure; use solana_measure::thread_mem_usage; use solana_metrics::{inc_new_counter_debug, inc_new_counter_error}; @@ -1834,7 +1833,7 @@ impl ClusterInfo { let stakes: HashMap<_, _> = match bank_forks { Some(ref bank_forks) => { - staking_utils::staked_nodes(&bank_forks.read().unwrap().working_bank()) + bank_forks.read().unwrap().working_bank().staked_nodes() } None => HashMap::new(), }; @@ -2492,7 +2491,7 @@ impl ClusterInfo { let epoch = bank.epoch(); let epoch_schedule = bank.epoch_schedule(); epoch_time_ms = epoch_schedule.get_slots_in_epoch(epoch) * DEFAULT_MS_PER_SLOT; - staking_utils::staked_nodes(&bank) + bank.staked_nodes() } None => { inc_new_counter_info!("cluster_info-purge-no_working_bank", 1); diff --git a/core/src/retransmit_stage.rs b/core/src/retransmit_stage.rs index f0c6c60c28..7b482d7e54 100644 --- a/core/src/retransmit_stage.rs +++ b/core/src/retransmit_stage.rs @@ -19,7 +19,6 @@ use solana_ledger::shred::{get_shred_slot_index_type, ShredFetchStats}; use solana_ledger::{ blockstore::{Blockstore, CompletedSlotsReceiver}, leader_schedule_cache::LeaderScheduleCache, - staking_utils, }; use solana_measure::measure::Measure; use solana_metrics::inc_new_counter_error; @@ -278,7 +277,7 @@ fn retransmit( drop(r_epoch_stakes_cache); let mut w_epoch_stakes_cache = epoch_stakes_cache.write().unwrap(); if w_epoch_stakes_cache.epoch != bank_epoch { - let stakes = staking_utils::staked_nodes_at_epoch(&r_bank, bank_epoch); + let stakes = r_bank.epoch_staked_nodes(bank_epoch); let stakes = stakes.map(Arc::new); w_epoch_stakes_cache.stakes = stakes; w_epoch_stakes_cache.epoch = bank_epoch; diff --git a/ledger/src/leader_schedule_utils.rs b/ledger/src/leader_schedule_utils.rs index 4d53567934..2789333bc7 100644 --- a/ledger/src/leader_schedule_utils.rs +++ b/ledger/src/leader_schedule_utils.rs @@ -1,5 +1,4 @@ use crate::leader_schedule::LeaderSchedule; -use crate::staking_utils; use solana_runtime::bank::Bank; use solana_sdk::{ clock::{Epoch, Slot, NUM_CONSECUTIVE_LEADER_SLOTS}, @@ -8,7 +7,7 @@ use solana_sdk::{ /// Return the leader schedule for the given epoch. pub fn leader_schedule(epoch: Epoch, bank: &Bank) -> Option { - staking_utils::staked_nodes_at_epoch(bank, epoch).map(|stakes| { + bank.epoch_staked_nodes(epoch).map(|stakes| { let mut seed = [0u8; 32]; seed[0..8].copy_from_slice(&epoch.to_le_bytes()); let mut stakes: Vec<_> = stakes.into_iter().collect(); @@ -66,7 +65,7 @@ mod tests { .genesis_config; let bank = Bank::new(&genesis_config); - let pubkeys_and_stakes: Vec<_> = staking_utils::staked_nodes(&bank).into_iter().collect(); + let pubkeys_and_stakes: Vec<_> = bank.staked_nodes().into_iter().collect(); let seed = [0u8; 32]; let leader_schedule = LeaderSchedule::new( &pubkeys_and_stakes, diff --git a/ledger/src/staking_utils.rs b/ledger/src/staking_utils.rs index 7436d55c51..c407a7cdf0 100644 --- a/ledger/src/staking_utils.rs +++ b/ledger/src/staking_utils.rs @@ -1,9 +1,9 @@ -use solana_runtime::{bank::Bank, vote_account::ArcVoteAccount}; +use solana_runtime::bank::Bank; use solana_sdk::{ clock::{Epoch, Slot}, pubkey::Pubkey, }; -use std::{borrow::Borrow, collections::HashMap}; +use std::collections::HashMap; /// Looks through vote accounts, and finds the latest slot that has achieved /// supermajority lockout @@ -24,36 +24,6 @@ pub fn vote_account_stakes(bank: &Bank) -> HashMap { .collect() } -/// Collect the staked nodes, as named by staked vote accounts from the given bank -pub fn staked_nodes(bank: &Bank) -> HashMap { - to_staked_nodes(bank.vote_accounts()) -} - -/// At the specified epoch, collect the delegate account balance and vote states for delegates -/// that have non-zero balance in any of their managed staking accounts -pub fn staked_nodes_at_epoch(bank: &Bank, epoch: Epoch) -> Option> { - bank.epoch_vote_accounts(epoch).map(to_staked_nodes) -} - -fn to_staked_nodes( - vote_accounts: I, -) -> HashMap -where - I: IntoIterator, - V: Borrow<(u64 /*stake*/, ArcVoteAccount)>, -{ - let mut out: HashMap = HashMap::new(); - for (_ /*vote pubkey*/, stake_vote_account) in vote_accounts { - let (stake, vote_account) = stake_vote_account.borrow(); - if let Ok(vote_state) = vote_account.vote_state().as_ref() { - out.entry(vote_state.node_pubkey) - .and_modify(|s| *s += *stake) - .or_insert(*stake); - } - } - out -} - fn epoch_stakes_and_lockouts(bank: &Bank, epoch: Epoch) -> Vec<(u64, Option)> { bank.epoch_vote_accounts(epoch) .expect("Bank state for epoch is missing") @@ -96,6 +66,7 @@ pub(crate) mod tests { bootstrap_validator_stake_lamports, create_genesis_config, GenesisConfigInfo, }; use rand::Rng; + use solana_runtime::vote_account::{ArcVoteAccount, VoteAccounts}; use solana_sdk::{ account::{from_account, Account}, clock::Clock, @@ -347,7 +318,7 @@ pub(crate) mod tests { let vote_pubkey = Pubkey::new_unique(); (vote_pubkey, (stake, ArcVoteAccount::from(account))) }); - let result = to_staked_nodes(vote_accounts); + let result = vote_accounts.collect::().staked_nodes(); assert_eq!(result.len(), 2); assert_eq!(result[&node1], 3); assert_eq!(result[&node2], 5); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index a74677502c..773d57a30a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -4293,6 +4293,10 @@ impl Bank { self.stakes.read().unwrap().stake_delegations().clone() } + pub fn staked_nodes(&self) -> HashMap { + self.stakes.read().unwrap().staked_nodes() + } + /// current vote accounts for this bank along with the stake /// attributed to each account /// Note: This clones the entire vote-accounts hashmap. For a single @@ -4323,6 +4327,10 @@ impl Bank { &self.epoch_stakes } + pub fn epoch_staked_nodes(&self, epoch: Epoch) -> Option> { + Some(self.epoch_stakes.get(&epoch)?.stakes().staked_nodes()) + } + /// vote accounts for the specific epoch along with the stake /// attributed to each account pub fn epoch_vote_accounts( diff --git a/runtime/src/serde_snapshot/tests.rs b/runtime/src/serde_snapshot/tests.rs index 6f3879dbb8..e179d25f83 100644 --- a/runtime/src/serde_snapshot/tests.rs +++ b/runtime/src/serde_snapshot/tests.rs @@ -261,7 +261,7 @@ mod test_bank_serialize { // These some what long test harness is required to freeze the ABI of // Bank's serialization due to versioned nature - #[frozen_abi(digest = "5NHt6PLRJPWJH9FUcweSsUWgN5hXMfXj1BduDrDHH73w")] + #[frozen_abi(digest = "Gv3em1cZt9cjQWepg8C5aaK95deyA1fifowRfmmTuoES")] #[derive(Serialize, AbiExample)] pub struct BankAbiTestWrapperFuture { #[serde(serialize_with = "wrapper_future")] diff --git a/runtime/src/stakes.rs b/runtime/src/stakes.rs index 00abfb8880..daf6e9e6c5 100644 --- a/runtime/src/stakes.rs +++ b/runtime/src/stakes.rs @@ -1,16 +1,16 @@ //! Stakes serve as a cache of stake and vote accounts to derive //! node stakes -use crate::vote_account::ArcVoteAccount; +use crate::vote_account::{ArcVoteAccount, VoteAccounts}; use solana_sdk::{ account::Account, clock::Epoch, pubkey::Pubkey, sysvar::stake_history::StakeHistory, }; use solana_stake_program::stake_state::{new_stake_history_entry, Delegation, StakeState}; -use std::collections::HashMap; +use std::{borrow::Borrow, collections::HashMap}; #[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize, AbiExample)] pub struct Stakes { /// vote accounts - vote_accounts: HashMap, + vote_accounts: VoteAccounts, /// stake_delegations stake_delegations: HashMap, @@ -52,19 +52,14 @@ impl Stakes { let vote_accounts_for_next_epoch = self .vote_accounts .iter() - .map(|(pubkey, (_stake, account))| { - ( - *pubkey, - ( - self.calculate_stake( - pubkey, - next_epoch, - Some(&stake_history_upto_prev_epoch), - fix_stake_deactivate, - ), - account.clone(), - ), - ) + .map(|(pubkey, (_ /*stake*/, account))| { + let stake = self.calculate_stake( + pubkey, + next_epoch, + Some(&stake_history_upto_prev_epoch), + fix_stake_deactivate, + ); + (*pubkey, (stake, account.clone())) }) .collect(); @@ -179,14 +174,10 @@ impl Stakes { // if adjustments need to be made... if stake != old_stake { if let Some((voter_pubkey, stake)) = old_stake { - self.vote_accounts - .entry(voter_pubkey) - .and_modify(|e| e.0 -= stake); + self.vote_accounts.sub_stake(&voter_pubkey, stake); } if let Some((voter_pubkey, stake)) = stake { - self.vote_accounts - .entry(voter_pubkey) - .and_modify(|e| e.0 += stake); + self.vote_accounts.add_stake(&voter_pubkey, stake); } } @@ -209,13 +200,17 @@ impl Stakes { } pub fn vote_accounts(&self) -> &HashMap { - &self.vote_accounts + self.vote_accounts.borrow() } pub fn stake_delegations(&self) -> &HashMap { &self.stake_delegations } + pub fn staked_nodes(&self) -> HashMap { + self.vote_accounts.staked_nodes() + } + pub fn highest_staked_node(&self) -> Option { let (_pubkey, (_stake, vote_account)) = self .vote_accounts diff --git a/runtime/src/vote_account.rs b/runtime/src/vote_account.rs index d6eea0d948..c0dd52a9f2 100644 --- a/runtime/src/vote_account.rs +++ b/runtime/src/vote_account.rs @@ -1,9 +1,15 @@ use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; -use solana_sdk::{account::Account, instruction::InstructionError}; +use solana_sdk::{account::Account, instruction::InstructionError, pubkey::Pubkey}; use solana_vote_program::vote_state::VoteState; -use std::ops::Deref; -use std::sync::{Arc, Once, RwLock, RwLockReadGuard}; +use std::{ + borrow::Borrow, + cmp::Ordering, + collections::{hash_map::Entry, HashMap}, + iter::FromIterator, + ops::Deref, + sync::{Arc, Once, RwLock, RwLockReadGuard}, +}; // The value here does not matter. It will be overwritten // at the first call to VoteAccount::vote_state(). @@ -20,6 +26,18 @@ pub struct VoteAccount { vote_state_once: Once, } +#[derive(Debug, AbiExample)] +pub struct VoteAccounts { + vote_accounts: HashMap, + staked_nodes: RwLock< + HashMap< + Pubkey, // VoteAccount.vote_state.node_pubkey. + u64, // Total stake across all vote-accounts. + >, + >, + staked_nodes_once: Once, +} + impl VoteAccount { pub fn lamports(&self) -> u64 { self.account.lamports @@ -31,6 +49,100 @@ impl VoteAccount { }); self.vote_state.read().unwrap() } + + /// VoteState.node_pubkey of this vote-account. + fn node_pubkey(&self) -> Option { + Some(self.vote_state().as_ref().ok()?.node_pubkey) + } +} + +impl VoteAccounts { + pub fn staked_nodes(&self) -> HashMap { + self.staked_nodes_once.call_once(|| { + let mut staked_nodes = HashMap::new(); + for (stake, vote_account) in + self.vote_accounts.values().filter(|(stake, _)| *stake != 0) + { + if let Some(node_pubkey) = vote_account.node_pubkey() { + staked_nodes + .entry(node_pubkey) + .and_modify(|s| *s += *stake) + .or_insert(*stake); + } + } + *self.staked_nodes.write().unwrap() = staked_nodes + }); + self.staked_nodes.read().unwrap().clone() + } + + pub fn iter(&self) -> impl Iterator { + self.vote_accounts.iter() + } + + pub fn insert(&mut self, pubkey: Pubkey, (stake, vote_account): (u64, ArcVoteAccount)) { + self.add_node_stake(stake, &vote_account); + if let Some((stake, vote_account)) = + self.vote_accounts.insert(pubkey, (stake, vote_account)) + { + self.sub_node_stake(stake, &vote_account); + } + } + + pub fn remove(&mut self, pubkey: &Pubkey) -> Option<(u64, ArcVoteAccount)> { + let value = self.vote_accounts.remove(pubkey); + if let Some((stake, ref vote_account)) = value { + self.sub_node_stake(stake, vote_account); + } + value + } + + pub fn add_stake(&mut self, pubkey: &Pubkey, delta: u64) { + if let Some((stake, vote_account)) = self.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) { + *stake = stake + .checked_sub(delta) + .expect("subtraction value exceeds account's stake"); + let vote_account = vote_account.clone(); + self.sub_node_stake(delta, &vote_account); + } + } + + fn add_node_stake(&mut self, stake: u64, vote_account: &ArcVoteAccount) { + if stake != 0 && self.staked_nodes_once.is_completed() { + if let Some(node_pubkey) = vote_account.node_pubkey() { + self.staked_nodes + .write() + .unwrap() + .entry(node_pubkey) + .and_modify(|s| *s += stake) + .or_insert(stake); + } + } + } + + fn sub_node_stake(&mut self, stake: u64, vote_account: &ArcVoteAccount) { + if stake != 0 && self.staked_nodes_once.is_completed() { + if let Some(node_pubkey) = vote_account.node_pubkey() { + match self.staked_nodes.write().unwrap().entry(node_pubkey) { + Entry::Vacant(_) => panic!("this should not happen!"), + Entry::Occupied(mut entry) => match entry.get().cmp(&stake) { + Ordering::Less => panic!("subtraction value exceeds node's stake"), + Ordering::Equal => { + entry.remove_entry(); + } + Ordering::Greater => *entry.get_mut() -= stake, + }, + } + } + } + } } impl Deref for ArcVoteAccount { @@ -92,16 +204,104 @@ impl PartialEq for VoteAccount { } } +impl Default for VoteAccounts { + fn default() -> Self { + Self { + vote_accounts: HashMap::default(), + staked_nodes: RwLock::default(), + staked_nodes_once: Once::new(), + } + } +} + +impl Clone for VoteAccounts { + fn clone(&self) -> Self { + if self.staked_nodes_once.is_completed() { + let staked_nodes = self.staked_nodes.read().unwrap().clone(); + let other = Self { + vote_accounts: self.vote_accounts.clone(), + staked_nodes: RwLock::new(staked_nodes), + staked_nodes_once: Once::new(), + }; + other.staked_nodes_once.call_once(|| {}); + other + } else { + Self { + vote_accounts: self.vote_accounts.clone(), + staked_nodes: RwLock::default(), + staked_nodes_once: Once::new(), + } + } + } +} + +impl PartialEq for VoteAccounts { + fn eq(&self, other: &Self) -> bool { + self.vote_accounts == other.vote_accounts + } +} + +type VoteAccountsHashMap = HashMap; + +impl From for VoteAccounts { + fn from(vote_accounts: VoteAccountsHashMap) -> Self { + Self { + vote_accounts, + staked_nodes: RwLock::default(), + staked_nodes_once: Once::new(), + } + } +} + +impl Borrow for VoteAccounts { + fn borrow(&self) -> &VoteAccountsHashMap { + &self.vote_accounts + } +} + +impl FromIterator<(Pubkey, (u64 /*stake*/, ArcVoteAccount))> for VoteAccounts { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + Self::from(HashMap::from_iter(iter)) + } +} + +impl Serialize for VoteAccounts { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.vote_accounts.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for VoteAccounts { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let vote_accounts = VoteAccountsHashMap::deserialize(deserializer)?; + Ok(Self::from(vote_accounts)) + } +} + #[cfg(test)] mod tests { use super::*; + use bincode::Options; use rand::Rng; use solana_sdk::{pubkey::Pubkey, sysvar::clock::Clock}; use solana_vote_program::vote_state::{VoteInit, VoteStateVersions}; + use std::iter::repeat_with; - fn new_rand_vote_account(rng: &mut R) -> (Account, VoteState) { + fn new_rand_vote_account( + rng: &mut R, + node_pubkey: Option, + ) -> (Account, VoteState) { let vote_init = VoteInit { - node_pubkey: Pubkey::new_unique(), + node_pubkey: node_pubkey.unwrap_or_else(Pubkey::new_unique), authorized_voter: Pubkey::new_unique(), authorized_withdrawer: Pubkey::new_unique(), commission: rng.gen(), @@ -123,10 +323,42 @@ mod tests { (account, vote_state) } + fn new_rand_vote_accounts( + rng: &mut R, + num_nodes: usize, + ) -> impl Iterator + '_ { + let nodes: Vec<_> = repeat_with(Pubkey::new_unique).take(num_nodes).collect(); + repeat_with(move || { + let node = nodes[rng.gen_range(0, nodes.len())]; + let (account, _) = new_rand_vote_account(rng, Some(node)); + let stake = rng.gen_range(0, 997); + (Pubkey::new_unique(), (stake, ArcVoteAccount::from(account))) + }) + } + + fn staked_nodes<'a, I>(vote_accounts: I) -> HashMap + where + I: IntoIterator, + { + let mut staked_nodes = HashMap::new(); + for (_, (stake, vote_account)) in vote_accounts + .into_iter() + .filter(|(_, (stake, _))| *stake != 0) + { + if let Some(node_pubkey) = vote_account.node_pubkey() { + staked_nodes + .entry(node_pubkey) + .and_modify(|s| *s += *stake) + .or_insert(*stake); + } + } + staked_nodes + } + #[test] fn test_vote_account() { let mut rng = rand::thread_rng(); - let (account, vote_state) = new_rand_vote_account(&mut rng); + let (account, vote_state) = new_rand_vote_account(&mut rng, None); let lamports = account.lamports; let vote_account = ArcVoteAccount::from(account); assert_eq!(lamports, vote_account.lamports()); @@ -138,7 +370,7 @@ mod tests { #[test] fn test_vote_account_serialize() { let mut rng = rand::thread_rng(); - let (account, vote_state) = new_rand_vote_account(&mut rng); + let (account, vote_state) = new_rand_vote_account(&mut rng, None); let vote_account = ArcVoteAccount::from(account.clone()); assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap()); // Assert than ArcVoteAccount has the same wire format as Account. @@ -151,7 +383,7 @@ mod tests { #[test] fn test_vote_account_deserialize() { let mut rng = rand::thread_rng(); - let (account, vote_state) = new_rand_vote_account(&mut rng); + let (account, vote_state) = new_rand_vote_account(&mut rng, None); let data = bincode::serialize(&account).unwrap(); let vote_account = ArcVoteAccount::from(account); assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap()); @@ -166,7 +398,7 @@ mod tests { #[test] fn test_vote_account_round_trip() { let mut rng = rand::thread_rng(); - let (account, vote_state) = new_rand_vote_account(&mut rng); + let (account, vote_state) = new_rand_vote_account(&mut rng, None); let vote_account = ArcVoteAccount::from(account); assert_eq!(vote_state, *vote_account.vote_state().as_ref().unwrap()); let data = bincode::serialize(&vote_account).unwrap(); @@ -178,4 +410,90 @@ mod tests { *other_vote_account.vote_state().as_ref().unwrap() ); } + + #[test] + fn test_vote_accounts_serialize() { + let mut rng = rand::thread_rng(); + let vote_accounts_hash_map: HashMap = + new_rand_vote_accounts(&mut rng, 64).take(1024).collect(); + let vote_accounts = VoteAccounts::from(vote_accounts_hash_map.clone()); + assert!(vote_accounts.staked_nodes().len() > 32); + assert_eq!( + bincode::serialize(&vote_accounts).unwrap(), + bincode::serialize(&vote_accounts_hash_map).unwrap(), + ); + assert_eq!( + bincode::options().serialize(&vote_accounts).unwrap(), + bincode::options() + .serialize(&vote_accounts_hash_map) + .unwrap(), + ) + } + + #[test] + fn test_vote_accounts_deserialize() { + let mut rng = rand::thread_rng(); + let vote_accounts_hash_map: HashMap = + new_rand_vote_accounts(&mut rng, 64).take(1024).collect(); + 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); + 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); + } + + #[test] + fn test_staked_nodes() { + let mut rng = rand::thread_rng(); + let mut accounts: Vec<_> = new_rand_vote_accounts(&mut rng, 64).take(1024).collect(); + let mut vote_accounts = VoteAccounts::default(); + // Add vote accounts. + for (k, (pubkey, (stake, vote_account))) in accounts.iter().enumerate() { + vote_accounts.insert(*pubkey, (*stake, vote_account.clone())); + if (k + 1) % 128 == 0 { + assert_eq!( + staked_nodes(&accounts[..k + 1]), + vote_accounts.staked_nodes() + ); + } + } + // Remove some of the vote accounts. + for k in 0..256 { + let index = rng.gen_range(0, accounts.len()); + let (pubkey, (_, _)) = accounts.swap_remove(index); + vote_accounts.remove(&pubkey); + if (k + 1) % 32 == 0 { + assert_eq!(staked_nodes(&accounts), vote_accounts.staked_nodes()); + } + } + // Modify the stakes for some of the accounts. + for k in 0..2048 { + let index = rng.gen_range(0, accounts.len()); + let (pubkey, (stake, _)) = &mut accounts[index]; + let new_stake = rng.gen_range(0, 997); + if new_stake < *stake { + vote_accounts.sub_stake(pubkey, *stake - new_stake); + } else { + vote_accounts.add_stake(pubkey, new_stake - *stake); + } + *stake = new_stake; + if (k + 1) % 128 == 0 { + assert_eq!(staked_nodes(&accounts), vote_accounts.staked_nodes()); + } + } + // Remove everything. + while !accounts.is_empty() { + let index = rng.gen_range(0, accounts.len()); + let (pubkey, (_, _)) = accounts.swap_remove(index); + vote_accounts.remove(&pubkey); + if accounts.len() % 32 == 0 { + assert_eq!(staked_nodes(&accounts), vote_accounts.staked_nodes()); + } + } + assert!(vote_accounts.staked_nodes.read().unwrap().is_empty()); + } }