diff --git a/core/src/locktower.rs b/core/src/locktower.rs index eb248b4b29..c7c33c434d 100644 --- a/core/src/locktower.rs +++ b/core/src/locktower.rs @@ -54,13 +54,13 @@ impl EpochStakes { &Pubkey::default(), ) } - pub fn new_from_stake_accounts(epoch: u64, accounts: &[(Pubkey, Account)]) -> Self { - let stakes = accounts.iter().map(|(k, v)| (*k, v.lamports)).collect(); + pub fn new_from_stakes(epoch: u64, accounts: &[(Pubkey, (u64, Account))]) -> Self { + let stakes = accounts.iter().map(|(k, (v, _))| (*k, *v)).collect(); Self::new(epoch, stakes, &accounts[0].0) } pub fn new_from_bank(bank: &Bank, my_id: &Pubkey) -> Self { let bank_epoch = bank.get_epoch_and_slot_index(bank.slot()).0; - let stakes = staking_utils::vote_account_balances_at_epoch(bank, bank_epoch) + let stakes = staking_utils::vote_account_stakes_at_epoch(bank, bank_epoch) .expect("voting require a bank with stakes"); Self::new(bank_epoch, stakes, my_id) } @@ -105,10 +105,10 @@ impl Locktower { ancestors: &HashMap>, ) -> HashMap where - F: Iterator, + F: Iterator, { let mut stake_lockouts = HashMap::new(); - for (key, account) in vote_accounts { + for (key, (_, account)) in vote_accounts { let lamports: u64 = *self.epoch_stakes.stakes.get(&key).unwrap_or(&0); if lamports == 0 { continue; @@ -361,7 +361,7 @@ impl Locktower { fn initialize_lockouts_from_bank(bank: &Bank, current_epoch: u64) -> VoteState { let mut lockouts = VoteState::default(); if let Some(iter) = staking_utils::node_staked_accounts_at_epoch(&bank, current_epoch) { - for (delegate_id, _, account) in iter { + for (delegate_id, (_, account)) in iter { if *delegate_id == bank.collector_id() { let state = VoteState::deserialize(&account.data).expect("votes"); if lockouts.votes.len() < state.votes.len() { @@ -378,8 +378,8 @@ impl Locktower { mod test { use super::*; - fn gen_accounts(stake_votes: &[(u64, &[u64])]) -> Vec<(Pubkey, Account)> { - let mut accounts = vec![]; + fn gen_stakes(stake_votes: &[(u64, &[u64])]) -> Vec<(Pubkey, (u64, Account))> { + let mut stakes = vec![]; for (lamports, votes) in stake_votes { let mut account = Account::default(); account.data = vec![0; 1024]; @@ -391,14 +391,14 @@ mod test { vote_state .serialize(&mut account.data) .expect("serialize state"); - accounts.push((Pubkey::new_rand(), account)); + stakes.push((Pubkey::new_rand(), (*lamports, account))); } - accounts + stakes } #[test] fn test_collect_vote_lockouts_no_epoch_stakes() { - let accounts = gen_accounts(&[(1, &[0])]); + let accounts = gen_stakes(&[(1, &[0])]); let epoch_stakes = EpochStakes::new_for_tests(2); let locktower = Locktower::new(epoch_stakes, 0, 0.67); let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())] @@ -411,8 +411,8 @@ mod test { #[test] fn test_collect_vote_lockouts_sums() { //two accounts voting for slot 0 with 1 token staked - let accounts = gen_accounts(&[(1, &[0]), (1, &[0])]); - let epoch_stakes = EpochStakes::new_from_stake_accounts(0, &accounts); + let accounts = gen_stakes(&[(1, &[0]), (1, &[0])]); + let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts); let locktower = Locktower::new(epoch_stakes, 0, 0.67); let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())] .into_iter() @@ -426,8 +426,8 @@ mod test { fn test_collect_vote_lockouts_root() { let votes: Vec = (0..MAX_LOCKOUT_HISTORY as u64).into_iter().collect(); //two accounts voting for slot 0 with 1 token staked - let accounts = gen_accounts(&[(1, &votes), (1, &votes)]); - let epoch_stakes = EpochStakes::new_from_stake_accounts(0, &accounts); + let accounts = gen_stakes(&[(1, &votes), (1, &votes)]); + let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts); let mut locktower = Locktower::new(epoch_stakes, 0, 0.67); let mut ancestors = HashMap::new(); for i in 0..(MAX_LOCKOUT_HISTORY + 1) { @@ -754,13 +754,13 @@ mod test { let threshold_size = 0.67; let threshold_stake = (f64::ceil(total_stake as f64 * threshold_size)) as u64; let locktower_votes: Vec = (0..VOTE_THRESHOLD_DEPTH as u64).collect(); - let accounts = gen_accounts(&[ + let accounts = gen_stakes(&[ (threshold_stake, &[(VOTE_THRESHOLD_DEPTH - 2) as u64]), (total_stake - threshold_stake, &locktower_votes[..]), ]); // Initialize locktower - let stakes: HashMap<_, _> = accounts.iter().map(|(pk, a)| (*pk, a.lamports)).collect(); + let stakes: HashMap<_, _> = accounts.iter().map(|(pk, (s, _))| (*pk, *s)).collect(); let epoch_stakes = EpochStakes::new(0, stakes, &Pubkey::default()); let mut locktower = Locktower::new(epoch_stakes, VOTE_THRESHOLD_DEPTH, threshold_size); diff --git a/core/src/staking_utils.rs b/core/src/staking_utils.rs index 0ae538bce4..fae7532c8c 100644 --- a/core/src/staking_utils.rs +++ b/core/src/staking_utils.rs @@ -17,10 +17,10 @@ pub fn get_supermajority_slot(bank: &Bank, epoch_height: u64) -> Option { find_supermajority_slot(supermajority_stake, stakes_and_lockouts.iter()) } -pub fn vote_account_balances(bank: &Bank) -> HashMap { +pub fn vote_account_stakes(bank: &Bank) -> HashMap { let node_staked_accounts = node_staked_accounts(bank); node_staked_accounts - .map(|(id, stake, _)| (id, stake)) + .map(|(id, (stake, _))| (id, stake)) .collect() } @@ -34,12 +34,13 @@ pub fn delegated_stakes(bank: &Bank) -> HashMap { /// At the specified epoch, collect the node account balance and vote states for nodes that /// have non-zero balance in their corresponding staking accounts -pub fn vote_account_balances_at_epoch( +pub fn vote_account_stakes_at_epoch( bank: &Bank, epoch_height: u64, ) -> Option> { let node_staked_accounts = node_staked_accounts_at_epoch(bank, epoch_height); - node_staked_accounts.map(|epoch_state| epoch_state.map(|(id, stake, _)| (*id, stake)).collect()) + node_staked_accounts + .map(|epoch_state| epoch_state.map(|(id, (stake, _))| (*id, *stake)).collect()) } /// At the specified epoch, collect the delegate account balance and vote states for delegates @@ -52,21 +53,18 @@ pub fn delegated_stakes_at_epoch(bank: &Bank, epoch_height: u64) -> Option impl Iterator { - bank.vote_accounts() - .into_iter() - .map(|(account_id, account)| (account_id, Bank::read_balance(&account), account)) +fn node_staked_accounts(bank: &Bank) -> impl Iterator { + bank.vote_accounts().into_iter() } pub fn node_staked_accounts_at_epoch( bank: &Bank, epoch_height: u64, -) -> Option> { +) -> Option> { bank.epoch_vote_accounts(epoch_height).map(|vote_accounts| { vote_accounts .into_iter() - .filter(|(account_id, account)| filter_no_delegate(account_id, account)) - .map(|(account_id, account)| (account_id, Bank::read_balance(&account), account)) + .filter(|(account_id, (_, account))| filter_no_delegate(account_id, account)) }) } @@ -77,12 +75,12 @@ fn filter_no_delegate(account_id: &Pubkey, account: &Account) -> bool { } fn to_vote_state( - node_staked_accounts: impl Iterator, u64, impl Borrow)>, + node_staked_accounts: impl Iterator, impl Borrow<(u64, Account)>)>, ) -> impl Iterator { - node_staked_accounts.filter_map(|(_, stake, account)| { - VoteState::deserialize(&account.borrow().data) + node_staked_accounts.filter_map(|(_, stake_account)| { + VoteState::deserialize(&stake_account.borrow().1.data) .ok() - .map(|vote_state| (stake, vote_state)) + .map(|vote_state| (stake_account.borrow().0, vote_state)) }) } @@ -158,17 +156,17 @@ pub mod tests { // Epoch doesn't exist let mut expected = HashMap::new(); - assert_eq!(vote_account_balances_at_epoch(&bank, 10), None); + assert_eq!(vote_account_stakes_at_epoch(&bank, 10), None); // First epoch has the bootstrap leader expected.insert(voting_keypair.pubkey(), BOOTSTRAP_LEADER_LAMPORTS); let expected = Some(expected); - assert_eq!(vote_account_balances_at_epoch(&bank, 0), expected); + assert_eq!(vote_account_stakes_at_epoch(&bank, 0), expected); // Second epoch carries same information let bank = new_from_parent(&Arc::new(bank), 1); - assert_eq!(vote_account_balances_at_epoch(&bank, 0), expected); - assert_eq!(vote_account_balances_at_epoch(&bank, 1), expected); + assert_eq!(vote_account_stakes_at_epoch(&bank, 0), expected); + assert_eq!(vote_account_stakes_at_epoch(&bank, 1), expected); } #[test] diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index eb5f033c45..a679c9a662 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -33,13 +33,36 @@ use std::time::Instant; /// cache of staking information #[derive(Default, Clone)] pub struct Stakes { - /// stakes - vote_accounts: HashMap, + /// vote accounts + pub vote_accounts: HashMap, /// stake_accounts stake_accounts: HashMap, } +impl Stakes { + pub fn is_stake(account: &Account) -> bool { + solana_vote_api::check_id(&account.owner) || solana_stake_api::check_id(&account.owner) + } + + pub fn update(&mut self, pubkey: &Pubkey, account: &Account) { + if solana_vote_api::check_id(&account.owner) { + if account.lamports != 0 { + self.vote_accounts + .insert(*pubkey, (account.lamports, account.clone())); + } else { + self.vote_accounts.remove(pubkey); + } + } else if solana_stake_api::check_id(&account.owner) { + if account.lamports != 0 { + self.stake_accounts.insert(*pubkey, account.clone()); + } else { + self.stake_accounts.remove(pubkey); + } + } + } +} + type BankStatusCache = StatusCache>; /// Manager for the state of all accounts and programs after processing its entries. @@ -126,9 +149,11 @@ impl Bank { bank.process_genesis_block(genesis_block); // genesis needs stakes for all epochs up to the epoch implied by // slot = 0 and genesis configuration - let stakes = bank.stakes(); - for i in 0..=bank.get_stakers_epoch(bank.slot) { - bank.epoch_stakes.insert(i, stakes.clone()); + { + let stakes = bank.stakes.read().unwrap(); + for i in 0..=bank.get_stakers_epoch(bank.slot) { + bank.epoch_stakes.insert(i, stakes.clone()); + } } bank } @@ -146,7 +171,7 @@ impl Bank { bank.transaction_count .store(parent.transaction_count() as usize, Ordering::Relaxed); - bank.stakes = RwLock::new(parent.stakes()); + bank.stakes = RwLock::new(parent.stakes.read().unwrap().clone()); bank.tick_height .store(parent.tick_height.load(Ordering::SeqCst), Ordering::SeqCst); @@ -175,7 +200,7 @@ impl Bank { // if my parent didn't populate for this epoch, we've // crossed a boundary if epoch_stakes.get(&epoch).is_none() { - epoch_stakes.insert(epoch, bank.stakes()); + epoch_stakes.insert(epoch, bank.stakes.read().unwrap().clone()); } epoch_stakes }; @@ -284,6 +309,10 @@ impl Bank { "solana_bpf_loader", &solana_sdk::bpf_loader::id(), ); + self.register_native_instruction_processor( + &solana_vote_program!().0, + &solana_vote_program!().1, + ); // Add additional native programs specified in the genesis block for (name, program_id) in &genesis_block.native_instruction_processors { @@ -772,33 +801,11 @@ impl Bank { parents } - fn update_stakes_accounts( - accounts: &mut HashMap, - pubkey: &Pubkey, - account: &Account, - ) { - if account.lamports != 0 { - accounts.insert(*pubkey, account.clone()); - } else { - accounts.remove(pubkey); - } - } - fn store(&self, pubkey: &Pubkey, account: &Account) { self.accounts.store_slow(self.slot(), pubkey, account); - if solana_vote_api::check_id(&account.owner) { - Self::update_stakes_accounts( - &mut self.stakes.write().unwrap().vote_accounts, - pubkey, - account, - ); - } else if solana_stake_api::check_id(&account.owner) { - Self::update_stakes_accounts( - &mut self.stakes.write().unwrap().stake_accounts, - pubkey, - account, - ); + if Stakes::is_stake(account) { + self.stakes.write().unwrap().update(pubkey, account); } } @@ -916,8 +923,6 @@ impl Bank { res: &[Result<()>], loaded: &[Result<(InstructionAccounts, InstructionLoaders)>], ) { - let mut stakes = self.stakes.write().unwrap(); - for (i, raccs) in loaded.iter().enumerate() { if res[i].is_err() || raccs.is_err() { continue; @@ -926,33 +931,24 @@ impl Bank { let message = &txs[i].message(); let acc = raccs.as_ref().unwrap(); - for (pubkey, account) in message.account_keys.iter().zip(acc.0.iter()) { - if solana_vote_api::check_id(&account.owner) { - Self::update_stakes_accounts(&mut stakes.vote_accounts, pubkey, account); - } else if solana_stake_api::check_id(&account.owner) { - Self::update_stakes_accounts(&mut stakes.stake_accounts, pubkey, account); - } + for (pubkey, account) in message + .account_keys + .iter() + .zip(acc.0.iter()) + .filter(|(_, account)| Stakes::is_stake(account)) + { + self.stakes.write().unwrap().update(pubkey, account); } } } - /// current stakes for this bank - pub fn stakes(&self) -> Stakes { - self.stakes.read().unwrap().clone() - } - /// current vote accounts for this bank - pub fn vote_accounts(&self) -> HashMap { + pub fn vote_accounts(&self) -> HashMap { self.stakes.read().unwrap().vote_accounts.clone() } - /// stakes for the specific epoch - pub fn epoch_stakes(&self, epoch: u64) -> Option<&Stakes> { - self.epoch_stakes.get(&epoch) - } - - /// vote accounts for the specific epoch - pub fn epoch_vote_accounts(&self, epoch: u64) -> Option<&HashMap> { + /// vote accounts for the specific epoch + pub fn epoch_vote_accounts(&self, epoch: u64) -> Option<&HashMap> { self.epoch_stakes .get(&epoch) .map(|stakes| &stakes.vote_accounts) @@ -1583,13 +1579,13 @@ mod tests { } #[test] - fn test_bank_epoch_stakes() { + fn test_bank_epoch_vote_accounts() { let leader_id = Pubkey::new_rand(); let leader_lamports = 3; let mut genesis_block = create_genesis_block_with_leader(5, &leader_id, leader_lamports).0; // set this up weird, forces future generation, odd mod(), etc. - // this says: "stakes for epoch X should be generated at slot index 3 in epoch X-2... + // this says: "vote_accounts for epoch X should be generated at slot index 3 in epoch X-2... const SLOTS_PER_EPOCH: u64 = MINIMUM_SLOT_LENGTH as u64; const STAKERS_SLOT_OFFSET: u64 = SLOTS_PER_EPOCH * 3 - 3; genesis_block.slots_per_epoch = SLOTS_PER_EPOCH; @@ -1598,10 +1594,10 @@ mod tests { let parent = Arc::new(Bank::new(&genesis_block)); - let stakes0: Option> = parent.epoch_vote_accounts(0).map(|accounts| { + let vote_accounts0: Option> = parent.epoch_vote_accounts(0).map(|accounts| { accounts .iter() - .filter_map(|(pubkey, account)| { + .filter_map(|(pubkey, (_, account))| { if let Ok(vote_state) = VoteState::deserialize(&account.data) { if vote_state.node_id == leader_id { Some((*pubkey, true)) @@ -1614,15 +1610,15 @@ mod tests { }) .collect() }); - assert!(stakes0.is_some()); - assert!(stakes0.iter().len() != 0); + assert!(vote_accounts0.is_some()); + assert!(vote_accounts0.iter().len() != 0); let mut i = 1; loop { if i > STAKERS_SLOT_OFFSET / SLOTS_PER_EPOCH { break; } - assert!(parent.epoch_stakes(i).is_some()); + assert!(parent.epoch_vote_accounts(i).is_some()); i += 1; } @@ -1633,7 +1629,7 @@ mod tests { SLOTS_PER_EPOCH - (STAKERS_SLOT_OFFSET % SLOTS_PER_EPOCH), ); - assert!(child.epoch_stakes(i).is_some()); + assert!(child.epoch_vote_accounts(i).is_some()); // child crosses epoch boundary but isn't the first slot in the epoch let child = Bank::new_from_parent( @@ -1641,7 +1637,7 @@ mod tests { &leader_id, SLOTS_PER_EPOCH - (STAKERS_SLOT_OFFSET % SLOTS_PER_EPOCH) + 1, ); - assert!(child.epoch_stakes(i).is_some()); + assert!(child.epoch_vote_accounts(i).is_some()); } #[test]